JUnit does not guarantee that lifecycle methods of the same type defined within a single class are executed in any particular order. For example,
if multiple methods are annotated with @BeforeEach, they can be executed in an unpredictable sequence. The same applies to
@BeforeAll, @AfterEach, and @AfterAll.
The same limitation exists in JUnit 4 with the corresponding annotations @Before, @After, @BeforeClass, and
@AfterClass: when a class declares more than one method for the same annotation, their relative execution order is not guaranteed.
While these methods may appear to run in a fixed order, that order results from a non-obvious internal algorithm that JUnit does not formally guarantee. Relying on such an unguaranteed order makes tests fragile: they may pass initially and then break after a small code change or a JUnit upgrade, with failures that are hard to reproduce and diagnose.
Therefore, to ensure tests execute reliably across all environments, a class should contain at most one method for each lifecycle annotation type.
| Note |
This rule applies strictly within a single class. There is a guaranteed execution order across class inheritance: "before" methods from a superclass are always executed first, and the order is reversed for "after" methods. Splitting lifecycle methods across a class hierarchy is therefore not flagged. |
To fix this issue, merge the bodies of the duplicated lifecycle methods into a single method annotated with that lifecycle annotation. When the original methods must run in a specific order, call them sequentially from the merged method so the order is explicit and guaranteed.
In JUnit 5, the affected lifecycle annotations are @BeforeEach, @AfterEach, @BeforeAll, and
@AfterAll.
class BoardTest {
@BeforeEach
void initBoard() { // Noncompliant; execution order relative to the other @BeforeEach method is not guaranteed
board = new Board();
}
@BeforeEach
void initPlayers() { // Noncompliant
players = List.of(new Player(), new Player());
}
}
class BoardTest {
@BeforeEach
void setUp() {
board = new Board();
players = List.of(new Player(), new Player());
}
}
The order is equally unguaranteed for the once-per-class static methods:
class DatabaseTest {
@AfterAll
static void closeConnection() { // Noncompliant; execution order relative to the other @AfterAll method is not guaranteed
connection.close();
}
@AfterAll
static void clearCache() { // Noncompliant
cache.clear();
}
}
class DatabaseTest {
@AfterAll
static void tearDown() {
connection.close();
cache.clear();
}
}
In JUnit 4, the affected lifecycle annotations are @Before, @After, @BeforeClass, and
@AfterClass.
public class BoardTest {
@Before
public void initBoard() { // Noncompliant; execution order relative to the other @Before method is not guaranteed
board = new Board();
}
@Before
public void initPlayers() { // Noncompliant
players = Arrays.asList(new Player(), new Player());
}
}
public class BoardTest {
@Before
public void setUp() {
board = new Board();
players = Arrays.asList(new Player(), new Player());
}
}
The order is equally unguaranteed for the once-per-class static methods:
public class DatabaseTest {
@AfterClass
public static void closeConnection() { // Noncompliant; execution order relative to the other @AfterClass method is not guaranteed
connection.close();
}
@AfterClass
public static void clearCache() { // Noncompliant
cache.clear();
}
}
public class DatabaseTest {
@AfterClass
public static void tearDown() {
connection.close();
cache.clear();
}
}