Spring Boot decides the JUnit version for us through its dependency management, and Spring Boot 4.1.1 manages JUnit 6.0.3, which reaches the project through spring-boot-starter-test. To use a newer JUnit release, we override the version property junit-jupiter.version instead of declaring JUnit versions ourselves.
We check the JUnit version with Spring Boot when a JUnit feature or fix we need is newer than what Spring Boot manages, when the IDE and the build disagree about the JUnit version, or when we upgrade Spring Boot and want to know what changes in the test stack.
The following example sets the JUnit version of a Spring Boot 4.1.1 project to 6.1.3. The property is defined in the pom.xml that uses spring-boot-starter-parent as its parent.
<properties>
<java.version>25</java.version>
<junit-jupiter.version>6.1.3</junit-jupiter.version> <!-- Boot 4.1.1 default is 6.0.3 -->
</properties>
Notice that one property moves every JUnit module, including the JUnit Platform, because Spring Boot imports the junit-bom with that version. We look at the versions each Spring Boot release manages, prove the override with mvn dependency:tree, list what spring-boot-starter-test brings, and write a unit test, a web slice test and a full @SpringBootTest for a small REST API.
1. Which JUnit Version Spring Boot Uses
Every Spring Boot release ships a bill of materials, spring-boot-dependencies, that fixes the versions of the libraries Spring Boot supports. For JUnit, the BOM imports org.junit:junit-bom with the version from the property junit-jupiter.version, so JUnit Jupiter, the Platform and the Vintage engine always match.
| Spring Boot | Managed JUnit version | JUnit generation |
|---|---|---|
| 2.7.18 | 5.8.2 | JUnit 5 |
| 3.0.0 | 5.9.1 | JUnit 5 |
| 3.4.12 | 5.11.4 | JUnit 5 |
| 3.5.16 | 5.12.2 | JUnit 5 |
| 4.0.0 | 6.0.1 | JUnit 6 |
| 4.0.8 | 6.0.3 | JUnit 6 |
| 4.1.1 | 6.0.3 | JUnit 6 |
The values come from the spring-boot-dependencies POM of each release on Maven Central. Spring Boot 4 is the first line on JUnit 6, which matches its Java 17 baseline. The same BOM also manages junit:junit 4.13.2 for projects that still have JUnit 4 tests.
To see the version in our own project, we ask Maven for the dependency tree and filter it to JUnit. Without the override, the example project resolves JUnit 6.0.3.
mvn dependency:tree -Dincludes=org.junit.jupiter:junit-jupiter,org.junit.platform:junit-platform-engine
[INFO] com.howtodoinjava:junit-spring-boot:jar:1.0.0
[INFO] \- org.springframework.boot:spring-boot-starter-test:jar:4.1.1:test
[INFO] \- org.junit.jupiter:junit-jupiter:jar:6.0.3:test
[INFO] \- org.junit.jupiter:junit-jupiter-engine:jar:6.0.3:test
[INFO] \- org.junit.platform:junit-platform-engine:jar:6.0.3:test
2. Overriding the JUnit Version in Spring Boot
Say a team wants a JUnit 6.1 feature, such as the @DefaultLocale extension, in a Spring Boot 4.1.1 service. Spring Boot 4.1.1 still manages 6.0.3, so the team overrides the JUnit property from the Spring Boot version properties list.

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.1.1</version>
<relativePath/>
</parent>
<properties>
<java.version>25</java.version>
<junit-jupiter.version>6.1.3</junit-jupiter.version>
</properties>
[INFO] com.howtodoinjava:junit-spring-boot:jar:1.0.0
[INFO] \- org.springframework.boot:spring-boot-starter-test:jar:4.1.1:test
[INFO] \- org.junit.jupiter:junit-jupiter:jar:6.1.3:test
[INFO] \- org.junit.jupiter:junit-jupiter-engine:jar:6.1.3:test
[INFO] \- org.junit.platform:junit-platform-engine:jar:6.1.3:test
We can see that the engine and the Platform moved to 6.1.3 together with the API. With the Spring Boot parent POM, we change the property and do not import junit-bom ourselves, because two BOMs would manage the same JUnit artifacts and can end up with conflicting versions. The JUnit build support documentation names the same property for Maven and Gradle.
The override works as long as the newer JUnit release is compatible with the Spring test framework of the Spring Boot line, which holds for JUnit 6.1.3 with Spring Boot 4.1.1, as the test run in section 4 shows. We do not move a Spring Boot 3.x project to JUnit 6 this way, because Spring Boot 3.x targets JUnit 5. A project that imports spring-boot-dependencies as a BOM instead of using the parent POM cannot override a property, so it imports junit-bom 6.1.3 before the Spring Boot BOM in dependencyManagement. Gradle builds with the dependency management plugin set the same property name with ext[‘junit-jupiter.version’].
Spring Boot 4.1.1 also manages the Surefire plugin. The build log shows version 3.5.6, which is above the 3.0.0 minimum of JUnit 6, so the project needs no Surefire configuration.
[INFO] --- surefire:3.5.6:test (default-test) @ junit-spring-boot ---
3. What spring-boot-starter-test Contains
The test starter is the only test dependency most Spring Boot projects need. In Spring Boot 4.1.1, its POM declares these test scope dependencies, all in the test scope of our project.
| Library | Purpose |
|---|---|
| junit-jupiter | JUnit API, parameterized tests and the Jupiter engine |
| spring-test, spring-boot-test, spring-boot-test-autoconfigure | Spring TestContext framework, @SpringBootTest, slice infrastructure |
| mockito-core, mockito-junit-jupiter | Mocks and the MockitoExtension |
| assertj-core, hamcrest | Fluent assertions and matchers |
| json-path, jsonassert | Assertions on JSON responses |
| awaitility | Waiting for asynchronous results |
| xmlunit-core | Assertions on XML |
Spring Boot 4 moved the test slices into focused modules. For example, @WebMvcTest comes from spring-boot-webmvc-test, in the package org.springframework.boot.webmvc.test.autoconfigure, and the other test modules follow the same pattern. The matching starter brings the module together with the test starter.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc-test</artifactId>
<scope>test</scope>
</dependency>
4. Testing a Spring Boot App with JUnit
The following example is a recipe REST API. RecipeService holds two recipes, and RecipeController returns one recipe as JSON or 404 when the ID is unknown. We test it at three levels, from the fastest to the most complete.
@GetMapping("/{id}")
public ResponseEntity<Recipe> byId(@PathVariable long id) {
return ResponseEntity.of(recipeService.findById(id));
}
4.1. Plain Unit Test
Logic that does not need Spring is tested with JUnit alone. The test creates the service with new, starts no application context and runs in milliseconds.
class RecipeServiceTest {
private final RecipeService service = new RecipeService();
@Test
void quickRecipesAreUnderTheLimit() {
assertThat(service.quickRecipes(30))
.extracting(Recipe::name)
.containsExactly("Pancakes");
}
}
4.2. Web Slice Test with @WebMvcTest
A slice test starts only the beans of one layer. @WebMvcTest loads the controller, the JSON converters and Spring MVC, and @MockitoBean replaces the service with a Mockito mock. MockMvcTester from Spring Framework 7 sends the request and offers AssertJ-style assertions on the response.
@WebMvcTest(RecipeController.class)
class RecipeControllerTest {
@Autowired
private MockMvcTester mvc;
@MockitoBean
private RecipeService recipeService;
@Test
void returnsRecipeAsJson() {
given(recipeService.findById(1)).willReturn(Optional.of(new Recipe(1, "Pancakes", 20)));
assertThat(mvc.get().uri("/api/recipes/1"))
.hasStatusOk()
.bodyJson().extractingPath("$.name").isEqualTo("Pancakes");
}
@Test
void unknownRecipeIs404() {
given(recipeService.findById(99)).willReturn(Optional.empty());
assertThat(mvc.get().uri("/api/recipes/99")).hasStatus(404);
}
}
4.3. Full Context Test with @SpringBootTest
@SpringBootTest starts the whole application context, as in production, with the real service. @AutoConfigureMockMvc adds a MockMvcTester without starting a server. The annotation already includes @ExtendWith(SpringExtension.class), so the class needs no runner or extension declaration.
@SpringBootTest
@AutoConfigureMockMvc
class RecipeApplicationTest {
@Autowired
private MockMvcTester mvc;
@Test
void contextLoadsAndServesRecipes() {
assertThat(mvc.get().uri("/api/recipes/2"))
.hasStatusOk()
.bodyJson().extractingPath("$.minutes").isEqualTo(35);
}
}
[INFO] Running com.howtodoinjava.junit.recipes.RecipeControllerTest
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.916 s -- in com.howtodoinjava.junit.recipes.RecipeControllerTest
[INFO] Running com.howtodoinjava.junit.recipes.RecipeServiceTest
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.024 s -- in com.howtodoinjava.junit.recipes.RecipeServiceTest
[INFO] Running com.howtodoinjava.junit.recipes.RecipeApplicationTest
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.566 s -- in com.howtodoinjava.junit.recipes.RecipeApplicationTest
[INFO]
[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0
The first Spring test class also takes the time for JVM warm-up and for loading the Spring classes, so the later classes look faster. Spring caches each application context and reuses it in other test classes with the same configuration, so many slice and full context classes with identical setup stay cheap. We keep most tests at the first two levels and use a few full context tests for the wiring. Mocking in Spring Boot tests is covered in Spring Boot, Mockito and JUnit, and tests against real databases in Testcontainers with JUnit and Spring Boot.
| Test type | Annotation | Starts | Use for |
|---|---|---|---|
| Unit test | none | Nothing from Spring | Services, mappers, validation rules |
| Web slice | @WebMvcTest | Controllers, MVC infrastructure | Request mapping, JSON, status codes, security rules |
| Full context | @SpringBootTest | The whole application | Wiring, configuration, end-to-end flows |
5. JUnit and Spring Boot FAQs
The JUnit version and the test modules differ between Spring Boot 3 and Spring Boot 4.
5.1. Does Spring Boot 4 Use JUnit 5 or JUnit 6?
JUnit 6. Spring Boot 4.0.0 manages JUnit 6.0.1, and Spring Boot 4.1.1 manages JUnit 6.0.3. Spring Boot 3.x releases manage JUnit 5. The Jupiter annotations are the same in both, so most tests move from Spring Boot 3 to Spring Boot 4 without code changes, and JUnit 5 vs JUnit 6 lists the exceptions.
5.2. Do We Need a Separate junit-jupiter Dependency?
No. The starter spring-boot-starter-test already depends on junit-jupiter. A second declaration without a version is harmless, but a declaration with a fixed version can mix JUnit versions, so we change the version only through junit-jupiter.version.
5.3. Can a Spring Boot 4 Project Still Run JUnit 4 Tests?
Yes, through the Vintage engine. We add org.junit.vintage:junit-vintage-engine with test scope and exclude org.hamcrest:hamcrest-core from it, because the test starter brings org.hamcrest:hamcrest. The Vintage engine is deprecated in JUnit 6, so the JUnit 4 classes should be migrated, as shown in JUnit 5 vs JUnit 4.
5.4. Why Does @WebMvcTest Not Compile After Upgrading to Spring Boot 4?
Its package changed. In Spring Boot 4 the annotation is located in spring-boot-webmvc-test as org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest, so we add spring-boot-starter-webmvc-test and update the import. The same applies to @AutoConfigureMockMvc.
6. Conclusion
Spring Boot picks the JUnit version through spring-boot-dependencies, and Spring Boot 4.1.1 manages JUnit 6.0.3. The property junit-jupiter.version moves all JUnit modules to another release in one place, and mvn dependency:tree shows which version the build uses. With the Spring Boot parent POM, we never import junit-bom ourselves.
The test starter brings JUnit, Mockito and AssertJ, and the focused Spring Boot 4 modules add slices such as @WebMvcTest. A secured version of the same kind of controller is tested in testing Spring Security authentication with JUnit, and the JUnit tutorial covers the JUnit features themselves.
7. References
- JUnit 6.1.3 User Guide – Build Support (Spring Boot)
- Spring Boot 4.1 – Version Properties
- Spring Boot 4.1 – Test Scope Dependencies
- Spring Boot 4.1 – Test Modules
- Spring Framework 7.0 – MockMvcTester
Happy Learning !!