Why is this an issue?

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.

How to fix it in JUnit 5

In JUnit 5, the affected lifecycle annotations are @BeforeEach, @AfterEach, @BeforeAll, and @AfterAll.

Code examples

Noncompliant code example

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());
  }
}

Compliant solution

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:

Noncompliant code example

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();
  }
}

Compliant solution

class DatabaseTest {

  @AfterAll
  static void tearDown() {
    connection.close();
    cache.clear();
  }
}

How to fix it in JUnit 4

In JUnit 4, the affected lifecycle annotations are @Before, @After, @BeforeClass, and @AfterClass.

Code examples

Noncompliant code example

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());
  }
}

Compliant solution

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:

Noncompliant code example

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();
  }
}

Compliant solution

public class DatabaseTest {

  @AfterClass
  public static void tearDown() {
    connection.close();
    cache.clear();
  }
}

Resources

Documentation