Kotlin 2.1 is small on purpose. The compiler rewrite landed in 2.0, so this cycle goes to language ergonomics instead. Most of what's here ships behind preview flags rather than as stable syntax — worth knowing now, worth waiting on before production code depends on it.
#Guard conditions in when
A when with a subject can now carry an extra condition on a branch via if. You get the nested if without leaving the when.
sealed interface Animal
data class Cat(val mouseHunter: Boolean) : Animal
data class Dog(val breed: String) : Animal
fun feed(animal: Animal) {
when (animal) {
is Cat if animal.mouseHunter -> feedCat()
is Cat -> feedFish()
is Dog -> feedDog()
}
}
Preview feature in 2.1. It needs an explicit opt-in, and the syntax can still change before it stabilizes.
Use it when: you have when branches that currently nest an if purely to split one is case into two, and you're comfortable tracking a preview feature through to stabilization.
#Non-local break and continue in inline lambdas
break and continue used to require an actual loop construct. A lambda passed to an inline function like forEach didn't count, even though its body gets inlined straight into the loop's call site. Kotlin 2.1 previews exactly that: a break or continue inside an inlined lambda argument now affects the enclosing loop.
fun findFirstEven(numbers: List<Int>): Int? {
for (group in numbers.chunked(3)) {
group.forEach { value ->
if (value % 2 == 0) return value
}
}
return null
}
Use it when: you currently reach for an early-return trick or a mutable flag variable to fake a break out of a forEach-style call. This is a preview feature, so keep it out of code you can't easily revisit if the syntax shifts before stabilization.
#Multi-dollar string interpolation
String templates get a preview mechanism for controlling how many consecutive $ characters trigger interpolation inside a literal. The target is strings that legitimately contain a pile of literal dollar signs — JSON or shell-script templates — without escaping every one by hand. The exact syntax is still a preview, so read the current documentation before adopting it rather than guessing at the form from a blog post.
#A smaller-footprint standard library addition
kotlin.uuid.Uuid keeps expanding. The multiplatform UUID type arrived as experimental in the 2.0.x line so multiplatform code wouldn't need a separate UUID library or java.util.UUID, which isn't available outside the JVM target anyway. Still experimental in 2.1, still guarded behind an explicit opt-in annotation.
#K2 kapt moves toward stability
Annotation-processing-heavy Android codebases that still depend on kapt get a K2-based kapt implementation to try. When the rest of the compiler moved to K2 in 2.0, kapt kept running its own analysis path and a performance gap opened up; this closes it. If your build still leans on kapt rather than KSP, benchmark it against your current setup. Treat it as an opt-in experiment, not a default, and check the current release notes for the exact opt-in mechanism.
#What to adopt first
- None of the preview language features are production defaults — guard conditions, non-local break/continue, and multi-dollar interpolation all need explicit opt-in and can still change shape before they stabilize. Try them in scratch projects, not shipping code, unless you're prepared to track breaking syntax changes.
- If you're still on kapt, benchmark the K2 kapt path. It's the one item here with a measurable payoff in build time rather than a syntax preference.
- The real adoption move for most teams is just bumping to 2.1 for the continued K2 improvements and bug fixes, independent of any of the preview features.
- If your minimum supported Kotlin or Gradle plugin version is pinned for compatibility with an SDK or library, verify that dependency has certified 2.1 before bumping — point releases can still surface compatibility gaps in annotation-processor-heavy dependency chains.