Kotlin und Spring Boot in der Praxis: Ein Erfahrungsbericht
Kotlin und Spring Boot in der Praxis: Ein Erfahrungsbericht
Ende 2018 haben wir in einem neuen Kundenprojekt zum ersten Mal bewusst Kotlin statt Java als Sprache für ein Spring-Boot-Backend gewählt. Jetzt, über zwei Jahre später, ist Kotlin unsere Standardsprache für neue Spring-Projekte. Zeit für eine ehrliche Bilanz: Was hat sich bewährt, wo sind wir gestolpert und würden wir den gleichen Weg noch einmal gehen?
Die ersten Monate: Java in Kotlin-Syntax
Wer ehrlich ist, muss zugeben: Die ersten ein bis zwei Jahre schreibt man Kotlin, das ziemlich nach Java aussieht. Man deklariert Variablen mit val statt final, benutzt Data Classes statt POJOs und freut sich über die kürzere Syntax. Aber man denkt noch in Java-Mustern. Extension Functions, Scope Functions wie let, apply oder also, Sealed Classes -- all das kommt erst mit der Zeit, wenn man die Sprache wirklich verinnerlicht hat.
Das ist kein Nachteil. Im Gegenteil: Kotlin belohnt einen schrittweisen Lernprozess. Der Code wird von Monat zu Monat idiomatischer, ohne dass man die Produktivität verliert. Neue Teammitglieder mit Java-Hintergrund sind innerhalb von ein bis zwei Wochen arbeitsfähig. Der Einstieg ist deutlich sanfter als bei einem Wechsel zu einer funktionalen Sprache wie Scala.
Was hervorragend funktioniert
Data Classes: Der offensichtlichste Gewinn
In jedem Spring-Boot-Projekt gibt es Dutzende von DTOs, Request-Objekten und Response-Klassen. In Java bedeutet das Getter, Setter, Konstruktoren, equals(), hashCode(), toString() -- entweder von Hand geschrieben oder über Lombok generiert. In Kotlin ist das eine Zeile.
Der Unterschied in der Praxis:
Java (mit Lombok):
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CreateOrderRequest {
@NotBlank private String customerId;
@NotNull private List<OrderItem> items;
private String comment;
private LocalDate deliveryDate;
}
Kotlin:
data class CreateOrderRequest(
@field:NotBlank val customerId: String,
@field:NotNull val items: List<OrderItem>,
val comment: String? = null,
val deliveryDate: LocalDate? = null
)
Auf den ersten Blick spart man nur ein paar Zeilen. Der eigentliche Vorteil liegt tiefer: Kotlin Data Classes sind immutable by default. Die copy()-Funktion macht defensive Kopien trivial. Und das Nullable-Typsystem zeigt direkt in der Klassendefinition, welche Felder optional sind -- kein @Nullable-Annotationswirrwarr mehr. In einem Projekt mit über 80 DTOs summiert sich das zu einer erheblichen Reduktion an Boilerplate und einer deutlich besseren Lesbarkeit.
Null Safety im Produktivbetrieb
Die Null Safety von Kotlin hat in unseren Projekten die Anzahl der NullPointerException-Fehler in Produktion spürbar reduziert. Das klingt nach einer Selbstverständlichkeit, ist in der Praxis aber bemerkenswert. Der Compiler zwingt einen dazu, sich mit jedem potenziellen Null-Wert auseinanderzusetzen -- und zwar zur Compile-Zeit, nicht erst zur Laufzeit.
Besonders wertvoll ist das an den Grenzen des Systems: dort, wo Daten von externen APIs, Datenbanken oder Benutzer-Eingaben kommen. In Java vertraut man darauf, dass die @NotNull-Annotation hält, was sie verspricht. In Kotlin ist der Typ entweder nullable oder nicht -- der Compiler lässt einem keine Wahl.
Extension Functions im Spring-Kontext
Extension Functions sind eines der Features, die man in Kotlin zunächst unterschätzt und dann nicht mehr missen will. In unseren Projekten nutzen wir sie vor allem für zwei Dinge: erstens, um häufig wiederkehrende Konvertierungen lesbar zu machen, und zweitens, um Test-Utilities zu schaffen.
fun Customer.toResponse() = CustomerResponse(
id = this.id,
name = "${this.firstName} ${this.lastName}",
email = this.email,
memberSince = this.createdAt.toLocalDate()
)
// Im Controller dann einfach:
@GetMapping("/{id}")
fun getCustomer(@PathVariable id: Long): CustomerResponse =
customerService.findById(id).toResponse()
Das ist kein revolutionäres Feature. Aber es macht den Code konsistent lesbarer, weil die Konvertierungslogik dort definiert wird, wo sie hingehört -- nah am Datentyp, ohne dass man die Entity-Klasse selbst aufblähen muss.
Die Stolpersteine
JPA und Kotlin: Es funktioniert, aber mit Nachhilfe
JPA-Entities in Kotlin zu schreiben ist möglich, erfordert aber zwei Compiler-Plugins: kotlin-jpa (generiert den für JPA nötigen No-Arg-Konstruktor) und kotlin-allopen (macht Entity-Klassen open, da Hibernate Proxies benötigt). Ohne diese Plugins bekommt man zur Laufzeit kryptische Fehlermeldungen.
Die Konfiguration im build.gradle.kts:
plugins {
kotlin("plugin.spring")
kotlin("plugin.jpa")
kotlin("plugin.allopen")
}
allOpen {
annotation("javax.persistence.Entity")
annotation("javax.persistence.Embeddable")
annotation("javax.persistence.MappedSuperclass")
}
Einmal konfiguriert, funktioniert es zuverlässig. Aber die initiale Fehlersuche hat uns einige Stunden gekostet. Ein weiterer Punkt: Data Classes und JPA-Entities passen nicht perfekt zusammen. JPA erwartet mutable Entities mit einem stabilen Identifier für equals() und hashCode(). Wir sind dazu übergegangen, JPA-Entities als reguläre Klassen zu schreiben und Data Classes nur für DTOs zu verwenden -- eine Trennung, die sich bewährt hat.
Datenbankzugriff: Die Qual der Wahl
Neben JPA mit Hibernate haben wir in einigen Projekten auch Alternativen evaluiert. Spring JdbcTemplate funktioniert mit Kotlin einwandfrei, der neuere JdbcClient in Spring 6 macht das Ganze noch angenehmer. Exposed, das Kotlin-native ORM-Framework von JetBrains, bietet eine elegante DSL, hat aber ein kleineres Ökosystem. Für die meisten unserer Projekte bleiben wir bei JPA, weil die Verbreitung und der Spring-Data-Support unschlagbar sind. Aber wer ein neues Projekt ohne JPA-Altlasten startet, sollte sich Exposed zumindest anschauen.
Coroutines: Potenzial, aber noch nicht unser Alltag
Kotlin Coroutines versprechen elegante asynchrone Programmierung. In der Theorie beeindruckend, in unserer Praxis spielen sie noch eine untergeordnete Rolle. Die meisten unserer Services nutzen Spring MVC mit blockierendem I/O -- und das funktioniert für unsere Anwendungsfälle hervorragend. Der Wechsel auf Coroutines würde bedeuten, auch die gesamte Persistenzschicht auf nicht-blockierende Treiber umzustellen. Der Aufwand steht für uns aktuell nicht im Verhältnis zum Nutzen.
Wo wir Coroutines punktuell einsetzen, ist die Parallelisierung von unabhängigen Service-Aufrufen -- etwa wenn ein Aggregationsendpunkt Daten von drei internen APIs zusammenführt. Hier macht async/await den Code tatsächlich lesbarer als CompletableFuture. Aber flächendeckend haben wir den Schritt noch nicht gemacht.
Kotlin DSLs in der Praxis
Ein Vorteil, den man erst mit der Zeit schätzen lernt: Viele Bibliotheken bieten mittlerweile Kotlin-DSLs an. Gradle Kotlin DSL statt Groovy, Rest Assured mit Kotlin-Extensions, Testcontainers mit Kotlin-Unterstützung. Diese DSLs fühlen sich natürlicher an und bieten echte Typsicherheit -- kein Raten mehr, welche Methoden verfügbar sind.
Fazit: Wir würden es wieder so machen
Nach über zwei Jahren Kotlin in Spring-Boot-Projekten ist unser Resümee eindeutig positiv. Die Sprache reduziert Boilerplate, verhindert ganze Fehlerklassen durch Null Safety und macht den Code ausdrucksstärker. Die Interoperabilität mit Java ist hervorragend -- wir nutzen weiterhin das gesamte Spring-Ökosystem ohne Einschränkungen.
Die Stolpersteine sind real, aber beherrschbar. Die JPA-Plugin-Konfiguration ist eine einmalige Angelegenheit. Die Lernkurve für Java-Entwickler ist moderat. Und die wenigen Bereiche, in denen Kotlin mit dem Java-Ökosystem reibt, sind gut dokumentiert.
Was uns am meisten überrascht hat: Der grösste Gewinn liegt nicht in einem einzelnen Feature, sondern in der Summe vieler kleiner Verbesserungen. Weniger Zeilen Code, weniger Fehlerquellen, lesbarere Tests, ausdrucksstärkere APIs. In jedem einzelnen Fall ist der Unterschied überschaubar. Über ein ganzes Projekt summiert, verändert es die Art, wie man arbeitet.
Für neue Spring-Boot-Projekte ist Kotlin bei uns mittlerweile die Standardwahl. Nicht weil Java schlecht wäre -- Java 16 hat mit Records und Pattern Matching aufgeholt. Sondern weil Kotlin heute schon die Sprache ist, die Java in einigen Jahren sein möchte.
Geschrieben von
Patrick HütterGründer & Software-Architekt
Softwarearchitekt, Ingenieur und Unternehmer. Patrick baut seit über einem Jahrzehnt Produkte und Plattformen — von Enterprise-Backends über Cloud-Native-Infrastruktur bis hin zu KI-gestützten Anwendungen. Als Gründer von encircle360 verbindet er technische Tiefe mit unternehmerischem Denken und treibt Open-Source-Projekte voran, die echten Impact haben.
Das könnte dich auch interessieren
Vom Manifest zur Produktion: Wie ADL, A2A und das Inference Gateway die Agenten-Infrastruktur revolutionieren
05.07.2026 · 12 Min. Lesezeit
Agent-Orchestrierung mit Java: Wie Sie LLM-Agenten produktionsreif in die JVM bringen
04.07.2026 · 6 Min. Lesezeit
Spring AI: Wie Java-Entwickler endlich KI-Features ohne Umwege integrieren
25.03.2026 · 5 Min. Lesezeit