ADR-009: Kotlin as Primary Language
Field |
Value |
|---|---|
Status |
|
Date |
2026-05-12 |
Deciders |
Full Team |
Supersedes |
– |
Superseded by |
– |
Context
We need to choose a primary JVM language for our Spring Modulith backend (ADR-002). The choice affects day-to-day developer productivity, code expressiveness, safety guarantees, and the long-term maintainability of the codebase. The team has existing experience on the JVM and the system is a backend service without UI concerns.
Decision Drivers
Null-safety as a language-level guarantee reduces an entire class of runtime errors
Concise, expressive syntax reduces boilerplate in domain and data classes
Full interoperability with the Java/Spring ecosystem
Coroutine support for non-blocking I/O without callback complexity
Considered Options
Option A – Kotlin
Option B – Java
Decision
Chosen option: Option A – Kotlin is used as the primary language across all modules. Java interoperability is maintained, but all new code is written in Kotlin.
Rationale
The team already has Java background, so the step up to Kotlin is a natural one. Kotlin’s null-safety system catches potential null pointer exceptions at compile time rather than at runtime, which is particularly valuable in a backend service where data flows through multiple layers. Data classes significantly reduce boilerplate for model and event classes — a daily concern given our module structure (ADR-004). Coroutines provide structured, readable non-blocking I/O without the complexity of reactive streams, and integrate well with Spring WebFlux and Spring Data if needed in the future. Spring has first-class Kotlin support, including Kotlin-specific DSLs and extension functions.
Pros and Cons of the Options
Option A – Kotlin
✅ Null-safety at compile time — eliminates a class of NullPointerException bugs
✅ Data classes reduce boilerplate for model and event classes
✅ Coroutines enable non-blocking I/O with sequential, readable code
✅ Full Java interoperability — all Spring and Java libraries work without wrappers
✅ First-class Spring support including Kotlin DSLs and extension functions
❌ Slightly longer compile times than Java in some configurations
❌ Slight adoption barrier at first
Option B – Java
✅ Team experience in Java
✅ Slightly faster compile times in some toolchain setups
❌ Verbose boilerplate for data classes, builders, and null checks (partially mitigated by Records and newer Java features, but still less concise than Kotlin)
❌ Null safety requires external tooling (e.g.
@NonNullannotations) rather than being built into the type system❌ No native coroutine support — reactive programming requires a steeper learning curve
Consequences
Positive:
Concise data and event classes across all modules (aligns with ADR-0003 module structure)
Compile-time null-safety reduces defensive null checks throughout the codebase
Coroutines available if non-blocking I/O becomes a requirement without introducing a reactive framework
Negative / Trade-offs:
All contributors must be comfortable with Kotlin; onboarding Java-only developers requires a short ramp-up period
Kotlin compiler plugins (e.g. for Spring, JPA) must be configured correctly — misconfiguration can cause subtle runtime issues (e.g. open classes for JPA entities)
Follow-up actions:
Ensure Kotlin compiler plugins for Spring (
kotlin-spring) and JPA (kotlin-jpa) are configured in the build fileAdd a Kotlin style guide reference to the team wiki (e.g. official Kotlin coding conventions)
Agree on coroutine usage boundaries — whether coroutines are used broadly or only where explicitly needed