In JUnit 5, what do you have to configure to make tests actually run in parallel, and where do those settings live?
answer
- enabled=true is only the switch
- mode.default + mode.classes.default
- junit-platform.properties on test classpath
- @Execution overrides the defaults
- PER_CLASS / MethodOrderer stay same_thread
basics
~10 sParallel 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 sTwo 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 linesjunit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = same_thread
junit.jupiter.execution.parallel.mode.classes.default = concurrentgo deeper
Recall the three parameter names and where the properties file goes, and state clearly that the switch alone does nothing.
Explain the difference between the class-level and method-level defaults, name the four combinations, and mention @Execution as the per-target override.
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.
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