Skip to main content

Testing Asynchronous Code with Awaitility (@Async, Kafka Listeners, Schedulers)

Thread.sleep in a test is a guess about timing, not a wait. This post replaces it with Awaitility's await() across a void @Async method, a real in-process @EmbeddedKafka listener, and a @Scheduled job -- plus the pom.xml trap where Spring Boot 4.1.1 split Kafka's autoconfiguration into its own module, leaving @KafkaListener silently unwired.

Migrating to Testcontainers 2.0: Artifact Renames, Package Moves and JUnit 6

Testcontainers 2.0 bundles three independent changes under one version number: 61 artifacts renamed with a testcontainers- prefix, JUnit 4 support physically removed from the GenericContainer type hierarchy (not deprecated — verified by jar inspection, zero classes implement TestRule), and the default Docker API version bumped from 1.32 to 1.44, which is what actually fixes the real Docker Engine 29 compatibility break reported in testcontainers-java#11235. Includes a self-caught mistake: @EnabledIfDockerAvailable only works on the class, not the method, because TestcontainersExtension starts @Container fields in beforeAll before any method-level condition runs.

@DataJpaTest in Spring Boot 4.1 with Testcontainers @ServiceConnection

@DataJpaTest substitutes an embedded database by default, and Replace.NONE alone doesn't guarantee a real connection behind it — but the opposite trap is the real story here: @DataJpaTest DOES run your Flyway/Liquibase migrations by default, through an undocumented fifth meta-annotation that merges autoconfiguration imports across three separate jars. Built with no Docker daemon available, with an honest accounting of what ran for real versus what's verified from source.

Kotlin with Spring Boot 4.1: Null Safety, Coroutines, and What Java Developers Get Wrong

A real Kotlin + Spring Boot 4.1 module, built and run: the exact two BeanDefinitionParsingException failures a missing allopen/noarg compiler plugin produces, a JSpecify @NullMarked Java method that still NPEs because the contract isn't runtime-checked, and a suspend fun controller method proven to resume on kotlinx.coroutines.DefaultExecutor rather than a Tomcat thread.

Spring Boot 4.1 vs Quarkus vs Micronaut: Startup, Memory and Throughput Benchmarked

One Widget REST API, built three times — Spring Boot 4.1.1, Quarkus 3.40.1, and Micronaut 5.2.2 — run in both JVM and GraalVM native mode, with real measured startup time, memory, throughput, artifact size and native build cost. Includes the four real native-image build failures it took to get Micronaut's build working, and the config file that silently got ignored.

ArchUnit for Spring Boot: Enforcing Layered Architecture as a Unit Test

A layered-architecture rule, a no-field-injection rule, and a no-cycles rule, all checked against real compiled bytecode in under two seconds with no Spring context anywhere -- plus a real captured violation, and a FreezingArchRule demonstration showing how to turn a rule on against a codebase that already fails it.

CQRS in Spring Boot Without a Framework: Separate Read Models and Projections

A real CQRS split built with no framework at all: a JPA write model, a second H2 database reached only through its own JdbcTemplate, and two interchangeable projection listeners (synchronous and @Async) that make the latency-versus-freshness trade-off measurable instead of theoretical, including a genuine 404 for an order that was just placed successfully.

Hexagonal Architecture (Ports and Adapters) in Spring Boot 4

A 4-module Maven reactor that proves "the core has no Spring dependency" with mvn dependency:tree instead of a promise, tests the business rule with zero Spring context, and hits (then fixes) three real Spring Boot 4 surprises along the way: @DataJpaTest and @WebMvcTest moved to new artifacts and packages, TestRestTemplate stopped auto-configuring itself, and Spring Data JPA's repository scan silently ignores scanBasePackages in a multi-module layout.