skip to content

What is the difference between invoking `mvn test` (a phase) and `mvn surefire:test` (a goal directly)? When would you use each?

level: seniorimportance: should knowfreq 45%

answer

  1. phase = run chain up to it
  2. goal = just that one goal
  3. direct goal skips compile -> stale classes
  4. unbound goals (dependency:tree) only run directly
  5. CI uses phases

basics

~20 s

mvn test runs the whole lifecycle up to test (validate, compile, test-compile, then tests). mvn surefire:test runs only that one goal, skipping compilation, so it can fail or use stale classes if you haven't built first.

solid answer

~40 s

A phase invocation (`mvn test`) triggers the full chain: Maven executes every phase in order up to `test`, so resources are copied, main and test sources are compiled, then `surefire:test` runs against freshly compiled classes. A direct goal invocation (`mvn surefire:test`) runs only that single goal with none of the preceding phases — no recompile, no resource copy. That is faster for re-running tests when nothing changed, but dangerous if sources changed because it tests stale `target/classes`. Direct goals are also how you call goals that aren't bound to any phase (e.g., `mvn dependency:tree`, `mvn help:effective-pom`, `mvn versions:set`). Rule of thumb: use a phase for a correct, repeatable build; use a direct goal for ad-hoc tooling or fast iteration when you accept the staleness trade-off.

code

bash · 3 lines
bash
mvn test            # compiles main + test, then runs tests (correct)
mvn surefire:test   # ONLY runs tests against existing target/classes (fast, can be stale)
mvn dependency:tree # unbound utility goal; phase invocation can't reach it

go deeper

for a junior

Know mvn test runs more than just tests (it compiles first).

for a middle

Explain that a direct goal skips the preceding phases and can hit stale classes.

for a senior

Decide between phase vs direct goal based on correctness vs speed and recognize unbound utility goals.

for a principal

Set team conventions (CI uses phases; direct goals only for tooling) and educate on the stale-classes failure mode.

## Two ways to invoke Maven The Maven command line accepts both **phases** and **plugin goals**: ```bash mvn test # a phase mvn surefire:test # a goal (plugin:goal) mvn clean package # mixing: clean lifecycle phase + default lifecycle phase ``` ## Phase invocation = run the chain `mvn test` tells Maven: execute the `default` lifecycle from the start **up to and including** `test`. Concretely it runs (for jar packaging): ``` validate -> ... -> process-resources (resources:resources) -> compile (compiler:compile) -> process-test-resources (resources:testResources) -> test-compile (compiler:testCompile) -> test (surefire:test) ``` So your classes and test classes are guaranteed fresh before tests run. This is the **correct, reproducible** path. ## Direct goal invocation = just that goal `mvn surefire:test` runs **only** `surefire:test`. Nothing recompiles. If you edited `src/main/java` but didn't rebuild, Surefire tests the **stale** `target/classes`, producing misleading green/red results. It is faster (no compile) but you own the staleness risk. ## Goals with no phase binding Many utility goals are **not bound** to any phase and can only be run directly: - `mvn dependency:tree` — print the resolved dependency graph. - `mvn help:effective-pom` — show the merged POM. - `mvn versions:set -DnewVersion=2.0.0` — rewrite versions. - `mvn exec:java -Dexec.mainClass=...` — run a main class. These never appear in a lifecycle binding, so you must invoke them by `plugin:goal`. ## Choosing - **CI / release / anything that must be correct:** use phases (`mvn verify`, `mvn install`). - **Fast local test re-run when you KNOW nothing changed:** `mvn surefire:test` saves a compile. - **Tooling / inspection / one-off mutation:** direct goal is the only option. ## Gotcha `mvn surefire:test` skipping compilation is the #1 'my fix didn't take effect' trap. When in doubt, run the phase.

  • Why might `mvn surefire:test` show passing tests for code you just broke?
    It does not recompile; it runs against the previously compiled target/classes, so your new (broken) source was never built.
  • How do you run a goal like dependency:tree that has no phase binding?
    Invoke it directly as plugin:goal, e.g. `mvn dependency:tree`; no phase will trigger it because it isn't bound to the lifecycle.

saying these in an interview costs you the question

  • Claiming `mvn surefire:test` recompiles sources first
  • Saying direct goals always run preceding phases
  • Believing every goal can be reached via some phase

context