Flaky tests in Spring — common causes and patterns. — Cracked Java
// Spring Framework & Spring Boot · Testing — Unit, Slice, Integration
SeniorTrickSystem Design

Flaky tests in Spring — common causes and patterns.

A flaky test passes and fails non-deterministically against the same code — and that's worse than a consistently failing one, because teams learn to ignore it and it masks real regressions. Almost all flakiness in Spring suites traces to a handful of root causes, each with a known fix.

Shared mutable state and test ordering

The most common cause. Because Spring caches and reuses one context, singleton beans, static fields, and in-memory caches persist across tests. If test A mutates state and test B assumes a clean slate, B passes alone but fails when A runs first. JUnit doesn't guarantee method order, so the failure is intermittent.

Fix: isolate state. Use @Transactional rollback (or Testcontainers cleanup) for the DB, reset mocks/caches in @AfterEach, and never let tests depend on execution order. As a diagnostic (not a fix) @DirtiesContext can confirm pollution is the cause.

Async without proper waiting

Code under test runs on another thread (@Async, an event listener, a message consumer) and the test asserts before the work finishes — or uses a fixed Thread.sleep that's too short under load. Both are timing-dependent.

// Flaky: races the async work
service.processAsync(order);
assertThat(repo.findById(id)).isPresent();   // may not be saved yet

// Robust: poll until the condition holds (Awaitility)
service.processAsync(order);
await().atMost(5, SECONDS)
       .untilAsserted(() -> assertThat(repo.findById(id)).isPresent());

Replace sleep with Awaitility (await().until(...)) or a CountDownLatch — wait for the condition, not the clock.

Time and ordering dependencies

Tests that read LocalDateTime.now(), depend on the current date, or assume a particular result ordering. Inject a fixed Clock bean so "now" is deterministic, and never assert on the order of a query result without an explicit ORDER BY.

Port conflicts and external resources

@SpringBootTest(webEnvironment = DEFINED_PORT) or hardcoded ports collide in parallel/CI runs. Use RANDOM_PORT. Likewise, Testcontainers' random host ports avoid clashes — don't hardcode container ports.

Parallel execution hazards

Running tests in parallel multiplies shared-state and resource contention. If you enable JUnit parallelism, ensure tests are genuinely independent (no shared static state, isolated DB rows/schemas, random ports).

Mark your status