JUnit provides @BeforeSuite and @AfterSuite annotations to support initialization and cleanup logic that must run exactly once for an entire test suite. Unlike @BeforeAll and @AfterAll, these callbacks operate at the JUnit Platform suite level and are executed outside individual test classes.
When to use suite-level callbacks
Suite-level callbacks are useful when tests depend on shared infrastructure such as external services, databases, or global configuration that should be initialized once before any test runs and cleaned up only after all tests have completed.
Suite definition and lifecycle hooks
The suite class acts as the orchestration entry point. It selects test classes and hosts the @BeforeSuite and @AfterSuite methods.
Definition of BeforeSuiteVersion: 6.0.0 package org.junit.platform.suite.api;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@API(status = MAINTAINED, since = "1.13.3")
public @interface BeforeSuite {
}
Definition of AfterSuiteVersion: 6.0.0 package org.junit.platform.suite.api;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@API(status = MAINTAINED, since = "1.13.3")
public @interface AfterSuite {
}
Example
Shared Database class
package com.logicbig.example;
import java.util.HashMap;
import java.util.Map;
public class DbService {
private static final DbService INSTANCE = new DbService();
private Map<String, String> data;
private DbService() {
}
public static DbService getInstance() {
return INSTANCE;
}
public void connect(){
//just initializing a map for demo
data = new HashMap<>();
data.put("itemCount", "1000");
}
public String getData(String name){
if(data==null){
throw new RuntimeException("data not initialized");
}
return data.get(name);
}
public void disconnect(){
data = null;
}
}
Test class
package com.logicbig.example;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class SimpleServiceTest {
@Test
void serviceWorks() {
System.out.println("Test executed");
assertEquals("1000", DbService.getInstance()
.getData("itemCount"));
}
}
Suite Class
package com.logicbig.example;
import org.junit.platform.suite.api.AfterSuite;
import org.junit.platform.suite.api.BeforeSuite;
import org.junit.platform.suite.api.SelectClasses;
import org.junit.platform.suite.api.Suite;
@Suite
@SelectClasses(SimpleServiceTest.class)
public class BeforeAfterSuiteExampleSuite {
@BeforeSuite
static void beforeSuite() {
System.out.println("Suite setup");
DbService.getInstance().connect();
}
@AfterSuite
static void afterSuite() {
System.out.println("Suite cleanup");
DbService.getInstance().disconnect();
}
}
Output$ mvn test -Dtest=BeforeAfterSuiteExampleSuite [INFO] Scanning for projects... [INFO] [INFO] -------< com.logicbig.example:junit-5-before-after-suite-usage >-------- [INFO] Building junit-5-before-after-suite-usage 1.0-SNAPSHOT [INFO] from pom.xml [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- resources:3.3.1:resources (default-resources) @ junit-5-before-after-suite-usage --- [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-before-after-suite-usage\src\main\resources [INFO] [INFO] --- compiler:3.11.0:compile (default-compile) @ junit-5-before-after-suite-usage --- [INFO] No sources to compile [INFO] [INFO] --- resources:3.3.1:testResources (default-testResources) @ junit-5-before-after-suite-usage --- [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-before-after-suite-usage\src\test\resources [INFO] [INFO] --- compiler:3.11.0:testCompile (default-testCompile) @ junit-5-before-after-suite-usage --- [INFO] Nothing to compile - all classes are up to date [INFO] [INFO] --- surefire:3.5.0:test (default-test) @ junit-5-before-after-suite-usage --- [INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider [INFO] [INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- Suite setup Test executed [INFO] +--BeforeAfterSuiteExampleSuite SimpleServiceTest - 0.055 ss [INFO] | '-- [OK] serviceWorks - 0.041 ss Suite cleanup [INFO] [INFO] Results: [INFO] [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 3.494 s [INFO] Finished at: 2025-12-22T00:10:32+08:00 [INFO] ------------------------------------------------------------------------
The output of this example is expected because the suite engine guarantees that @BeforeSuite executes once before any selected test classes are run, and @AfterSuite executes once after all tests have completed. The appearance of the setup message before the test output, followed by the teardown message at the very end, confirms that the lifecycle hooks are bound to the suite execution rather than individual test classes.
Example ProjectDependencies and Technologies Used: - junit-jupiter-engine 6.0.1 (Module "junit-jupiter-engine" of JUnit)
Version Compatibility: 5.11.0 - 6.0.1 Version compatibilities of junit-jupiter-engine with this example:
- 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
|