skip to content

In JUnit 5 parallel execution, what is the difference between the configuration parameters junit.jupiter.execution.parallel.mode.default and junit.jupiter.execution.parallel.mode.classes.default, and how does the @Execution annotation interact with them?

level: middleimportance: must knowfreq 38%

answer

  1. classes.default = between classes
  2. mode.default = methods and nested
  3. four combinations table
  4. @Execution overrides + inherits downward
  5. SAME_THREAD ≠ isolated from other classes

basics

~20 s

mode.classes.default decides whether top-level test classes may run concurrently with each other; mode.default is the default mode for the rest of the tree — methods and nested classes. Both take same_thread or concurrent. @Execution(CONCURRENT|SAME_THREAD) on a class or method overrides the configured default for that node and everything nested inside it.

solid answer

~50 s

They are two defaults for two levels of the test tree. `mode.classes.default` applies to top-level test classes — whether two classes can be in flight simultaneously. `mode.default` applies to the nodes below that: test methods and nested classes. Combining them gives four behaviours; the useful middle ground for an existing suite is `classes.default=concurrent` with `mode.default=same_thread`, so classes overlap but each class's methods stay on one thread, which keeps per-class fixtures and instance state safe. `@Execution(ExecutionMode.CONCURRENT)` or `@Execution(ExecutionMode.SAME_THREAD)` is the per-node override. Put on a class it applies to that class and, being inherited down the tree, to its methods and nested classes unless one of them overrides it again. That makes the practical rollout pattern possible: keep the global default at `same_thread` and annotate the classes you have audited. Two constructs stay on one thread regardless of the default: `@TestInstance(PER_CLASS)` classes and classes with a `MethodOrderer`.

code

properties · 3 lines
properties
junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = concurrent
junit.jupiter.execution.parallel.mode.classes.default = concurrent

go deeper

for a junior

Name both parameters and say which one is about classes and which about methods; knowing @Execution exists is enough.

for a middle

Walk the four combinations, explain inheritance of @Execution down the tree, and recommend the classes-concurrent/methods-same-thread starting point.

for a senior

Contrast the opt-in and opt-out rollout strategies, cover the PER_CLASS and MethodOrderer exceptions, and be precise that SAME_THREAD is not isolation.

for a principal

Discuss which default a whole codebase should ship with, how the annotations are kept from becoming permanent debt, and what determines when a team graduates from class-level to method-level concurrency.

## The tree, and why there are two defaults JUnit's platform executes a tree of nodes: engine → top-level test classes → (nested classes) → test methods. Concurrency can be introduced at any level of that tree, and the two levels have very different risk profiles, which is why Jupiter gives you two knobs instead of one. - `junit.jupiter.execution.parallel.mode.classes.default` — the default execution mode for **top-level test classes**. Setting it to `concurrent` means several test classes may execute at the same time on different threads. - `junit.jupiter.execution.parallel.mode.default` — the default execution mode for the **rest of the nodes**, principally test methods and nested classes. Setting it to `concurrent` means methods inside the same class may overlap. Both accept exactly two values, `same_thread` and `concurrent`, and neither has any effect unless `junit.jupiter.execution.parallel.enabled=true`. Out of the box the suite behaves as if both were `same_thread`. Because the classes-level default is derived from the method-level default when you don't state it, it is worth setting both lines explicitly rather than reasoning about the fallback. ## The four combinations 1. **same_thread / same_thread** — the classic sequential run. Deterministic order, no thread-safety requirements. 2. **classes.default=concurrent, mode.default=same_thread** — classes overlap; within a class, methods run one at a time *and on the same thread*. This is the sweet spot for retrofitting parallelism: everything a class owns (an instance field, a `@BeforeAll` fixture, a `ThreadLocal` set in `@BeforeEach`) is still touched by exactly one thread at a time. Only genuinely global state — system properties, static singletons, a shared database, a fixed port — has to be made safe. 3. **classes.default=same_thread, mode.default=concurrent** — one class at a time, but its methods run together. Useful when you have a small number of very slow classes each containing many independent methods, e.g. a class whose tests each make a slow network call. 4. **concurrent / concurrent** — maximum throughput and maximum exposure: any two test methods anywhere in the suite may overlap. ## The @Execution annotation `@Execution` takes an `ExecutionMode` (`CONCURRENT` or `SAME_THREAD`) and can be placed on a test class, a nested class, or an individual test method. It **overrides the configured default for that node**, and because execution mode is inherited down the tree, the annotated node's descendants inherit it too unless they carry their own `@Execution`. The resolution for any node is therefore: its own `@Execution`, else the nearest enclosing `@Execution`, else the configuration default for that kind of node. That gives two opposite rollout strategies: - **Opt-in**: leave the defaults at `same_thread` and annotate audited classes with `@Execution(CONCURRENT)`. Safe, incremental, but the annotations accumulate and no one ever removes them. - **Opt-out**: set the defaults to `concurrent` and mark the known-unsafe classes `@Execution(SAME_THREAD)`. You get the speed-up immediately and the annotations become a visible to-do list of tests with shared state. Riskier on day one. Note that `@Execution(SAME_THREAD)` on a class means "the methods of this class do not run concurrently with each other"; it does **not** mean the class is isolated from the rest of the suite. Another class can still be running at the same time on another thread. Preventing overlap with *other* tests is a different mechanism (exclusive resources), not an execution mode. ## Exceptions the engine applies for you Even with `mode.default=concurrent`, Jupiter keeps a class's methods on one thread when the class uses `@TestInstance(Lifecycle.PER_CLASS)` — all its methods share one instance, so its fields are shared mutable state — or when it declares a `MethodOrderer`, because an ordering guarantee is meaningless if methods overlap. If you want those concurrent anyway, an explicit `@Execution(CONCURRENT)` on the class wins, and you own the consequences. ## What does *not* change `@BeforeAll` and `@AfterAll` still run once per class, before and after all of its methods, whichever threads those methods use. With the default per-method test instance lifecycle each test still gets a fresh instance, so instance fields are naturally isolated — it is `static` fields, JVM-global settings and external resources that break. And output interleaves: with concurrency on, `System.out` from two tests can land in the same line, which is one reason to prefer assertion messages over printing. ## Interview framing A strong answer names both parameters, says which level of the tree each governs, gives the four-combination table or at least the pragmatic `concurrent / same_thread` recommendation, and describes `@Execution` as the per-node override with inheritance. Mentioning the opt-in versus opt-out rollout strategies signals you have actually done this on a real suite.

  • A class is annotated @Execution(ExecutionMode.SAME_THREAD). Does that guarantee no other test in the suite runs at the same time?
    No. The execution mode only says that this node's children do not run concurrently with each other and stay on one thread. Other top-level classes can still be executing in parallel on other threads. Guaranteeing that nothing else runs concurrently requires an exclusive-resource declaration, which is a separate mechanism from execution mode.
  • Which configuration would you choose first when adding parallelism to a large existing suite, and why?
    `mode.classes.default=concurrent` with `mode.default=same_thread`. It makes the class the unit of concurrency, so anything a class owns — instance fields, @BeforeAll fixtures, ThreadLocals set in @BeforeEach — is still touched by one thread at a time. Only truly global state has to be audited, which is a much smaller surface than every method pair in the suite.

saying these in an interview costs you the question

  • Saying mode.default also controls top-level classes
  • Thinking @Execution(SAME_THREAD) isolates a class from the rest of the suite
  • Believing @Execution on a method affects sibling methods
  • Assuming @BeforeAll runs once per thread under concurrent execution
  • Claiming @TestInstance(PER_CLASS) classes parallelise their methods by default

context