JUnit 6 is the major release that follows JUnit 5, and it keeps the same Jupiter programming model, so most JUnit 5 tests compile and run on JUnit 6 after a version change. JUnit 6.0.0 was released on September 30, 2025, and the current release is 6.1.3. The upgrade raises the minimum Java version to 17, removes APIs that were deprecated during the JUnit 5 years, and gives Platform, Jupiter and Vintage one shared version number.
We need Junit 5 to 6 upgrade when we move a project to Spring Boot 4, which manages JUnit 6 for us, or when we want the new built-in extensions and the stricter CSV handling in parameterized tests.
The following example shows the core of the migration in a Maven build. We change the version of the JUnit BOM (bill of materials, a POM that sets the versions of all JUnit artifacts), and every JUnit artifact follows it.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>6.1.3</version> <!-- was 5.14.4 -->
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Notice that the test code stays in the org.junit.jupiter packages, so ordinary test imports do not change. The rest of this guide compares both versions, then goes through the migration steps, the compile errors we can expect, the behavior changes that show up only at runtime, and the new features worth using.
1. JUnit 5 vs JUnit 6 at a Glance
JUnit 6 is an evolution of JUnit 5, not a rewrite like the move from JUnit 4 to JUnit 5 was. The table compares the latest JUnit 5 line (5.14) with JUnit 6.1, and most rows matter only to some projects.
| Area | JUnit 5 (5.14) | JUnit 6 (6.1) |
|---|---|---|
| Minimum Java version | Java 8 | Java 17 |
| Minimum Kotlin version | Older Kotlin versions supported | Kotlin 2.2 |
| Version numbers | Platform 1.14, Jupiter 5.14, Vintage 5.14 | Platform, Jupiter and Vintage all 6.1.3 |
| Null-safety | No nullability annotations | JSpecify annotations on all modules |
| CSV parser for @CsvSource | univocity-parsers | FastCSV, stricter with malformed input |
| Parameterized display names | fruit=apple | fruit = “apple” |
| JUnit 4 runner (junit-platform-runner) | Available, deprecated | Removed |
| Vintage engine for JUnit 4 tests | Supported | Deprecated |
| Maven Surefire and Failsafe | 2.22.0 and newer | 3.0.0 and newer |
| Locale, time zone and system property extensions | JUnit Pioneer library | Built in since 6.1 |
| Kotlin suspend test methods | Not supported | Supported |
For a project that already runs on Java 17 or newer and has no compiler warnings about deprecated JUnit APIs, the upgrade is mostly a version change. The work grows with every deprecated API the tests still use and with every JUnit 4 test that still runs through the Vintage engine.
2. Should We Upgrade to JUnit 6?
JUnit 6 needs Java 17 or newer to run the tests, and Maven builds need Surefire 3.0.0 or newer. If both are in place, the framework version and the remaining JUnit 4 tests decide how much work the upgrade takes.

Spring Boot 4 manages JUnit 6 through its dependency management (Spring Boot 4.1.1 manages JUnit Jupiter 6.0.3), so a Spring Boot 4 project gets JUnit 6 without a manual version change. Spring Boot 4.1.1 still manages 6.0.3, so a Spring Boot 4 project that wants the 6.1 features from section 5 overrides the version with a Maven property.
<properties>
<junit-jupiter.version>6.1.3</junit-jupiter.version>
</properties>
Spring Boot 3.x projects use JUnit 5, and the simplest path for them is to upgrade JUnit together with the Spring Boot version.
3. How to Migrate from JUnit 5 to JUnit 6
The safest order is to remove the deprecated API usage while we are still on JUnit 5, and only then change the version. When we split the work this way, a failing build always points to one small change.
The following example is a small project with one class, PriceCalculator, which calculates and formats the total price of fruit orders. Its tests use the JUnit 5 APIs that JUnit 6 removed or deprecated, and we migrate it on Java 25 with Maven 3.9.16, from JUnit 5.14.4 to JUnit 6.1.3. The complete project is in the junit-5-to-6-migration folder on GitHub.
3.1. Upgrade to JUnit 5.14 and Fix Deprecation Warnings
Most APIs that JUnit 6 removed were already deprecated in JUnit 5, so the compiler lists many of them for us. We set the BOM to 5.14.4, compile the tests with deprecation warnings turned on, and fix each warning while the build still passes.
mvn test-compile -Dmaven.compiler.showDeprecation=true
3.2. Change the JUnit Version in Maven or Gradle
In Maven, we change the BOM version as shown in the intro and raise Surefire (and Failsafe, if the project has integration tests) to a 3.x version, because JUnit 6 no longer supports Surefire versions below 3.0.0. The JUnit 6 Maven dependency post has the full pom.xml.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.6.0</version>
</plugin>
If the build pins any junit-platform-* artifact to a 1.x version, we remove that version, because the Platform artifacts follow the same 6.1.3 version as Jupiter. In Gradle, we change the BOM version and add junit-platform-launcher as a testRuntimeOnly dependency, as the JUnit 6 Gradle dependency post explains in detail.
dependencies {
testImplementation platform("org.junit:junit-bom:6.1.3") // was 5.14.4
testImplementation "org.junit.jupiter:junit-jupiter"
testRuntimeOnly "org.junit.platform:junit-platform-launcher"
}
test {
useJUnitPlatform()
}
We ran the same tests with Gradle 9.1.0 and this configuration, and all of them passed.
3.3. Remove the Modules That JUnit 6 Dropped
JUnit 6 removed three modules and deprecated a fourth. A build that still declares one of the removed modules fails at dependency resolution, because no 6.x version of that artifact exists.
| Module | Status in JUnit 6 | What to do |
|---|---|---|
| junit-platform-runner | Removed | Delete it, and run the tests through the JUnit Platform in the IDE or build tool instead of the JUnit 4 @RunWith(JUnitPlatform.class) runner |
| junit-platform-jfr | Removed | Delete it; the Java Flight Recorder events are part of junit-platform-launcher |
| junit-platform-suite-commons | Removed | Delete it; its classes moved into junit-platform-suite |
| junit-jupiter-migrationsupport | Deprecated | Replace the JUnit 4 rule support with Jupiter extensions before the next major version removes it |
3.4. Fix the Compile Errors
On JUnit 5.14.4, our project showed only one warning, for MethodOrderer.Alphanumeric. Other changes, such as the removed lineSeparator attribute, were never marked deprecated in JUnit 5, so they appear only when we compile against JUnit 6. Compiling the JUnit 5 version of our tests against JUnit 6.1.3 gives two errors and two warnings.
[WARNING] CallCounterExtension.java:[16,9] <K,V>getOrComputeIfAbsent(K,java.util.function.Function<? super K,? extends V>,java.lang.Class<V>) in org.junit.jupiter.api.extension.ExtensionContext.Store has been deprecated
[WARNING] JreConditionTest.java:[12,32] JAVA_11 in org.junit.jupiter.api.condition.JRE has been deprecated and marked for removal
[ERROR] CsvFileSourceTest.java:[15,65] cannot find symbol
symbol: method lineSeparator()
location: @interface org.junit.jupiter.params.provider.CsvFileSource
[ERROR] MethodOrderTest.java:[9,31] cannot find symbol
symbol: class Alphanumeric
location: interface org.junit.jupiter.api.MethodOrderer
Each message maps to a one-line fix. The table lists the changes that JUnit 6 forces most often, with the JUnit 5 code on the left.
| JUnit 5 code | JUnit 6 code | Why |
|---|---|---|
| @TestMethodOrder(MethodOrderer.Alphanumeric.class) | @TestMethodOrder(MethodOrderer.MethodName.class) | Alphanumeric was removed |
| @CsvFileSource(resources = “/prices.csv”, lineSeparator = “\n”) | @CsvFileSource(resources = “/prices.csv”) | JUnit 6 detects \n, \r and \r\n by itself |
| @EnabledForJreRange(min = JRE.JAVA_11) | Remove it, because every test runs on Java 17 or newer; keep a condition only for a newer version, such as minVersion = 21 | JAVA_8 to JAVA_16 are deprecated |
| store.getOrComputeIfAbsent(key, factory, type) | store.computeIfAbsent(key, factory, type) | getOrComputeIfAbsent() is deprecated |
| org.junit.jupiter.engine.Constants | org.junit.jupiter.api.Constants | The engine class is deprecated since 6.1 |
The extension store change is the one most likely to hide in shared test utilities. In our example, the beforeEach(ExtensionContext context) method of a small extension counts the tests that run, and only the method name changes.
ExtensionContext.Namespace NAMESPACE = ExtensionContext.Namespace.create(CallCounterExtension.class);
ExtensionContext.Store store = context.getRoot().getStore(NAMESPACE);
// JUnit 5: store.getOrComputeIfAbsent("calls", key -> new AtomicInteger(), AtomicInteger.class)
AtomicInteger counter = store.computeIfAbsent("calls", key -> new AtomicInteger(), AtomicInteger.class);
3.5. Automate the Migration With OpenRewrite
For a large codebase, the OpenRewrite JUnit 5 to 6 recipe applies most of these changes for us. OpenRewrite is a tool that edits source code and build files with predefined recipes, and it runs as a Maven or Gradle plugin. We ran it on the JUnit 5 version of our project with the plugin version available on Maven Central.
mvn org.openrewrite.maven:rewrite-maven-plugin:6.46.1:run \
-Drewrite.recipeArtifactCoordinates=org.openrewrite.recipe:rewrite-testing-frameworks:RELEASE \
-Drewrite.activeRecipes=org.openrewrite.java.testing.junit6.JUnit5to6Migration
The recipe changed the JUnit version to 6.1.3 and Surefire to 3.6.0, replaced Alphanumeric with MethodName and removed the lineSeparator attribute. The recipe also deleted the @EnabledForJreRange(min = JRE.JAVA_11) condition, because that condition is always true on Java 17. The getOrComputeIfAbsent() call stayed as it was, so we still review the compiler warnings after the recipe runs.
The OpenRewrite documentation lists newer plugin versions that it serves from a repository that needs credentials. The 6.46.1 plugin from Maven Central works without credentials, and RELEASE in the command resolved to version 3.44.0 of the recipe library.
4. Behavior Changes to Check After Upgrading
Some changes do not break the build. The tests still compile, but test names, CSV parsing or execution order differ, so we compare one CI run before and after the upgrade.
4.1. Parameterized Test Display Names
JUnit 6 writes argument names as name = value and puts every argument that comes from CSV text in quotes, numbers included. The following parameterized test prints its own display name, and we ran it once on each version with the -parameters compiler flag (maven.compiler.parameters set to true), which lets JUnit see the parameter names.
private final PriceCalculator calculator = new PriceCalculator();
@ParameterizedTest
@CsvSource({
"apple, 5, 2.0, 10.0",
"banana, 3, 1.5, 4.5"
})
void total(String fruit, int quantity, double unitPrice, double expected, TestInfo info) {
System.out.println("Display name: " + info.getDisplayName());
assertEquals(expected, calculator.total(quantity, unitPrice));
}
JUnit 5.14.4
Display name: [1] fruit=apple, quantity=5, unitPrice=2.0, expected=10.0
JUnit 6.1.3
Display name: [1] fruit = "apple", quantity = "5", unitPrice = "2.0", expected = "10.0"
Test reports, CI dashboards and flaky-test trackers that identify tests by their display name see these tests as new tests after the upgrade. A test that sets its own name attribute on @ParameterizedTest keeps its pattern, but the placeholders inside it use the new format.
4.2. Stricter CSV Parsing in @CsvSource and @CsvFileSource
JUnit 6 parses parameterized test data with the FastCSV library instead of univocity-parsers. Malformed rows that JUnit 5 accepted fail in JUnit 6, for example a character right after a closing quote.
@ParameterizedTest
@CsvSource("'apple'x, 5") // passes on JUnit 5.14.4, fails on JUnit 6.1.3
void textAfterClosingQuote(String fruit, int quantity) {
assertNotNull(fruit);
}
org.junit.jupiter.params.provider.CsvParsingException: Failed to parse CSV input configured via @org.junit.jupiter.params.provider.CsvSource(...)
Caused by: org.junit.jupiter.params.shadow.de.siegmar.fastcsv.reader.CsvParseException: Unexpected character after closing quote: 'x' (0x78)
Two more CSV rules changed. The attributes ignoreLeadingAndTrailingWhitespace and nullValues apply to header fields too, and since 6.0.1 a new commentCharacter attribute (default #) controls which lines count as comments.
4.3. Test Order in @Nested Classes
In JUnit 6, @TestMethodOrder on a test class also applies to its @Nested classes, the same way @TestClassOrder already did. JUnit 6 also runs @Nested classes in a fixed order that does not change between runs, but that order is not alphabetical.
@TestMethodOrder(MethodOrderer.MethodName.class)
class MethodOrderTest {
@Nested
class Inner { // JUnit 6: methods also run in name order here
@Test
void dTest() {
}
@Test
void cTest() { // runs before dTest()
}
}
}
If one @Nested class needs the default order back, we annotate it with @TestMethodOrder(MethodOrderer.Default.class). Tests that depend on running in a certain order are fragile in any version, so the upgrade is a good moment to remove that dependency.
4.4. Other Runtime Changes
A few smaller changes affect only projects that use these settings, so we search the code and the junit-platform.properties file for them.
- Invalid values for enum-based configuration parameters, such as junit.jupiter.execution.parallel.mode.default, fail the run instead of being ignored.
- String to Locale conversion in parameterized tests always uses the BCP 47 format, such as en-US, and the junit.jupiter.params.arguments.conversion.locale.format parameter was removed.
- The junit.jupiter.tempdir.scope parameter for @TempDir is no longer supported.
- The ConsoleLauncher, the command-line tool for running tests outside an IDE or build tool, needs a subcommand such as execute, and it accepts only the standard option spellings, such as –help instead of -help.
5. New Features in JUnit 6 Worth Using
JUnit 6.1 added extensions that many projects used to get from the JUnit Pioneer library. The extensions set the default locale, the default time zone or a system property for one test and restore the old value afterwards.
@Test
@DefaultLocale("en-US")
void formatsUsDollars() {
assertEquals("$10.00", calculator.format(10.0)); // passes
}
@Test
@DefaultTimeZone("Asia/Kolkata")
void usesKolkataTimeZone() {
assertEquals("Asia/Kolkata", TimeZone.getDefault().getID()); // passes
}
@Test
@SetSystemProperty(key = "store.currency", value = "EUR")
void readsSystemProperty() {
assertEquals("EUR", System.getProperty("store.currency")); // passes
}
The annotations are in the org.junit.jupiter.api.util package, and the test class has a PriceCalculator field named calculator and imports java.util.TimeZone. Projects that used the Pioneer versions can switch to them and drop one dependency, and the current OpenRewrite recipe from section 3.5 lists a step for this change.
JUnit 6 also brings a few features that matter to fewer projects.
- All JUnit modules use JSpecify annotations (a standard set of @Nullable and @NullMarked annotations), so tools such as NullAway warn when we pass null where JUnit does not accept it. The JUnit 6 nullability post explains @Nullable and @NullMarked.
- Kotlin tests can be suspend functions, so coroutine code no longer needs a runBlocking wrapper.
- The ConsoleLauncher has a –fail-fast option that stops the run after the first failure, based on a new cancellation API that build tools and IDEs can use.
- JUnit 6.1 adds an org.junit.start module for running tests from Java compact source files with a single module import.
6. JUnit 4 Tests and the Vintage Engine
The Vintage engine, which runs JUnit 4 tests on the JUnit Platform, still works in JUnit 6, but it is deprecated and reports an INFO-level message when it finds a JUnit 4 test class. JUnit describes it as a temporary bridge, so a project that still has JUnit 4 tests should plan to move them to Jupiter.
The differences between the two programming models are covered in JUnit 5 vs JUnit 4, and everything in that post also applies when the target is JUnit 6.
7. JUnit 5 to JUnit 6 FAQs
Teams planning this upgrade often ask whether their Java version, their framework or their other test libraries block it.
7.1. Is JUnit 6 Backward Compatible With JUnit 5?
Mostly, yes. JUnit 6 keeps the Jupiter annotations, assertions and extension model, so tests compile after the fixes in section 3.4, and only the behavior changes in section 4 need a check.
7.2. Does JUnit 6 Work With Java 11?
No. JUnit 6 needs Java 17 or newer, so a project that runs its tests on Java 8 or Java 11 stays on JUnit 5.14 until it upgrades Java.
7.3. Do I Need to Change My Imports?
No, not for ordinary tests. The packages stay the same, for example org.junit.jupiter.api.Test and org.junit.jupiter.params.ParameterizedTest. Only code that uses removed or moved classes, such as MethodOrderer.Alphanumeric or org.junit.jupiter.engine.Constants, needs a change.
7.4. Does Spring Boot 3 Support JUnit 6?
Spring Boot 3.x manages JUnit 5, and Spring Boot 4 manages JUnit 6. A Spring Boot 3 project on Java 17 can set the junit-jupiter.version property to 6.1.3, but upgrading JUnit together with Spring Boot 4 keeps the versions that Spring tests with.
7.5. Does Mockito Work With JUnit 6?
Yes. Mockito 5 and its MockitoExtension run on JUnit 6, as the examples in the Mockito 5 tutorial show. For other test libraries, we check that their latest version supports JUnit 6 before the upgrade.
8. Conclusion
Moving from JUnit 5 to JUnit 6 is a small upgrade for most projects. Once the tests run on Java 17, the build uses Surefire 3 and no deprecated JUnit APIs are left, the change is one BOM version.
The parts that need attention are the ones that do not break the build, such as the new display names in test reports and the stricter CSV parsing. One CI run before and one after the upgrade shows both, and the built-in locale, time zone and system property extensions are a good reason to make the move.
9. References
- JUnit 6.0.0 Release Notes
- JUnit 6.1 Release Notes
- JUnit User Guide
- OpenRewrite JUnit 6 Migration Recipe
- Maven Surefire Plugin
Happy Learning !!