skip to content

What does the JUnit 5 @ConfigurationParameter annotation on a suite class do, and how does it relate to a junit-platform.properties file and JVM system properties?

level: middleimportance: nice to knowfreq 26%

answer

  1. @ConfigurationParameter(key=..., value=...) on @Suite; repeatable
  2. Order: launcher params > system properties > junit-platform.properties
  3. Suite = nested Launcher run, so it can carry its own params
  4. Typical keys: parallel.enabled, testinstance.lifecycle.default, timeout.default
  5. Scoped: only affects tests inside that suite run

basics

~20 s

@ConfigurationParameter sets a JUnit Platform configuration parameter (key/value) for that suite's nested run only — for example enabling parallel execution or per-class test instance lifecycle. It is supplied to the launcher, so it takes precedence over system properties and junit-platform.properties.

solid answer

~50 s

JUnit reads behaviour switches — parallel execution, default test instance lifecycle, display-name generator, timeouts — as **configuration parameters**: plain string key/value pairs. They can come from three places, resolved in this order: 1. parameters passed directly to the `Launcher`, 2. JVM system properties, 3. a `junit-platform.properties` file at the classpath root. Because the suite engine launches a *nested* run, `@ConfigurationParameter(key = ..., value = ...)` on a `@Suite` class feeds category 1 for that run. So it wins over the global properties file, and it is scoped: only the tests selected by this suite see it. ```java @Suite @SelectPackages("com.acme.slow") @ConfigurationParameter(key = "junit.jupiter.execution.parallel.enabled", value = "true") @ConfigurationParameter(key = "junit.jupiter.execution.parallel.mode.default", value = "concurrent") class ParallelSlowSuite {} ``` That is the main reason to keep a suite class at all: it is the only place where a group of tests can carry its own execution settings in version-controlled code, without changing them globally.

code

java · 11 lines
java
import org.junit.platform.suite.api.ConfigurationParameter;
import org.junit.platform.suite.api.SelectPackages;
import org.junit.platform.suite.api.Suite;

@Suite
@SelectPackages("com.acme.unit")
@ConfigurationParameter(key = "junit.jupiter.execution.parallel.enabled", value = "true")
@ConfigurationParameter(key = "junit.jupiter.execution.parallel.mode.default", value = "concurrent")
@ConfigurationParameter(key = "junit.jupiter.execution.parallel.config.strategy", value = "dynamic")
class ParallelUnitSuite {
}

go deeper

for a junior

Know that configuration parameters are string key/value switches and that junit-platform.properties is the usual place for them.

for a middle

State the three sources and their precedence, and that a suite can set parameters for its own run.

for a senior

Use it deliberately — scoped parallelism or lifecycle for a subset — and warn that a suite-scoped setting makes behaviour depend on the entry point.

for a principal

Weigh scoped configuration against a single global default: fewer modes are easier to reason about, so justify each divergence and keep it visible in code.

## Configuration parameters, briefly The JUnit Platform is configured by string key/value pairs called *configuration parameters*. Nothing about them is type-safe or annotation-driven; engines simply ask the platform for a key and interpret the value. Jupiter's most-used keys include: | Key | Effect | |---|---| | `junit.jupiter.execution.parallel.enabled` | master switch for parallel execution | | `junit.jupiter.execution.parallel.mode.default` | `same_thread` or `concurrent` | | `junit.jupiter.execution.parallel.config.strategy` | `dynamic`, `fixed`, `custom` | | `junit.jupiter.testinstance.lifecycle.default` | `per_method` or `per_class` | | `junit.jupiter.displayname.generator.default` | fully-qualified generator class | | `junit.jupiter.execution.timeout.default` | default timeout for all tests | | `junit.jupiter.conditions.deactivate` | pattern of conditions to switch off | ## Where values come from Resolution order, highest priority first: 1. **Parameters supplied to the Launcher** via `LauncherDiscoveryRequest`. This is what `@ConfigurationParameter` on a suite produces, and it is also what a ConsoleLauncher `--config` option produces. 2. **JVM system properties** — `-Djunit.jupiter.execution.parallel.enabled=true`. 3. **`junit-platform.properties`** — a plain properties file at the root of the test classpath. This is the usual home for project-wide defaults. Higher-priority sources shadow lower ones per key, not wholesale: setting one key in a suite leaves the other keys from the properties file intact. ## Why a suite is the interesting place to set them Recall how a suite executes: `junit-platform-suite-engine` discovers the `@Suite` class and starts a **nested Launcher run** for the selectors and filters it declares. Because that inner run has its own discovery request, it can also have its own configuration parameters. Practical consequences: - **Scoped parallelism.** Enabling `junit.jupiter.execution.parallel.enabled` globally is a big blast radius on a legacy corpus full of shared static state. A suite that selects only the packages known to be thread-safe, with parallelism switched on there, is a safe increment. - **Scoped lifecycle or display-name conventions.** A suite for a legacy package can run with `per_class` instance lifecycle without forcing that on new tests. - **Reproducibility.** The setting travels with the code and behaves the same when a developer clicks run in the IDE as when the suite runs anywhere else — unlike a system property that lives in one launcher's options. ## Caveats worth stating in an interview - The annotation is **repeatable**: stack as many as you need, one key per annotation. - Values are strings; an invalid value usually surfaces as an engine-level exception or is quietly ignored depending on the key, so typos in the key name fail *silently* — nothing validates that `junit.jupiter.exectuion.parallel.enabled` is misspelled. - Parameters set on a suite apply to the tests **inside that suite run only**. The same classes running in the ordinary test run are unaffected — which is a feature, and also a source of "it behaves differently depending on how I run it" confusion. Say out loud which entry point you mean. - Parallel execution has more to do with correctness than configuration: `@Execution(SAME_THREAD)`, `@ResourceLock`, and shared mutable state decide whether it is safe. Turning the switch on in a suite does not make the tests isolated. - Because the suite run is nested, the suite's own container appears in reports as a parent node, and per-run listeners see a run-within-a-run. Tooling that assumes one flat run occasionally reports odd aggregates. ## A realistic use A team wants their 900 unit tests parallel but their 40 database tests strictly sequential, and does not want to depend on how any particular launcher is invoked. They keep the global default sequential in `junit-platform.properties`, then add one suite class over the unit-test packages that sets the parallel keys. The database tests keep the global default; the fast set gets the speedup; the intent is readable in a file that lives next to the tests.

  • You set a parallel-execution key in junit-platform.properties and also in @ConfigurationParameter on a suite, with different values. Which wins inside the suite?
    The suite annotation. Parameters handed to the Launcher have the highest precedence, then JVM system properties, then the junit-platform.properties file. Shadowing is per key, so any other keys from the properties file still apply inside the suite run.
  • Does enabling parallel execution on a suite make the selected tests safe to run concurrently?
    No. The configuration parameter only permits concurrency; thread safety is a property of the tests. Shared static state, singletons, non-reset system properties or a shared database all still break. You control it per test with @Execution and @ResourceLock, and by making fixtures independent.

saying these in an interview costs you the question

  • Thinking @ConfigurationParameter changes global behaviour for every test run rather than only the suite's nested run
  • Believing junit-platform.properties overrides values supplied to the launcher
  • Assuming a misspelled configuration key produces an error — it is silently ignored
  • Claiming the annotation is single-use per class when it is repeatable
  • Treating parallel.enabled=true as making tests thread-safe

context