What does @TestInstance(Lifecycle.PER_CLASS) change about how JUnit 5 runs a test class, and what does it enable?
answer
- PER_CLASS = one instance reused for all tests
- Unlocks non-static @BeforeAll/@AfterAll
- Non-static @MethodSource/@TestFactory allowed
- Lose isolation: fields persist between tests
- Set globally via junit.jupiter.testinstance.lifecycle.default
basics
~10 s@TestInstance(Lifecycle.PER_CLASS) tells JUnit to create just one instance of the test class and reuse it for every @Test. Because there's now a shared instance, @BeforeAll and @AfterAll can be non-static methods.
solid answer
~50 sAnnotating the test class with @TestInstance(TestInstance.Lifecycle.PER_CLASS) switches from the default per-method instantiation to a single shared instance reused across every @Test in the class. The main practical payoff is that @BeforeAll and @AfterAll no longer need to be static — since there's one persistent instance, they become ordinary instance methods that can touch instance fields. This is handy when the once-per-class setup naturally wants instance state (e.g. a field-injected dependency, or @MethodSource factory methods that JUnit also allows to be non-static under PER_CLASS). The cost is that instance fields now survive between tests, so you lose automatic isolation: tests can leak state into each other and may become order-dependent unless you reset shared mutable state in @BeforeEach. You enable it per-class via the annotation, or globally via the junit.jupiter.testinstance.lifecycle.default configuration parameter. Use it deliberately, not as a default.
code
java · 16 lines@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class ReportTest {
private List<String> rows; // shared instance state
@BeforeAll // non-static thanks to PER_CLASS
void buildOnce() {
rows = new ArrayList<>(loadExpensiveRows());
}
@BeforeEach
void resetView() {
// reset mutable per-test state so tests stay independent
}
@Test void countsRows() { assertFalse(rows.isEmpty()); }
}go deeper
Knows PER_CLASS reuses one instance and lets @BeforeAll be non-static.
Explains the enabled features (non-static lifecycle/source methods), how to enable it per-class and globally, and that isolation is lost.
Weighs when sharing is safe (immutable fixtures) vs dangerous (mutable state), and prescribes @BeforeEach resets to keep determinism.
Sets suite-wide policy on lifecycle defaults, balancing fixture cost and parallelism against the determinism/maintainability risk of shared state.
## Recap: the default JUnit 5's default is `Lifecycle.PER_METHOD` — a new test-class instance per `@Test`, giving isolation but forcing `@BeforeAll`/`@AfterAll` to be `static`. (See the companion question on PER_METHOD.) ## What PER_CLASS does Putting `@TestInstance(TestInstance.Lifecycle.PER_CLASS)` on the class flips this: **JUnit constructs the test class exactly once and reuses that single instance for all `@Test` methods in the class.** Every test now runs on the *same object*, so its instance fields persist for the whole class run. ```java @TestInstance(TestInstance.Lifecycle.PER_CLASS) class MyTest { private DataLoader loader; // shared across all tests now @BeforeAll void loadOnce() { // NON-static is now legal loader = new DataLoader(); loader.load(); } @Test void a() { /* uses loader */ } @Test void b() { /* same loader instance */ } } ``` ## What it enables 1. **Non-static `@BeforeAll`/`@AfterAll`.** Because a single instance exists for the class's whole lifetime, the once-per-class hooks can be ordinary instance methods that read and write instance fields. Under PER_METHOD they had to be static (no shared instance to call them on). 2. **Non-static factory methods for parameterized/dynamic tests.** `@MethodSource`, `@TestFactory` and similar source methods, which normally must be static, may be instance methods under PER_CLASS. This lets a source method use injected instance state. 3. **Naturally sharing expensive setup as instance state** — e.g. a started embedded server or a loaded dataset held in a field, initialized once in `@BeforeAll`. ## How you turn it on - **Per class:** the `@TestInstance(Lifecycle.PER_CLASS)` annotation (it is `@Inherited`, so it applies to subclasses too). - **Globally:** set the configuration parameter `junit.jupiter.testinstance.lifecycle.default = per_class` (e.g. in `junit-platform.properties`), which changes the default for the whole suite. Use this cautiously — it removes isolation everywhere. ## The catch: you give up automatic isolation With one shared instance, **instance-field state survives from test to test.** If `testA()` mutates a shared field and `testB()` assumes its initial value, `testB()` now depends on whether `testA()` ran first. That reintroduces order-dependence and flakiness — exactly what PER_METHOD prevented. The standard remedy is to **reset shared mutable state in `@BeforeEach`** so each test still starts clean, while still benefiting from the once-built immutable parts done in `@BeforeAll`. ## When to choose it - The once-per-class setup genuinely needs instance state, or - A source/factory method needs to be non-static, or - The expensive fixture is **immutable / read-only** after setup (safe to share). Avoid it when tests mutate shared state and you can't cleanly reset it — the isolation you lose usually isn't worth the convenience.
- Why can @BeforeAll be non-static under PER_CLASS but not under PER_METHOD?PER_CLASS keeps a single shared instance for the whole class, so JUnit can invoke @BeforeAll on that instance. PER_METHOD has no single shared instance (one per test), so it must call @BeforeAll statically at the class level.
- You switched a class to PER_CLASS and now a test only passes when the suite runs in a certain order. What's the likely cause and fix?Shared instance-field state is leaking between tests because the instance is reused. Reset the mutable shared state in @BeforeEach (or stop sharing mutable state) so each test starts from a known baseline.
Like keeping one reusable whiteboard for the whole meeting instead of a fresh sheet per topic — efficient, but you must wipe it between topics or old notes confuse the next one.
saying these in an interview costs you the question
- Saying PER_CLASS improves isolation (it removes the automatic isolation)
- Believing @BeforeAll must always be static even under PER_CLASS
- Forgetting that mutable shared state needs resetting in @BeforeEach
- Thinking PER_CLASS is the framework default