JUnit supports suite-based test execution through the JUnit Platform Suite Engine, allowing multiple test classes and even other suites to be composed into a single execution unit. Nested suite composition builds on this capability by enabling hierarchical grouping of suites, which is particularly useful for large test bases and CI orchestration.
Why Nested Suites?
Instead of placing all tests into a flat suite, nested suites allow tests to be grouped by responsibility, execution cost, or purpose. A top-level suite can reference multiple sub-suites, each of which defines its own selection rules using packages, tags, or explicit classes.
Example
In this example we have following classes:
D:\example-projects\junit-5\junit-5-suite-engine\junit-5-nested-suite-composition\src\test\java>tree /a /f \---com \---logicbig \---example AllTestsSuite.java IntegrationTest.java IntegrationTestSuite.java StagingTest.java StagingTestSuite.java
As the names indicate, we have three suite classes.
AllTestsSuite class selects other two classes (IntegrationTestSuite and StagingTestSuite) which are annotated with @Suite themselves.
Integration Test Class
package com.logicbig.example;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Tag("integration")
public class IntegrationTest {
@Test
void integrationWithCoreComponentTest() {
assertTrue(true);
}
}
Integration Suite
package com.logicbig.example;
import org.junit.platform.suite.api.IncludeTags;
import org.junit.platform.suite.api.SelectPackages;
import org.junit.platform.suite.api.Suite;
@Suite
@SelectPackages("com.logicbig.example")
@IncludeTags("integration")
public class IntegrationTestSuite {
}
Staging Test Class
package com.logicbig.example;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Tag("staging")
public class StagingTest {
@Test
void stagingInitTest() {
assertTrue(true);
}
}
Staging Suite
import org.junit.platform.suite.api.IncludeTags;
import org.junit.platform.suite.api.SelectPackages;
import org.junit.platform.suite.api.Suite;
@Suite
@SelectPackages("com.logicbig.example")
@IncludeTags("staging")
public class StagingTestSuite {
}
The suite class selecting other two suites
package com.logicbig.example;
import org.junit.platform.suite.api.SelectClasses;
import org.junit.platform.suite.api.Suite;
import org.junit.platform.suite.api.SuiteDisplayName;
@Suite
@SuiteDisplayName("Staging and Integration Suite")
@SelectClasses({
IntegrationTestSuite.class,
StagingTestSuite.class
})
public class AllTestsSuite {
}
Output$ mvn test -Dtest=AllTestsSuite [INFO] Scanning for projects... [INFO] [INFO] -------< com.logicbig.example:junit-5-nested-suite-composition >-------- [INFO] Building junit-5-nested-suite-composition 1.0-SNAPSHOT [INFO] from pom.xml [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- resources:3.3.1:resources (default-resources) @ junit-5-nested-suite-composition --- [WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources, i.e. build is platform dependent! [INFO] skip non existing resourceDirectory D:\example-projects\junit-5\junit-5-suite-engine\junit-5-nested-suite-composition\src\main\resources [INFO] [INFO] --- compiler:3.11.0:compile (default-compile) @ junit-5-nested-suite-composition --- [INFO] No sources to compile [INFO] [INFO] --- resources:3.3.1:testResources (default-testResources) @ junit-5-nested-suite-composition --- [WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources, i.e. build is platform dependent! [INFO] skip non existing resourceDirectory D:\example-projects\junit-5\junit-5-suite-engine\junit-5-nested-suite-composition\src\test\resources [INFO] [INFO] --- compiler:3.11.0:testCompile (default-testCompile) @ junit-5-nested-suite-composition --- [INFO] Nothing to compile - all classes are up to date [INFO] [INFO] --- surefire:3.5.0:test (default-test) @ junit-5-nested-suite-composition --- [INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider [INFO] [INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] +--Staging and Integration Suite IntegrationTestSuite IntegrationTest - 0.080 ss [INFO] | '-- [OK] integrationWithCoreComponentTest - 0.061 ss [INFO] +--Staging and Integration Suite StagingTestSuite StagingTest - 0.006 ss [INFO] | '-- [OK] stagingInitTest - 0.002 ss [INFO] [INFO] Results: [INFO] [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 3.418 s [INFO] Finished at: 2025-12-21T04:35:26+08:00 [INFO] ------------------------------------------------------------------------
The execution output confirms that the nested suite composition behaves as expected. When the AllTestsSuite is executed, the JUnit Platform first resolves the referenced sub-suites. The IntegrationTestSuite includes only tests tagged as integration, while the StagingTestSuite includes tests tagged as staging. Because the top-level suite selects these sub-suites explicitly, all tests selected by both suites are executed in a single run.
Example ProjectDependencies and Technologies Used: - junit-jupiter-engine 6.0.1 (Module "junit-jupiter-engine" of JUnit)
Version Compatibility: 5.8.0 - 6.0.1 Version compatibilities of junit-jupiter-engine with this example:
- 5.8.0
- 5.8.1
- 5.8.2
- 5.9.0
- 5.9.1
- 5.9.2
- 5.9.3
- 5.10.0
- 5.10.1
- 5.10.2
- 5.10.3
- 5.10.4
- 5.10.5
- 5.11.0
- 5.11.1
- 5.11.2
- 5.11.3
- 5.11.4
- 5.12.0
- 5.12.1
- 5.12.2
- 5.13.0
- 5.13.1
- 5.13.2
- 5.13.3
- 5.13.4
- 5.14.0
- 5.14.1
- 6.0.0
- 6.0.1
Versions in green have been tested.
- junit-platform-suite-engine 6.0.1 (Module "junit-platform-suite-engine" of JUnit)
- JDK 25
- Maven 3.9.11
|