Java or Kotlin for Spring Boot?
|
This section documents Spring Boot 4.1.x and Spring Framework 7.0.x on the Java 17/21+ baseline, as described by the official Spring Boot reference documentation and each sub-project’s own site (Spring Data, Spring for Apache Kafka, Micrometer, Project Reactor, springdoc-openapi, Spring gRPC, and the others listed in this section’s Bibliography) — which are the references these pages are written and verified against. This content was generated with the assistance of AI and should be verified against those official docs before being relied on in production. Spring Boot and its ecosystem continue to evolve; the examples here target the current 4.1.x / 7.0.x releases. This section’s bibliography lists the reference material consulted while preparing these pages. |
Spring Boot officially supports Kotlin as a first-class language, not merely as "a JVM language that happens to work" — the Spring Initializr’s own project wizard offers a Java/Kotlin language picker right alongside the build-tool choice, and the framework ships dedicated Gradle plugins specifically to smooth over the language differences that would otherwise bite a Kotlin Spring Boot codebase.
First-Class Kotlin Support
Two Kotlin Gradle plugins exist specifically because of how Spring works internally, and any Kotlin Spring Boot project should apply both:
// build.gradle.kts
plugins {
id("org.springframework.boot") version "4.1.0"
kotlin("jvm") version "2.4.0"
kotlin("plugin.spring") version "2.4.0" // "kotlin-spring": auto-opens @Configuration/@Service/etc.
kotlin("plugin.jpa") version "2.4.0" // "kotlin-jpa": auto-opens @Entity, adds a no-arg constructor
}
-
kotlin("plugin.spring")solves the problem described in Classes and Objects: Kotlin classes arefinalby default, but Spring needs to CGLIB-subclass a@Configuration/@Service/@Component/@Controllerclass to install its proxies (for AOP,@Transactional, and similar). The plugin automatically makes every class annotated with a Spring stereotype annotationopen, with no manualopen classneeded anywhere. -
kotlin("plugin.jpa")solves the equivalent problem for JPA: Hibernate needs a no-argument constructor and non-finalclasses/properties to generate lazy-loading proxies for@Entityclasses. The plugin adds both automatically for any class annotated@Entity,@MappedSuperclass, or@Embeddable.
Pros and Cons
| Dimension | Java | Kotlin |
|---|---|---|
Null safety |
no compile-time distinction — |
nullability is part of the type ( |
Boilerplate |
constructors, |
|
Concurrency model |
virtual threads (Project Loom, Virtual Threads) let blocking-style code scale without the reactive-programming tax — Spring Boot 4’s default choice for new blocking-style services. |
coroutines (Coroutines Basics) give similar scalability with
|
Framework proxying / all-open |
no extra step — non- |
requires the |
JPA/Hibernate interop |
no extra step — a no-arg constructor and non- |
requires the |
Java interop / ecosystem |
native — every Spring/Jakarta EE library targets Java directly. |
fully interoperable (Kotlin and the JVM), but occasional
friction at the edges: platform types from unannotated Java APIs, or a Java library’s fluent builder reading
less naturally from Kotlin than |
Compile times / tooling (K2) |
|
the modern K2 compiler closed most of the historical Kotlin-vs-Java compile-time gap, but a large multi-module Kotlin build can still compile somewhat slower than the equivalent Java one. |
Hiring / ramp-up |
larger overall talent pool; most backend engineers already know Java. |
smaller pool specifically for backend Kotlin, though most Java engineers ramp up quickly given the deliberate Java interoperability and similar OOP model. |
Android code sharing |
none — Android’s officially preferred language is Kotlin (Kotlin for Android), so a Java backend shares no source with an Android client team. |
a team already writing Kotlin for an Android app can share DTOs/validation logic/business rules with a Kotlin Spring Boot backend, and staff more fluidly across both. |
Side by Side: a Minimal @RestController
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/{id}")
public ResponseEntity<UserDto> getUser(@PathVariable Long id) {
return userService.findById(id)
.map(ResponseEntity::ok)
.orElseGet(() -> ResponseEntity.notFound().build());
}
}
@RestController
@RequestMapping("/api/users")
class UserController(private val userService: UserService) { // constructor injection, no boilerplate
@GetMapping("/{id}")
fun getUser(@PathVariable id: Long): ResponseEntity<UserDto> =
userService.findById(id)
?.let { ResponseEntity.ok(it) }
?: ResponseEntity.notFound().build()
}
The Kotlin version needs no kotlin-spring open annotation here (Spring proxies the class via the interfaces
it implements when there are any, and constructor-injected, non-proxied beans in general do not require open
at all — the plugin matters most for classes proxied by subclassing, such as many @Configuration classes and
@Transactional-annotated services). Its null-handling reads directly from
Null Safety's ?.let { } / ?: idiom in place of Java’s
Optional.map/.orElseGet.
When to Choose Which
Neither language is a strictly better default — the right choice tracks the team and the surrounding context more than any single row in the table above. Kotlin earns its keep fastest when a team already knows it (most concretely: an Android team extending into backend work, sharing models with their existing app), when null safety and reduced boilerplate would measurably cut down a class of bugs the team already fights, or when the project is new enough that ramp-up cost is a one-time thing rather than a migration. Java remains the steadier default for a large, existing Java codebase, a team without prior Kotlin exposure and no immediate driver to gain one, or a project that leans heavily on Java-only tooling/annotation processors that have no mature Kotlin story. This is guidance, not a mandate — both compile to the same bytecode, run on the same JVM, and are first-class citizens in Spring Boot either way.
References
-
the official Spring Boot reference documentation, which documents Kotlin support throughout rather than in one isolated section.
-
Kotlin docs — Create a RESTful web service with Spring Boot and Kotlin.
-
Kotlin docs — the all-open compiler plugin (
kotlin-springis a preconfigured profile ofkotlin-allopen). -
Kotlin docs — the no-arg compiler plugin (
kotlin-jpais a preconfigured profile ofkotlin-noarg). -
Kotlin docs — Coroutines guide and Virtual Threads — the two concurrency models compared in the table above.
-
Three comparison articles consulted while drafting the pros/cons table above (secondary commentary, not a primary source — the official docs linked throughout this page win on any discrepancy): Baeldung — Spring Boot: Kotlin vs. Java, JetBrains — the State of Developer Ecosystem: Kotlin, and Spring.io — Developing Spring Boot applications with Kotlin.