skip to content

Parallel Execution Config

Turning on Jupiter's parallel mode and controlling concurrency. A senior-level question about defaults, execution modes, and the strategy knobs in junit-platform.properties.

on this pageshow

questions

5

In JUnit 5, what do you have to configure to make tests actually run in parallel, and where do those settings live?

level: juniorimportance: must knowfreq 45%

answer

  1. enabled=true is only the switch
  2. mode.default + mode.classes.default
  3. junit-platform.properties on test classpath
  4. @Execution overrides the defaults
  5. PER_CLASS / MethodOrderer stay same_thread

basics

~10 s

Parallel execution is off by default. Set junit.jupiter.execution.parallel.enabled=true, then set the default execution modes: junit.jupiter.execution.parallel.mode.default and junit.jupiter.execution.parallel.mode.classes.default to concurrent. Put them in junit-platform.properties on the test classpath, or pass them as JVM system properties.

solid answer

~40 s

Two steps. First the master switch: `junit.jupiter.execution.parallel.enabled=true`. That only turns the machinery on — every node still inherits an execution mode of `same_thread`, so nothing runs concurrently yet. Second, choose what may run concurrently, either globally through `junit.jupiter.execution.parallel.mode.default` (methods and nested nodes) and `junit.jupiter.execution.parallel.mode.classes.default` (top-level classes), or selectively by annotating classes/methods with `@Execution(ExecutionMode.CONCURRENT)`. These are JUnit Platform configuration parameters. The usual home is a `junit-platform.properties` file at the root of the test classpath (`src/test/resources/junit-platform.properties`); the same keys also work as JVM system properties, and a program driving the Launcher can pass them in the discovery request, which wins over the others. The classic mistake is setting only `enabled=true`, seeing no speed-up, and concluding parallelism is broken.

code

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

go deeper

for a junior

Recall the three parameter names and where the properties file goes, and state clearly that the switch alone does nothing.

for a middle

Explain the difference between the class-level and method-level defaults, name the four combinations, and mention @Execution as the per-target override.

for a senior

Add the precedence of the three configuration sources, the PER_CLASS / MethodOrderer exceptions, and the fact that enabling parallelism changes the correctness contract of the suite.

for a principal

Frame it as a policy decision: what the default should be for the whole organisation's suites, how it is enforced in a shared properties file, and how teams opt out safely.

## What parallel execution means here JUnit 5 is a platform plus an engine (Jupiter). The platform builds a *test tree*: an engine node at the root, then test classes, then nested classes and test methods as leaves. By default the platform walks that tree on a single thread, so tests run one after another in a deterministic order. Parallel execution means the platform is allowed to hand sibling nodes to different worker threads at the same time. It is opt-in for a good reason: a suite written under the assumption of one thread will happily share static fields, system properties, fixed ports, files in a shared directory and rows in one database schema. Turning parallelism on changes the correctness requirements of your tests, so JUnit makes you ask for it explicitly. ## The master switch ``` junit.jupiter.execution.parallel.enabled = true ``` This tells the Jupiter engine to use the parallel (multi-threaded) hierarchical executor instead of the single-threaded one. On its own it changes **nothing observable**: every node still carries the default execution mode `same_thread`, so the executor keeps running everything on one thread. Almost every first encounter with this feature ends with "I enabled it and the build takes exactly as long" — because step two was missed. ## Choosing what may run concurrently Two more configuration parameters set the defaults for the tree: ``` junit.jupiter.execution.parallel.mode.default = concurrent | same_thread junit.jupiter.execution.parallel.mode.classes.default = concurrent | same_thread ``` `mode.default` is the default mode applied to nodes generally — test methods and nested classes. `mode.classes.default` is the default for top-level test classes, i.e. whether two different test classes may be in flight at once. Out of the box both behave as `same_thread`. Because the classes-level setting is derived from the method-level one when you don't state it, the safest habit is to set both explicitly so you get the shape of parallelism you intended rather than a surprising one. The four useful combinations: | classes.default | mode.default | Effect | |---|---|---| | same_thread | same_thread | fully sequential (the default) | | concurrent | same_thread | classes run in parallel; methods inside one class stay sequential and on one thread | | same_thread | concurrent | one class at a time, but its methods run in parallel | | concurrent | concurrent | maximum parallelism | The `concurrent / same_thread` row is the pragmatic starting point for a legacy suite: the unit of concurrency is a whole class, so per-class fixtures (`@BeforeAll`, a shared container, an instance field under the default per-method lifecycle) stay confined to one thread. For per-target control, `@Execution(ExecutionMode.CONCURRENT)` or `@Execution(ExecutionMode.SAME_THREAD)` on a class or method overrides the configured default for that node and everything below it. ## Where the settings live Three sources, in decreasing precedence: 1. Configuration parameters supplied programmatically in a `LauncherDiscoveryRequest` (what tools and the ConsoleLauncher use under the hood). 2. JVM system properties with the same key names. 3. A `junit-platform.properties` file at the **root of the class path** — conventionally `src/test/resources/junit-platform.properties`. The properties file is what you check into version control so every developer and every CI run gets the same behaviour; a system property is the convenient way to flip it for one run. ## Notable exceptions to the default mode A couple of Jupiter features are incompatible with running a class's methods concurrently, so JUnit keeps them on one thread even when the default is `concurrent`: test classes annotated with `@TestInstance(Lifecycle.PER_CLASS)` (all methods share one instance, so instance state is shared state) and classes that declare a `MethodOrderer` (ordering is meaningless if methods overlap). If you genuinely want those concurrent, you must annotate them explicitly with `@Execution(CONCURRENT)` and accept responsibility for the shared state. ## Verifying it is on The cheapest proof is to log the current thread name from a couple of tests. Under parallel execution you will see several distinct `ForkJoinPool-…-worker-N` names instead of one `main`-ish thread. Wall-clock time on a large suite is the other signal, but on a small suite thread-pool warm-up can hide the gain, which is why the thread name is the better check. ## What to expect afterwards Once concurrency is real, test output interleaves, assertion failures may point at tests that are victims rather than culprits, and anything that mutates JVM-global state becomes a race. Enabling parallelism is therefore a two-part job: the configuration above, plus a pass over the suite to decide which tests are safe to overlap.

  • You set junit.jupiter.execution.parallel.enabled=true and the suite takes exactly as long as before. What is wrong?
    Nothing is broken — the switch alone leaves every node at the `same_thread` execution mode. You still have to opt nodes into concurrency, either by setting `junit.jupiter.execution.parallel.mode.default` and `junit.jupiter.execution.parallel.mode.classes.default` to `concurrent`, or by annotating specific classes with `@Execution(ExecutionMode.CONCURRENT)`. Confirm the change by printing thread names.
  • If the same configuration parameter is set in junit-platform.properties and as a JVM system property, which wins?
    The system property. Precedence runs from the most specific to the most general: parameters passed programmatically in the LauncherDiscoveryRequest first, then JVM system properties, then the `junit-platform.properties` file on the classpath. That ordering is what lets you commit a safe default in the file and override it for a single run or a single CI job.

Turning on enabled=true is like unlocking extra checkout lanes at a supermarket: the lanes exist now, but until you tell customers they may use them, everyone still queues at lane one.

saying these in an interview costs you the question

  • Believing enabled=true alone makes tests run in parallel
  • Thinking JUnit detects unsafe tests and serialises them automatically
  • Assuming parallel execution is on by default in JUnit 5
  • Putting junit-platform.properties somewhere that is not on the test classpath and expecting it to be read
  • Confusing mode.default with mode.classes.default, then wondering why classes still run one at a time

context

open as a page

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%

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.

open as a page

JUnit 5 lets you choose a parallel execution strategy of dynamic, fixed, or custom. What does each one mean, and how do you control the resulting degree of parallelism?

level: middleimportance: should knowfreq 32%

basics

~10 s

junit.jupiter.execution.parallel.config.strategy picks how the thread pool is sized. dynamic (the default) uses available processors multiplied by ...config.dynamic.factor (default 1.0). fixed uses the exact number in ...config.fixed.parallelism. custom names your own ParallelExecutionConfigurationStrategy class in ...config.custom.class.

open as a page

You switched a large JUnit 5 suite to concurrent execution and a handful of tests started failing intermittently. How do you diagnose the failures and roll the change out safely?

level: seniorimportance: should knowfreq 34%

basics

~20 s

First prove concurrency is the cause: rerun with the parallel switch off. Then narrow it — set the default execution mode back to same_thread and opt classes in with @Execution(CONCURRENT), or keep concurrent globally and mark suspects @Execution(SAME_THREAD). Fix the real cause: static fields, system properties, fixed ports, shared files or database rows.

open as a page

How do you decide the degree of parallelism for a JUnit 5 test suite, and how do you judge whether running tests concurrently is paying off at all?

level: principalimportance: nice to knowfreq 24%

basics

~20 s

Measure, don't guess. Start with the dynamic strategy at factor 1.0, then sweep the factor or a fixed parallelism and plot wall-clock time and flake rate. CPU-bound suites plateau near the core count; I/O-bound ones keep improving until an external system saturates. Pin a fixed value on CI for reproducibility.

open as a page