kotlin
Companion project for the article Kotlin with Spring Boot 4.1: Null Safety, Coroutines, and What Java Developers Get Wrong on ankurm.com.
Every console block quoted in the article came out of docs/output/. 03-08 are raw curl captures
against a running instance; 01, 02 and 09 are real build/startup transcripts. BookControllerTest.kt
turns the null-safety claims into assertions, so a change in Spring, Kotlin or JSpecify behaviour
breaks the build, not just the prose.
There is deliberately no docs/NN-topic.md chapter folder: the deeper material lives in
collapsible "going deeper" sections inside the article itself, next to the paragraph each one
extends. docs/output/ (captured transcripts) is the one thing that still lives under docs/.
Versions
| Spring Boot | 4.1.1 |
| Spring Framework | 7.0.9 |
| Kotlin | 2.3.21 (Spring Boot 4.1.1's own managed version; latest actual GA is 2.4.20 — see the article) |
| kotlinx.coroutines | 1.10.2 |
| jackson-module-kotlin | 2.22.3 (not BOM-managed; pin it explicitly) |
| JSpecify | 1.0.0 |
| JDK | 25 (GraalVM CE 25.4.4.1.1) |
| Maven | 3.9 |
Quickstart
export JAVA_HOME=/path/to/jdk-25
mvn test # runs BookControllerTest, re-verifying every null-safety claim
mvn spring-boot:run # starts the app on :8090 with 3 seeded books
What's demonstrated
| Area | Where |
|---|---|
allopen/noarg via the spring/jpa kotlin compiler plugins |
pom.xml's kotlin-maven-plugin config; BookService.kt, Book.kt, Application.kt |
Real build failure without the spring plugin (@Configuration class may not be final, then @Bean method must not be private or final) |
docs/output/01-build-failure-without-allopen.txt |
| Clean startup with both plugins restored | docs/output/02-fixed-startup.txt |
suspend fun / Flow<T> controller methods under plain spring-boot-starter-webmvc (Tomcat, not WebFlux) |
BookController.kt (/books, /books/flow) |
| Which thread actually resumes a suspended controller method | /books/thread-check, docs/output/05-thread-check.txt |
Kotlin platform types (String!) for un-annotated Java APIs |
legacy/LegacyJavaApi.java, /books/null-demo/legacy/{id}, docs/output/06-null-demo-legacy.txt |
JSpecify @NullMarked/@Nullable interop, and that it is a static contract, not a runtime guarantee |
legacy/NullMarkedJavaApi.java, /books/null-demo/jspecify/{id}, /books/null-demo/jspecify-honest/{id}, docs/output/07-08 |
| Mixed Kotlin+Java compilation in one Maven module | pom.xml (kotlin-maven-plugin compiles src/main/kotlin + src/main/java; maven-compiler-plugin's default-compile/default-testCompile disabled, java-compile picks up the default src/main/java) |
TestRestTemplate no longer shipping in Spring Boot 4.1's spring-boot-test module |
docs/output/09-no-testresttemplate.txt, BookControllerTest.kt (plain java.net.http.HttpClient instead) |
Captured output
| File | What it shows |
|---|---|
01-build-failure-without-allopen.txt |
both real failures when the spring compiler plugin is removed |
02-fixed-startup.txt |
clean startup once it's restored |
03-books-endpoint.txt |
GET /books, a suspend fun controller method, real JSON |
04-books-flow-endpoint.txt |
GET /books/flow, a Flow<Book> controller method |
05-thread-check.txt |
three runs of /books/thread-check: before/after thread names across a real suspension point |
06-null-demo-legacy.txt |
the platform-type NPE, with no compiler hint anywhere, plus its stack trace |
07-null-demo-jspecify.txt |
the @NullMarked Java method that still NPEs, plus its stack trace |
08-null-demo-jspecify-honest.txt |
the @Nullable-annotated method the Kotlin compiler forces a null check on — no NPE |
09-no-testresttemplate.txt |
TestRestTemplate verified absent from spring-boot-test-4.1.1.jar |
Endpoints default to http://localhost:8090. H2 is in-memory (jdbc:h2:mem:kotlindemo), reseeded
on every restart with three books, one of them with a null title.