skip to content

Besides Gradle or Maven, how can you execute a JUnit Platform test suite straight from a command line, and when is doing so genuinely useful?

level: middleimportance: nice to knowfreq 24%

answer

  1. junit-platform-console-standalone = shaded fat jar
  2. subcommands: execute / discover / engines
  3. --scan-classpath, --select-class, --include-tag
  4. engines subcommand answers 'is my engine on the classpath?'
  5. diagnosis tool, not a build-tool replacement

basics

~20 s

Use the ConsoleLauncher, shipped as the junit-platform-console-standalone fat jar: java -jar junit-platform-console-standalone.jar execute --class-path <cp> --scan-classpath. It runs the same launcher a build tool uses, so it isolates classpath and engine problems from build configuration.

solid answer

~40 s

The **ConsoleLauncher** is JUnit's own command-line front end to the Platform `Launcher`. It ships as `junit-platform-console-standalone`, a shaded jar containing the platform, Jupiter and Vintage, so it needs nothing but a JVM and your test classpath: ``` java -jar junit-platform-console-standalone.jar execute \ --class-path out/test:libs/* --scan-classpath --include-tag fast ``` Subcommands: `execute` (run), `discover` (show what would run without running it), `engines` (list registered engines). Selection is via `--select-class`, `--select-package`, `--select-method` or `--scan-classpath`, with `--include-tag`/`--exclude-tag` and classname filters on top. It exits non-zero on failures, and `--fail-if-no-tests` covers empty runs. It is a diagnostic and niche-execution tool, not a build-tool replacement: no dependency resolution, no compilation, no incremental builds. Its value is exactly that it removes Gradle or Surefire from the picture when you are trying to work out whose fault the empty test run is.

code

bash · 12 lines
bash
# which engines does this classpath register?
java -jar junit-platform-console-standalone.jar engines

# what would run, without running it
java -jar junit-platform-console-standalone.jar discover \
  --class-path build/classes/java/test --scan-classpath

# run only the fast tags and write CI-readable reports
java -jar junit-platform-console-standalone.jar execute \
  --class-path build/classes/java/test:build/resources/test \
  --scan-classpath --include-tag 'fast & !flaky' \
  --reports-dir build/console-reports --fail-if-no-tests

go deeper

for a junior

Know the tool exists, that the standalone jar runs tests with java -jar, and roughly what --scan-classpath does.

for a middle

Show the execute/discover/engines subcommands and explain selectors versus filters, plus report and exit-code options.

for a senior

Use it as the isolation step in a diagnosis: build tool out of the loop, classpath in, definitive answer about engines and discovery — and articulate why it is not a CI runner.

for a principal

Frame it as evidence that the platform is a public API with several front ends, and use that framing to explain why IDE, CI and console runs can legitimately disagree on configuration.

## What it is Everything that runs JUnit 5 tests — Gradle, Maven Surefire, IntelliJ, Eclipse — is a client of one API: the JUnit Platform `Launcher`. The **ConsoleLauncher** is the client JUnit itself ships, driven from a terminal. Because it goes through the same `Launcher`, discovery, filtering and execution behave identically to a build-tool run; only the front end differs. It comes in two forms. `junit-platform-console` is the plain artifact, useful when you already have the platform on the classpath. `junit-platform-console-standalone` is a shaded fat jar bundling the platform, Jupiter, Vintage and their dependencies — that is the one people actually download, because it turns "run these tests" into a single `java -jar` invocation. ## The command shape Recent versions organise it into subcommands: - **`execute`** — discover and run, print a tree or flat report, exit non-zero if anything failed. - **`discover`** — build the test plan and print it without executing. This answers "what *would* run" without paying for the run, which is exactly the question when a filter looks wrong. - **`engines`** — list the `TestEngine` implementations registered on the given classpath. One command, definitive answer to "is the Jupiter engine actually there?". Selectors decide what to look at: `--select-class`, `--select-method`, `--select-package`, `--select-file`, or `--scan-classpath` to sweep classpath roots. Filters narrow it: `--include-classname` (whose default pattern accepts names ending in `Test`/`Tests` and similar, so an oddly named class needs the pattern widened), `--include-tag` / `--exclude-tag` taking full tag expressions, and `--include-package`. `--config key=value` supplies platform configuration parameters, and `--details` chooses the report format (`tree`, `flat`, `verbose`, `summary`, `none`). `--reports-dir` writes the same XML reports CI already knows how to read, and `--fail-if-no-tests` turns an empty run into a non-zero exit. ## When it earns its place **Diagnosing an empty or wrong test run.** This is the main one. If a Gradle or Maven build reports zero tests, you cannot tell from the outside whether the classpath lacks an engine or the build tool never enabled the platform. Dump the test runtime classpath, hand it to the ConsoleLauncher, and the ambiguity disappears: if it runs the tests, the classpath is fine and the build configuration is wrong; if it does not, `engines` will usually show the missing engine. **Minimal reproductions.** Filing a bug against a library or an engine is far easier with a `java -jar` command than with a whole Gradle project. Same for reproducing a discovery bug on a colleague's machine. **Environments without the build tool.** Running a compiled test suite inside a slim container, on a locked-down machine, in an autograder, or as part of a release-artifact smoke check where you deliberately do not want a build tool, a daemon, or network access to a dependency repository. **Teaching and exploration.** It makes the platform's model visible — selectors in, test plan out — which a build-tool wrapper hides. ## What it is not It does not resolve dependencies, compile sources, manage a source set layout, cache results, or do incremental builds. You assemble the classpath yourself, which for a real project means asking the build tool for it anyway (`./gradlew dependencies`, `mvn dependency:build-classpath`). So it is not an alternative for day-to-day work or CI; treating it as one means hand-maintaining a classpath the build tool already knows. Keep it in the diagnostic drawer next to dependency trees. ## A note on IDEs IDEs are a third `Launcher` client and behave differently from the ConsoleLauncher in one respect worth remembering: they generally see only the classpath and system properties of the run configuration, so build-tool-supplied configuration parameters are absent. When IDE, CI and console runs disagree, comparing the three tells you quickly whether the difference comes from the classpath or from configuration.

  • You run the ConsoleLauncher against your test classes and it reports no tests, while the classes clearly have @Test methods. What do you check next?
    Run the `engines` subcommand against the same classpath to confirm a TestEngine is registered at all, since the standalone jar bundles Jupiter but a plain junit-platform-console setup may not. If engines look right, check the class-name filter: `--include-classname` has a default pattern, so a class named something like OrderServiceSpec is skipped until the pattern is widened. Then confirm the annotations come from org.junit.jupiter.api rather than JUnit 4.
  • Why not use the ConsoleLauncher as the CI test runner instead of Gradle or Maven?
    It has no dependency resolution, no compilation and no build caching, so you would have to construct and maintain the test classpath by hand — duplicating what the build tool already computes, and drifting from it the first time a dependency changes. It also loses incremental execution and the build tool's reporting integration. Its value is diagnostic isolation, which is precisely the opposite of what a routine CI run needs.

saying these in an interview costs you the question

  • Calling it a separate test framework rather than another client of the same Launcher
  • Believing it can resolve dependencies or compile test sources
  • Assuming it discovers any class name, ignoring the default --include-classname pattern
  • Proposing it as the normal CI runner in place of the build tool
  • Forgetting that an empty run exits zero unless --fail-if-no-tests is passed

context