Why must @BeforeAll and @AfterAll methods be static under JUnit 5's default lifecycle?
answer
- Default lifecycle = PER_METHOD = new instance per test
- Once-per-class hook runs before any instance exists
- static = callable on the class, no instance needed
- Shared state -> static fields
- @TestInstance(PER_CLASS) lets them be non-static
basics
~20 sBy default JUnit 5 creates a new instance of the test class for every test method. @BeforeAll/@AfterAll run once, before any instance exists, so they can't belong to an instance — they must be static (belong to the class itself).
solid answer
~40 sJUnit 5's default test-instance lifecycle is PER_METHOD: the framework constructs a brand-new instance of the test class for each @Test method, so per-test state never leaks. @BeforeAll runs once before the first test and @AfterAll once after the last, at a point where there is no single, meaningful instance — and certainly not before any instance is created. A static method belongs to the class, not to any instance, so it can run without one. That's why these methods must be static (and, by the same logic, can only touch static fields for shared state). If you don't want them static, you switch the lifecycle to PER_CLASS with @TestInstance(Lifecycle.PER_CLASS); then one instance is reused for the whole class and @BeforeAll/@AfterAll may be non-static instance methods.
code
java · 32 linesimport org.junit.jupiter.api.*;
// Default PER_METHOD lifecycle: must be static.
class DefaultLifecycleTest {
static DatabaseHandle db; // static shared state
@BeforeAll
static void startDb() { // static required
db = Database.startEmbedded();
}
@AfterAll
static void stopDb() { // static required
db.close();
}
@Test void queryWorks() { /* uses db */ }
}
// PER_CLASS: one instance reused -> non-static allowed.
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class PerClassLifecycleTest {
DatabaseHandle db; // instance field is fine now
@BeforeAll
void startDb() { db = Database.startEmbedded(); } // non-static OK
@AfterAll
void stopDb() { db.close(); }
@Test void queryWorks() { /* uses db */ }
}go deeper
Knows that @BeforeAll must be static by default but may not yet explain why.
Explains the PER_METHOD lifecycle and that no instance exists when the hook runs, so static is required.
Connects static-ness to lifecycle, knows the PER_CLASS escape hatch and its isolation cost, and when each is appropriate.
Reasons about lifecycle as a team convention, including interactions with @Nested, @MethodSource factories, Kotlin/JVM ergonomics, and parallel execution safety.
## Background: instance vs static In Java, a **class** is a blueprint and an **instance** (object) is a concrete thing built from it with `new`. An **instance method** runs *on* a particular object and can read that object's fields (its **instance state**). A **`static`** member belongs to the **class itself**, shared across all instances, and can be called **without any object existing**. That last property is the crux here. ## JUnit's default: a fresh instance per test JUnit 5's **test-instance lifecycle** controls how many objects of your test class JUnit creates. The **default is `PER_METHOD`**: JUnit constructs a **new instance for every `@Test` method**. Why? **Isolation** — fields set during one test can't bleed into another, because the next test runs on a different object. This is a deliberate design choice that makes tests independent and order-insensitive. ## Where @BeforeAll/@AfterAll sit in time `@BeforeAll` runs **once, before the very first test** — therefore **before any per-test instance has been created** (under PER_METHOD, instances are created per test). `@AfterAll` runs **once, after the last test**. There is no single instance that owns 'all the tests', and at `@BeforeAll` time none exists yet. A method that needs an instance (`this`) couldn't run at that moment. ## Why `static` resolves it A **static method** is callable on the **class**, needing no instance. So JUnit can invoke `@BeforeAll`/`@AfterAll` as `TestClass.method()`. The compiler/JUnit therefore **requires them to be static** under PER_METHOD. By the same token, the shared state they set up must live in **static fields** (since there's no instance to hold it). ## The escape hatch: @TestInstance(PER_CLASS) If you annotate the class with `@TestInstance(TestInstance.Lifecycle.PER_CLASS)`, JUnit creates **one instance for the whole class** and reuses it for every test. Now there *is* a stable instance across all tests, so `@BeforeAll`/`@AfterAll` are allowed to be **non-static instance methods** and can use instance fields. The price: state is **no longer reset between tests**, so you give up some isolation (you may need `@BeforeEach` resets). PER_CLASS is handy for `@Nested` classes and `@MethodSource` factory methods (which otherwise must be static too), and for Kotlin where everything is instance-based by default. ## What happens if you forget Under the default lifecycle, a **non-static** `@BeforeAll` causes JUnit to fail with something like 'must be static' (a configuration/`PreconditionViolationException`-style error), not a normal test failure — the class won't run correctly. ## One-line summary No instance exists when once-per-class hooks run under the default PER_METHOD lifecycle, and only `static` members can run without an instance — switch to PER_CLASS to make them instance methods.
- How do you make @BeforeAll non-static, and what do you give up?Annotate the class with @TestInstance(Lifecycle.PER_CLASS). JUnit then reuses one instance for all tests, so @BeforeAll/@AfterAll can be instance methods. You give up per-test isolation: instance fields aren't reset between tests, so you may need @BeforeEach to reset state.
- Why does the default PER_METHOD lifecycle exist at all?To guarantee test isolation: a fresh instance per test means fields from one test can't leak into another, keeping tests independent and order-insensitive.
saying these in an interview costs you the question
- Saying it's an arbitrary JUnit rule with no reason
- Claiming PER_CLASS is the default
- Thinking you can use instance fields from a static @BeforeAll
- Believing non-static @BeforeAll under default just silently no-ops