Real Results: 25min → 12min Build Time in Two Weeks 🎯
One Annotation was Destroying their Performance 😵💫
Hi Mailwal,
Yesterday I told you about Spring context caching and the @DirtiesContext final boss.
Today I want to show you a real example of what fixing this looks like.
Yesterday I told you about Spring context caching and the @DirtiesContext final boss.
Today I want to show you a real example of what fixing this looks like.
The client: A German real estate platform
I worked with a well-known German platform for real estate management and search.
8-person engineering team. Spring Boot backend with React frontend. About 200,000 lines of code.
Their problem: CI pipeline took 25 minutes.
Developers were pushing 3-4 times per day. That's 30+ builds × 25 minutes = 12+ hours of collective waiting per day.
At their team's loaded cost, that was €50,000 per year in wasted CI time.
They hired me to figure out why their tests were so slow.
8-person engineering team. Spring Boot backend with React frontend. About 200,000 lines of code.
Their problem: CI pipeline took 25 minutes.
Developers were pushing 3-4 times per day. That's 30+ builds × 25 minutes = 12+ hours of collective waiting per day.
At their team's loaded cost, that was €50,000 per year in wasted CI time.
They hired me to figure out why their tests were so slow.
The Audit
I started by running their test suite locally with Spring's test cache logging enabled.
The output told the story: Spring reported 89 cache hits and 234 cache misses.
That's a 27% cache hit rate. Terrible.
For comparison, a healthy Spring Boot test suite should have 80-90% cache hit rate.
This meant Spring was creating a new context for almost every test class.
With 150 test classes, that's 150 context creations × 8 seconds each = 20 minutes just in context creation.
The output told the story: Spring reported 89 cache hits and 234 cache misses.
That's a 27% cache hit rate. Terrible.
For comparison, a healthy Spring Boot test suite should have 80-90% cache hit rate.
This meant Spring was creating a new context for almost every test class.
With 150 test classes, that's 150 context creations × 8 seconds each = 20 minutes just in context creation.
Finding the Culprit
I searched their codebase for the usual suspects:
- @TestPropertySource with varying properties: A few instances, but not the main issue
- @MockBean variations: Some inconsistency, but manageable
- @DirtiesContext: BINGO
They had @DirtiesContext on 43 test classes.
Worse, some used it at the method level - meaning every single test method in those classes was destroying and recreating the entire Spring context.
One test class had 12 test methods, each dirtying the context. That's 12 context creations for a single test file. 12 × 8 seconds = 96 seconds for one class.
Worse, some used it at the method level - meaning every single test method in those classes was destroying and recreating the entire Spring context.
One test class had 12 test methods, each dirtying the context. That's 12 context creations for a single test file. 12 × 8 seconds = 96 seconds for one class.
Why they were using @DirtiesContext
I asked the team: "Why @DirtiesContext?"
The answers:
The answers:
- "We copied it from Stack Overflow"
- "Tests were failing without it"
- "Someone said it makes tests more isolated"
Classic cargo culting.
Here's what was actually happening:
Some of their integration tests modified database state and didn't clean up properly. Adding @DirtiesContext "fixed" the problem by throwing away the entire Spring context (including the in-memory database) after each test.
It worked, but at a massive performance cost.
They were using a nuclear option (destroy everything) to solve a simple problem (clean up your test data).
Here's what was actually happening:
Some of their integration tests modified database state and didn't clean up properly. Adding @DirtiesContext "fixed" the problem by throwing away the entire Spring context (including the in-memory database) after each test.
It worked, but at a massive performance cost.
They were using a nuclear option (destroy everything) to solve a simple problem (clean up your test data).
The Fix
I spent a week with their team implementing proper test isolation:
Step 1: Add @Transactional to test classes
This makes each test run in a transaction that's rolled back afterward. Automatic cleanup. No need to destroy the context.
Step 2: Use @Sql for explicit data setup
Instead of modifying shared state, each test declares its data requirements upfront with SQL scripts.
Step 3: Fix the few tests that actually needed cleanup
Some integration tests genuinely modified state outside the database - like writing to the file system or calling external services. We added proper cleanup methods that ran after each test.
Step 4: Remove all @DirtiesContext annotations
Every single one. All 43 instances. Gone.
Step 1: Add @Transactional to test classes
This makes each test run in a transaction that's rolled back afterward. Automatic cleanup. No need to destroy the context.
Step 2: Use @Sql for explicit data setup
Instead of modifying shared state, each test declares its data requirements upfront with SQL scripts.
Step 3: Fix the few tests that actually needed cleanup
Some integration tests genuinely modified state outside the database - like writing to the file system or calling external services. We added proper cleanup methods that ran after each test.
Step 4: Remove all @DirtiesContext annotations
Every single one. All 43 instances. Gone.
The Results
Before:
- Test suite: 25 minutes
- Cache hit rate: 27%
- Spring contexts created: 234
After:
- Test suite: 12 minutes
- Cache hit rate: 89%
- Spring contexts created: 18
25 minutes to 12 minutes. 52% reduction.
No business logic changed. No test coverage lost. Just proper context caching.
No business logic changed. No test coverage lost. Just proper context caching.
The impact on the Team
The changes went beyond just the numbers.
Their tech lead told me: "We just got 13 minutes back per build. That's 6+ hours per day across the team. This paid for itself in the first week."
Developers started running tests locally again instead of pushing and waiting for CI. They caught bugs earlier.
Code reviews got faster because CI feedback came back quickly.
And most importantly - they stopped copying annotations from Stack Overflow without understanding them.
Their tech lead told me: "We just got 13 minutes back per build. That's 6+ hours per day across the team. This paid for itself in the first week."
Developers started running tests locally again instead of pushing and waiting for CI. They caught bugs earlier.
Code reviews got faster because CI feedback came back quickly.
And most importantly - they stopped copying annotations from Stack Overflow without understanding them.
The deeper Lesson
This team wasn't incompetent. They were smart engineers building a successful product used by thousands of companies across the world.
But they didn't understand how Spring's test context caching worked. Nobody does, until someone explains it.
They saw @DirtiesContext solve a problem once, so they used it everywhere. Each usage seemed harmless—just one annotation.
But 43 annotations across the codebase compounded into a 25-minute test suite.
The fix wasn't complex. It was just knowledge: understanding what breaks the cache, why @DirtiesContext is expensive, and how to achieve test isolation without throwing away reusable work.
But they didn't understand how Spring's test context caching worked. Nobody does, until someone explains it.
They saw @DirtiesContext solve a problem once, so they used it everywhere. Each usage seemed harmless—just one annotation.
But 43 annotations across the codebase compounded into a 25-minute test suite.
The fix wasn't complex. It was just knowledge: understanding what breaks the cache, why @DirtiesContext is expensive, and how to achieve test isolation without throwing away reusable work.
What this means for you
If your test suite takes 15+ minutes, there's a good chance you have similar issues.
You don't need to hire me to fix it. You just need to:
You don't need to hire me to fix it. You just need to:
- Check your context cache statistics (Spring logs this)
- Find your @DirtiesContext usage (search the codebase)
- Implement proper test isolation (@Transactional, cleanup methods)
- Remove the expensive annotations
Tomorrow, I'll show you the framework I use to think about all of this: testing is recipes, and Spring Boot gives you excellent tools to implement those recipes.
PS: Want me to walk you through exactly how to audit your test suite for caching issues? Module 8 of my comprehensive Spring Boot online course covers this step-by-step, including how to interpret the cache statistics, how to find every cache-breaking pattern, and how to fix each one.
Joyful testing,
Philip
PS: Want me to walk you through exactly how to audit your test suite for caching issues? Module 8 of my comprehensive Spring Boot online course covers this step-by-step, including how to interpret the cache statistics, how to find every cache-breaking pattern, and how to fix each one.
Joyful testing,
Philip
Copyright © 2026 PragmaTech Digital Solutions Philip Riecks, All rights reserved.
You are receiving this email because you opted in at our website https://rieckpil.de
Our mailing address is:
You are receiving this email because you opted in at our website https://rieckpil.de
Our mailing address is:
PragmaTech Digital Solutions Philip Riecks
Paul-Gossen-Straße 75
c/o Design Offices
Erlangen91052
Germany