JUnitCore is the JUnit 4 class that runs test classes from Java code or from the command line and returns a Result with the counts and failures. In JUnit 5 and JUnit 6, the JUnit Platform Launcher API does the same job for every test engine.
We run JUnit tests programmatically when a tool decides which tests to run, for example an internal QA dashboard that starts the checkout tests against a staging server, a smoke test step inside a deployment job, or a custom runner that stops after the first failure.
The following example runs all tests of one package with the Launcher API and collects a summary.
LauncherDiscoveryRequest discoveryRequest = request()
.selectors(selectPackage("com.howtodoinjava.junit.orders"))
.build();
SummaryGeneratingListener listener = new SummaryGeneratingListener();
LauncherFactory.create().execute(discoveryRequest, listener); // runs Jupiter and JUnit 4 tests
long failed = listener.getSummary().getTestsFailedCount(); // failed = 1
Notice that the request only describes which tests to run, and the listener receives the results. We start with JUnitCore for JUnit 4, move to the Launcher API with selectors, filters and summaries, stop a run after the first failure with a JUnit 6 cancellation token, and finish with the Console Launcher.
1. Running JUnit 4 Tests with JUnitCore
The JUnitCore class is a facade for running JUnit 4 and JUnit 3.8 tests. Its static method runClasses() runs the given classes and returns an org.junit.runner.Result, which holds the run count, the ignored count and a list of Failure objects.
The following example is a small online shop. OrderValidator checks the quantity against the stock and computes the shipping cost, which is free from 50 dollars. A JUnit 4 class tests the shipping rule, and a main() method runs it.
public class ShippingJUnit4Test {
private final OrderValidator validator = new OrderValidator();
@Test
public void smallOrderPaysShipping() {
assertEquals(499, validator.shippingCost(1999));
}
@Test
public void largeOrderShipsFree() {
assertEquals(0, validator.shippingCost(7500));
}
}
Result result = JUnitCore.runClasses(ShippingJUnit4Test.class);
for (Failure failure : result.getFailures()) {
System.out.println(failure);
}
System.out.println("Run: " + result.getRunCount()
+ ", failed: " + result.getFailureCount()
+ ", ignored: " + result.getIgnoreCount()
+ ", successful: " + result.wasSuccessful());
Run: 2, failed: 0, ignored: 0, successful: true
JUnitCore also has a main() method, so we can run classes from a terminal. It needs the compiled classes, junit-4.13.2.jar and hamcrest-core-1.3.jar on the classpath and fully qualified class names.
java -cp target/classes:target/test-classes:junit-4.13.2.jar:hamcrest-core-1.3.jar \
org.junit.runner.JUnitCore com.howtodoinjava.junit.orders.ShippingJUnit4Test
JUnit version 4.13.2
..
Time: 0.008
OK (2 tests)
Each dot is one test. JUnitCore knows nothing about JUnit Jupiter, so it does not run @Test methods from org.junit.jupiter.api. For a codebase that mixes both, the Launcher API is the right tool, because it runs JUnit 4 classes through the Vintage engine as well.
2. The JUnit Platform Launcher API
The Launcher API is the entry point that Maven Surefire, Gradle and the IDEs use to run tests. A run has two phases. In discovery, the launcher asks every test engine on the classpath which tests match a LauncherDiscoveryRequest and builds a TestPlan. In execution, the engines run the plan and report each event to the registered TestExecutionListener objects.

The API is in the junit-platform-launcher artifact, which junit-jupiter does not bring along. The junit-bom import supplies its version.
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
</dependency>
The Vintage engine is optional and only needed for the JUnit 4 class. The runner classes of the example live in src/test/java, because they need the test classpath, and the exec-maven-plugin 3.6.4 starts them with classpathScope set to test. The examples use JUnit 6.1.3 on Java 25.
mvn -q test-compile exec:java -Dexec.mainClass=com.howtodoinjava.junit.runner.RunWithLauncher
2.1. Discovering and Executing Tests
A LauncherSession groups several discoveries and executions and releases resources when it is closed, so the recommended pattern opens it in a try-with-resources block. The following runner discovers all tests of the orders package, prints the number of tests in the plan and executes it.
LauncherDiscoveryRequest discoveryRequest = request()
.selectors(selectPackage("com.howtodoinjava.junit.orders"))
.build();
SummaryGeneratingListener listener = new SummaryGeneratingListener();
try (LauncherSession session = LauncherFactory.openSession()) {
Launcher launcher = session.getLauncher();
TestPlan testPlan = launcher.discover(discoveryRequest);
System.out.println("Tests found: " + testPlan.countTestIdentifiers(id -> id.isTest()));
launcher.execute(testPlan, listener);
}
TestExecutionSummary summary = listener.getSummary();
summary.printTo(new PrintWriter(System.out));
summary.printFailuresTo(new PrintWriter(System.out), 3);
Tests found: 7
Test run finished after 317 ms
[ 5 containers found ]
[ 7 tests found ]
[ 0 tests skipped ]
[ 7 tests started ]
[ 6 tests successful ]
[ 1 tests failed ]
Failures (1):
JUnit Jupiter:RefundFailingTest:refundKeepsShippingCost()
MethodSource [className = 'com.howtodoinjava.junit.orders.RefundFailingTest', methodName = 'refundKeepsShippingCost', methodParameterTypes = '']
=> org.opentest4j.AssertionFailedError: expected: <499> but was: <0>
org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:569)
com.howtodoinjava.junit.orders.RefundFailingTest.refundKeepsShippingCost(RefundFailingTest.java:12)
We can see that the plan contains seven tests from three classes, two Jupiter classes and the JUnit 4 class. RefundFailingTest fails on purpose and is excluded from mvn test in the example pom.xml. The second argument of printFailuresTo() limits the stack trace to three lines per failure.
Since JUnit 6, the Vintage engine prints an INFO message during discovery when it finds JUnit 4 classes. The example project turns it off in src/test/resources/junit-platform.properties, which the launcher reads from the classpath.
junit.vintage.discovery.issue.reporting.enabled=false
2.2. Selecting Tests with Selectors and Filters
Selectors say where to look, and filters remove tests from what the selectors found. Both come as static factory methods. The selectors are in DiscoverySelectors.
| Selector | Selects |
|---|---|
| selectPackage(“com.shop”) | All test classes in the package and its subpackages |
| selectClass(“com.shop.OrderTest”) or selectClass(OrderTest.class) | One test class |
| selectMethod(“com.shop.OrderTest#check”) | One test method |
| selectClasspathRoots(Set.of(path)) | Every test class under a classpath root, such as target/test-classes |
| selectUniqueId(“[engine:junit-jupiter]/…”) | One node of a test plan, for example to rerun a failed test |
The following runner uses a tag filter for the checkout tests and a combination of a class, a single method and a class name pattern. Tags are explained in JUnit @Tag.
LauncherDiscoveryRequest byTag = request()
.selectors(selectPackage("com.howtodoinjava.junit.orders"))
.filters(TagFilter.includeTags("checkout"))
.build();
LauncherDiscoveryRequest byNames = request()
.selectors(
selectClass("com.howtodoinjava.junit.orders.ShippingJUnit4Test"),
selectMethod("com.howtodoinjava.junit.orders.OrderValidatorTest#freeShippingFromFiftyDollars"))
.filters(includeClassNamePatterns(".*Test"))
.build();
static String run(Launcher launcher, LauncherDiscoveryRequest request) {
SummaryGeneratingListener listener = new SummaryGeneratingListener();
launcher.execute(request, listener);
TestExecutionSummary summary = listener.getSummary();
return summary.getTestsSucceededCount() + " passed, " + summary.getTestsFailedCount() + " failed";
}
checkout tag: 2 passed, 0 failed
by name: 3 passed, 0 failed
The tag filter keeps the two @Tag(“checkout”) methods and drops the failing refund test, which has no tag. A request can also carry configuration parameters through configurationParameter(key, value), for example to switch on parallel execution for one run only.
2.3. Reading the TestExecutionSummary
The TestExecutionSummary from SummaryGeneratingListener answers the common questions of a custom runner. For a richer report, or to react to each test as it finishes, we write our own TestExecutionListener, as shown in JUnit test listeners.
| Method | Returns |
|---|---|
| getTestsFoundCount(), getTestsStartedCount() | Tests in the plan and tests that started |
| getTestsSucceededCount(), getTestsFailedCount() | Passed and failed tests |
| getTestsSkippedCount(), getTestsAbortedCount() | Disabled tests and tests aborted by an assumption |
| getTotalFailureCount() | Failed tests plus failed containers, such as a failing @BeforeAll |
| getFailures() | A list of Failure objects with the TestIdentifier and the exception |
| printTo(PrintWriter), printFailuresTo(PrintWriter, int) | The text report shown in the output above |
3. Stopping a Run After the First Failure
A smoke test job against a staging server has no reason to continue once the login test fails, because every later test would fail for the same reason. JUnit 6 added a CancellationToken for that case. We pass it in a LauncherExecutionRequest and call cancel() from a listener, and the engines skip every test that has not started yet.
CancellationToken cancellationToken = CancellationToken.create();
TestExecutionListener cancelOnFailure = new TestExecutionListener() {
@Override
public void executionFinished(TestIdentifier id, TestExecutionResult result) {
if (id.isTest() && result.getStatus() == TestExecutionResult.Status.FAILED) {
cancellationToken.cancel();
}
}
};
SummaryGeneratingListener summaryListener = new SummaryGeneratingListener();
Launcher launcher = LauncherFactory.create();
launcher.execute(executionRequest(discoveryRequest)
.listeners(cancelOnFailure, summaryListener)
.cancellationToken(cancellationToken)
.build());
Found 5, started 2, failed 1, skipped 3
The request selects the refund class first and the order class second. After the first failure, the launcher skips the remaining three tests and reports them as skipped. CancellationToken is an experimental API in JUnit 6.0 and later, and the Console Launcher offers the same behavior through its –fail-fast option.
4. Running Tests from the Command Line with the Console Launcher
The Console Launcher is a ready-made main() class on top of the Launcher API. The junit-platform-console-standalone jar contains the Platform, Jupiter, Vintage and Suite engines, so a build server or a Docker image can run compiled tests without Maven. Since JUnit 6, the launcher requires a subcommand such as execute or discover.
java -jar junit-platform-console-standalone-6.1.3.jar execute \
--disable-banner --disable-ansi-colors --details-theme=ascii \
-cp target/classes:target/test-classes \
--select-class com.howtodoinjava.junit.orders.OrderValidatorTest
.
+-- JUnit Platform Suite [OK]
+-- JUnit Jupiter [OK]
| '-- OrderValidatorTest [OK]
| +-- acceptsQuantityWithinStock() [OK]
| +-- freeShippingFromFiftyDollars() [OK]
| '-- rejectsQuantityAboveStock() [OK]
'-- JUnit Vintage [OK]
Test run finished after 111 ms
[ 3 tests found ]
[ 3 tests successful ]
[ 0 tests failed ]
The options mirror the Launcher API. For example, –select-package, –select-method and –scan-classpath are selectors, and –include-tag checkout and –include-classname are filters. The exit code is 1 when a test fails, so a shell script can stop on it.
5. JUnitCore vs Launcher API
For new code, the Launcher API replaces JUnitCore completely. JUnitCore stays useful only in a project that still runs on JUnit 4 alone.
| JUnitCore | Launcher API | |
|---|---|---|
| JUnit version | JUnit 4 (and 3.8) | JUnit 5 and JUnit 6 Platform |
| Runs Jupiter tests | No | Yes |
| Runs JUnit 4 tests | Yes | Yes, through the Vintage engine |
| Select tests | Classes or a Request | Packages, classes, methods, classpath roots, unique IDs |
| Filter by tag or name | Categories runner only | TagFilter, ClassNameFilter, engine filters |
| Result | Result | TestExecutionSummary or any listener |
| Stop on first failure | No | CancellationToken (JUnit 6) |
| Command line | org.junit.runner.JUnitCore | Console Launcher |
For a fixed group of classes that always runs together, a declarative @Suite class is simpler than a launcher program. The Launcher API is the better fit when the selection is computed at runtime.
6. Running Tests Programmatically FAQs
A JUnitCore runner that moves to JUnit 5 or 6 tends to raise questions about the replacement class and about a test plan that stays empty.
6.1. What Replaces JUnitCore in JUnit 5 and JUnit 6?
The Launcher API replaces it. LauncherFactory.create() or LauncherFactory.openSession() returns a Launcher, and a LauncherDiscoveryRequest plus a SummaryGeneratingListener give the same information as JUnitCore.runClasses() and Result.
6.2. Can JUnitCore Run JUnit 5 Tests?
No. JUnitCore runs only JUnit 4 and JUnit 3.8 tests. The old JUnitPlatform runner, which let JUnit 4 tools run Platform tests, came from the junit-platform-runner module, and JUnit 6 removed that module.
6.3. Why Does My Launcher Program Find Zero Tests?
In most cases a test engine is missing from the classpath or the selector points to the wrong place. The artifact junit-jupiter-engine must be on the runtime classpath, and the runner must see the compiled test classes, which is why the example runs it with the test classpath of Maven.
6.4. Do We Need junit-platform-launcher in a Normal Maven Build?
No. Surefire brings its own launcher, so a project that only runs mvn test does not declare it. We add junit-platform-launcher when our own code uses the Launcher API, and Gradle builds declare it as testRuntimeOnly.
7. Conclusion
JUnitCore.runClasses() runs JUnit 4 classes from code and returns a Result, and java org.junit.runner.JUnitCore does the same from a terminal. For JUnit 5 and JUnit 6, the Launcher API takes over. A LauncherDiscoveryRequest with selectors and filters picks the tests, a LauncherSession runs them, and a SummaryGeneratingListener collects the counts and failures.
JUnit 6 adds a CancellationToken to stop a run early, and the Console Launcher wraps the whole API in one jar for scripts. Teams that still have JUnit 4 classes can run them through the Vintage engine and plan the move with JUnit 5 vs JUnit 4. More topics are listed in the JUnit tutorial.
8. References
- JUnit 6.1.3 User Guide – Launcher API
- JUnit 6.1.3 User Guide – Console Launcher
- TestExecutionSummary Javadoc
- JUnit 6.0.0 Release Notes
- JUnitCore Javadoc (JUnit 4.13)
Happy Learning !!
I got an error here (test Cases) and Compiler in Eclipse doesn’t suggest me anything
for (Class testCase : testCases)
{
runTestCase(testCase);
}
}
i also got the same error. Could you find a solution for this?
Could you please give error message or any screen shot of error. That would help.
List testCases = new ArrayList();
I Used Junit Core to run specific tests now i need to get a junit report for the same, how can this be achieved? WIll be greats if u can help me
Have you tried any tool like ANT or maven to get this report generated. OR, you want any of yours own custom solution?
I tried with “mvn surefire-report:report” on a maven project from command prompt and it works like heaven. Produces a very well formatted html report in seconds.
https://maven.apache.org/surefire/maven-surefire-report-plugin/usage.html