tl;dr: I keep running into a minor problem every time I write tests for features in Spring Boot projects. It's a small, almost negligible thing, but once a codebase has swelled up to the size of a big field, running targeted tests for one domain means more clicking and file-explorer wrangling than I'd like. JUnit tags turned out to be a genuinely good fix for my little problem.
The problem and what drove me to look
I was working on a notification feature that builds a summarized report of background data syncs in our Connect product. Like most Spring Boot apps, the service-to-test ratio sits roughly 1-to-1, which gives you a sense of how many test classes we're talking about. Every time I wanted to run "just the tests related to this feature," I had to wrangle my way through a large pile of service and component classes.
I'm also trying to build the muscle for doing TDD on every new feature I touch (something I've never been great at). And as a lazy developer, I wanted a way to run unit, slice, and integration tests within the scope of what I'm working on — without clicking test classes one by one, or praying that all the components I want to test happen to live in the same package so I can gather and run them all at once.
My previous workaround was to pin tabs in IntelliJ's Run panel, just so I wouldn't lose my grouped runs. It worked, but it felt fragile, and I started wondering if there was a more convenient way.
There is. It's this thing called tags in JUnit.
Doing my research on tags
Tags are exactly what I was looking for. Picture this: with tags I can run tests that are "notification AND unit-test", "notification AND integration-test", or "notification AND (unit-test OR integration-test)". It lets me work on a feature's tests and run them with surgical precision against everything else in the suite.
There are syntax rules for tag names, but they're light enough that you can stay out of trouble if you pick your tag names sensibly.
Here are the expressions I planned to use for my own case:
| Expression | What it runs |
|---|---|
sync-notification & unit-test |
Unit tests for the notification feature |
sync-notification & integration-test |
Integration tests for the notification feature |
sync-notification |
All tests for the notification feature |
Tagging tests
Sample of a tagged unit test:
@Tag("sync-notification")
@Tag("unit-test")
class SyncNotificationProcessingCommandHandlerTest {
// commented out for brevity...
}
Sample of a tagged integration test:
@Tag("sync-notification")
@Tag("integration-test")
@ActiveProfiles("test")
@SpringBootTest(classes = ConnectPullApiApplication.class)
class SyncNotificationProcessingCommandHandlerIT {
// commented out for brevity...
}
Running targeted tests in IntelliJ
- In IntelliJ IDEA, click Run > Edit Configurations.
- Under the JUnit group, click the + icon in the top-right corner to add a new run configuration.
- Give it a name. On the Test kind dropdown, choose Tags.
- Enter the tag expression you want to filter by.
- Click Apply, then Run.
Questions that come to mind
Can I use tag expressions with Maven or Gradle?
Yes. Both have native support for filtering tests by tag. In Maven Surefire, use the groups and excludedGroups configuration. In Gradle, use useJUnitPlatform { includeTags 'sync-notification' } inside your test task.
Do tags work with Spring's @SpringBootTest and slice annotations like @WebMvcTest?
Yes. @Tag is independent of Spring's test annotations, so you can stack them on the same class without conflict — the integration test sample above shows exactly that.
Is there a downside to having lots of tags?
Mainly maintenance. If tag names drift (e.g., unit-test vs unit_test vs unitTest), your filter expressions silently stop matching things. Pick a convention early and document it somewhere your team will actually see.
Conclusion
Working in a large Spring Boot codebase comes with the challenge of running targeted tests. Solid package layout — onion, clean architecture, whatever flavor you prefer — helps by grouping related classes, and running tests by directory gets you part of the way there. But that approach has a ceiling, and eventually you want more flexibility and granularity over what runs.
JUnit tags solve that for me, and combined with IntelliJ's run configuration support, they give developers fine-grained control over inclusions and exclusions for any given test run. Small problem, small fix, big quality-of-life win.

Top comments (0)