Files

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.