The JUnit team has released JUnit 6.0.0 (GA on September 30, 2025), unifying version numbers across Platform, Jupiter, and Vintage and raising the minimum to Java 17 (and Kotlin 2.2 for Kotlin test code). The update adds native support for Kotlin suspend
tests, a new CancellationToken
API with fail-fast execution in the ConsoleLauncher
, built-in Java Flight Recorder (JFR) listeners, adoption of JSpecify nullability across modules, and a switch to FastCSV for CSV-driven parameterized tests. Vintage remains as a bridge for JUnit 4, but is now deprecated.
For Kotlin users, the most visible improvement is direct suspend
support. Previously, coroutine tests often wrapped bodies in runBlocking
; with JUnit 6, developers can declare suspend
on test and lifecycle methods and call suspending APIs directly. This removes boilerplate and makes coroutine tests read like the production code they exercise.
Consider the following code snippets:
// Before JUnit 6
@Test
fun foo() = runBlocking {
delay(1000)
assertEquals(1, 1)
}
// With JUnit 6
@Test
suspend fun foo() {
delay(1000) // suspend call works directly
assertEquals(1, 1)
}
In terms of execution semantics, JUnit 6 defines a deterministic, intentionally nonobvious order for nested classes. It also introduces MethodOrderer.Default
and ClassOrderer.Default
and inherits @TestMethodOrder
into @Nested
classes. Developers can now also impose an order for nested classes using @TestClassOrder
with @Order
.
The following example runs PrimaryTests
before SecondaryTests
:
import org.junit.jupiter.api.*;
@TestClassOrder(ClassOrderer.OrderAnnotation.class)
class OrderedNestedTestClassesDemo {
@Nested @Order(1)
class PrimaryTests {
@Test void test1() {}
}
@Nested @Order(2)
class SecondaryTests {
@Test void test2() {}
}
}
All JUnit modules now use JSpecify nullability annotations to explicitly indicate which method parameters, return types, and fields can be null. This provides better IDE support, improved compile-time safety, and more precise documentation for Kotlin users who benefit from proper nullable/non-nullable type distinctions.
JUnit 6 migrates from the unmaintained univocity-parsers library to FastCSV for @CsvSource
and @CsvFileSource
annotations. FastCSV is significantly faster, RFC 4180 compliant, has zero dependencies, and provides better error reporting for malformed CSV data. This change improves consistency in CSV parsing behaviour and overall test execution performance.
Cancellation and early-exit behavior also get a significant upgrade. The Platform now exposes CancellationToken
, which the launcher passes to engines. Developers can wire their own listener to cancel the run on the first failure, and the ConsoleLauncher
adds a --fail-fast
flag that does this automatically. The snippet below shows a listener-driven approach using the new LauncherExecutionRequest
API. Engines like Jupiter and Suite honour the token, so the execution aborts cleanly.
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.discoveryRequest;
import static org.junit.platform.launcher.core.LauncherExecutionRequestBuilder.executionRequest;
import java.io.PrintWriter;
import org.junit.platform.engine.CancellationToken;
import org.junit.platform.engine.TestExecutionResult;
import org.junit.platform.launcher.*;
import org.junit.platform.launcher.core.LauncherFactory;
import org.junit.platform.launcher.listeners.SummaryGeneratingListener;
public class FailFastLauncher {
static void main() {
CancellationToken token = CancellationToken.create();
TestExecutionListener failFast = new TestExecutionListener() {
@Override
public void executionFinished(TestIdentifier id, TestExecutionResult result) {
if (result.getStatus() == TestExecutionResult.Status.FAILED) {
token.cancel();
}
}
};
SummaryGeneratingListener summary = new SummaryGeneratingListener();
LauncherDiscoveryRequest discover = discoveryRequest()
.selectors(selectClass(FastFailDemoTest.class))
.build();
LauncherExecutionRequest exec = executionRequest(discover)
.cancellationToken(token)
.listeners(failFast, summary)
.build();
try (LauncherSession session = LauncherFactory.openSession()) {
session.getLauncher().execute(exec);
}
summary.getSummary().printTo(new PrintWriter(System.out, true));
}
}
JFR support is now built into the launcher under org.junit.platform.launcher.jfr
, replacing the old junit-platform-jfr
artifact. Developers can start a JFR recording when launching tests and then inspect discovery and execution events in JDK Mission Control or the *.jfr
tools without adding any extra dependency. Details are documented under “Flight Recorder Support” in the User Guide.
The 6.0.0 release removes the long-deprecated junit-platform-runner (JUnit 4 runner) and various legacy reflection and runner APIs. The team recommends using native Platform integrations in IDEs/build tools or adopting Jupiter directly; Vintage remains only as a temporary bridge and is now formally deprecated. A migration wiki is available for teams upgrading from 5.x.
For most teams already on Java 17 and JUnit 5.14, adoption should be a routine dependency bump followed by a quick dry run, modernizing any build plugins (e.g., Surefire/Failsafe ≥ 3.0), and validating CSV-driven tests. Kotlin users can simplify coroutine testing with direct suspend methods, and JFR integration becomes easier to adopt for performance investigations. Teams still on JUnit 4 should plan migration work, as Vintage’s deprecation signals the end of that compatibility path.