QA How-To
Java for Testers: Maven surefire vs failsafe (2026)
Compare java testers Maven surefire vs failsafe by lifecycle, naming, reports, cleanup, skip flags, CI commands, and correct runnable POM configuration.
18 min read | 2,977 words
TL;DR
Use Surefire for ordinary build tests in the test phase. Use Failsafe when tests need Maven-managed setup and teardown after packaging, bind integration-test and verify, and run mvn verify rather than stopping at integration-test.
Key Takeaways
- Surefire runs during test, while Failsafe runs integration-test and verifies later.
- Failsafe delays failure so post-integration-test cleanup can still execute.
- Always bind both Failsafe goals and invoke integration coverage with mvn verify.
- Use conventional class names to make lifecycle intent visible and avoid double execution.
- Publish surefire-reports and failsafe-reports as separate CI evidence.
- Choose the plugin by lifecycle responsibility, not merely test speed or protocol.
Java testers Maven surefire vs failsafe decisions determine more than which class names Maven discovers. Surefire runs tests in the test phase and fails that phase immediately. Failsafe runs integration tests later, records their result, allows post-integration-test cleanup to execute, and evaluates the result in verify.
Use Surefire for tests that belong to the ordinary build feedback loop. Use Failsafe for tests that require lifecycle-managed setup and teardown, such as starting an application, exercising it, and reliably stopping it. This guide explains the lifecycle, configuration, naming, reports, skip flags, CI commands, and interview reasoning.
TL;DR
| Question | Maven Surefire | Maven Failsafe |
|---|---|---|
| Primary purpose | Unit and fast component tests | Integration tests with managed lifecycle |
| Lifecycle phase | test |
integration-test plus verify |
| Common default names | *Test, *Tests, *TestCase |
IT*, *IT, *ITCase |
| Default reports | target/surefire-reports |
target/failsafe-reports |
| Correct main command | mvn test or later phase |
mvn verify |
| Failure timing | Fails during test |
Records test result, fails during verify |
| Main advantage | Fast conventional feedback | Preserves post-integration-test cleanup |
They share the Surefire test execution platform and many parameters, but they are not interchangeable aliases. Choose based on lifecycle responsibility, then reinforce the decision with naming and module boundaries.
1. Place Each Plugin in the Maven Lifecycle
Maven's default lifecycle progresses through phases such as compile, test, package, pre-integration-test, integration-test, post-integration-test, verify, and install. When you request a phase, Maven runs all earlier phases in order.
Surefire's test goal is bound to the test phase. If a test fails, Maven stops before packaging and integration-test setup. That is correct for fast tests because there is no external test environment to dismantle.
Failsafe separates execution from result verification. Its integration-test goal runs tests and writes a summary without failing the build at that point. Maven can continue to post-integration-test, where an application, container, proxy, or other fixture should be stopped. The failsafe:verify goal then reads the summary and fails the build if needed.
This is the reason behind the plugin's name. It does not mean integration failures are ignored. It means failure is reported at a lifecycle-safe point. If you run only mvn integration-test, you stop before both post-integration-test and verify, so cleanup and final result evaluation may never occur. The correct Failsafe command is mvn verify.
Lifecycle placement is the strongest selection rule. Test duration, browser use, or the word integration in a team label are secondary. Ask whether Maven must guarantee later teardown phases after test failure.
2. Configure Surefire for Fast Build Tests
Maven includes a default Surefire binding for common packaging types, but production projects should pin a plugin version and make important behavior explicit. The following POM fragment uses the stable 3.5.4 line.
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.4</version>
<configuration>
<failIfNoTests>true</failIfNoTests>
<useModulePath>false</useModulePath>
<systemPropertyVariables>
<target.env>local</target.env>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
Set useModulePath only for a known reason. It is shown as false for a conventional classpath-based test project, not as a universal rule for Java modules. A modular application should test its module-path behavior deliberately.
Surefire commonly discovers classes matching Test*, *Test, *Tests, and *TestCase. A class such as PriceCalculatorTest naturally belongs here. JUnit Jupiter, TestNG, and supported JUnit versions are selected through the corresponding provider based on project dependencies.
Keep this phase fast enough to provide build feedback on every relevant change. A repository may include mocked component tests or in-memory database tests in Surefire if they need no external lifecycle. The plugin name does not require tests to be microscopic. What matters is whether they are safe and repeatable inside the standard test phase.
3. Configure Failsafe With Both Required Goals
Failsafe is not active merely because the dependency exists. Bind both goals in one execution:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.5.4</version>
<executions>
<execution>
<id>integration-tests</id>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
<configuration>
<failIfNoTests>true</failIfNoTests>
<systemPropertyVariables>
<service.url>${service.url}</service.url>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
Top-level Maven properties should provide a safe default or force a clear configuration decision. Secrets should come from the environment or secret store, not from committed POM values.
Failsafe commonly discovers IT*, *IT, and *ITCase. OrderApiIT therefore runs later than PriceCalculatorTest when both plugins use defaults. Naming is an operational control. It makes the intended lifecycle visible before anyone reads annotations or POM filters.
Run mvn verify. If an integration test fails, Failsafe writes its result during integration-test, Maven proceeds through post-integration-test, and verify fails the build. Reports appear under target/failsafe-reports, with a summary XML file used by the verify goal. Do not delete or move that summary between the two goals.
4. Compare Java Testers Maven Surefire vs Failsafe by Test Type
Classify tests by dependencies and lifecycle needs, not by a rigid testing pyramid label.
| Example test | Suggested plugin | Reason |
|---|---|---|
| Pure pricing function with JUnit | Surefire | No external setup, immediate feedback |
| REST controller with an in-memory stub | Usually Surefire | Self-contained inside test process |
| Application started before tests and stopped after | Failsafe | Needs setup and guaranteed teardown phases |
| Database migration against a disposable service | Failsafe | External resource lifecycle and cleanup |
| Selenium test against an already managed shared grid | Depends on module policy | Browser alone does not decide lifecycle |
| Packaged JAR black-box verification | Failsafe | Requires package before execution |
A browser test can run through Surefire, and an API test can run through Failsafe. The protocol is not the deciding factor. A UI suite in a dedicated test repository may intentionally use Surefire because CI owns the remote environment and test is the repository's only job. In a Maven application build that starts its own server, Failsafe is a better lifecycle match.
Avoid pretending all slow tests are integration tests. Tags or profiles can split fast and slow subsets within one plugin. Conversely, a short test may still need Failsafe because it validates the packaged artifact after setup.
For test-framework organization beyond the plugins, see Java automation framework architecture. Module ownership, test data, and environment contracts often clarify a plugin decision that class annotations alone cannot.
5. Understand Failure and Cleanup Semantics
Suppose pre-integration-test starts a local application, integration-test calls it, and post-integration-test stops it. A failing Surefire-style execution bound directly to integration-test can halt Maven immediately and leave the process alive. Failsafe avoids that by postponing the build failure until verify.
This does not make teardown infallible. The plugin or script bound to post-integration-test must itself be correct and idempotent. Abrupt agent termination, JVM crashes, or machine loss can still bypass ordinary lifecycle cleanup. CI infrastructure should have additional resource timeouts and garbage collection for containers, namespaces, accounts, or cloud fixtures.
Also distinguish test fixture cleanup from Maven environment cleanup. JUnit @AfterEach resets test-owned state around a test. Maven post-integration-test stops suite-level infrastructure. Both are needed when responsibility exists at both levels.
Failsafe result handling depends on reaching verify. A pipeline that invokes integration-test and treats exit code zero as success can report a false green even when tests failed. This is a classic interview scenario and a real operational defect. Standardize mvn verify in scripts and documentation.
When teardown fails as well as tests, preserve both signals. Do not let cleanup code overwrite the original report or remove logs. Keep setup, test, teardown, and verification artifacts under the build directory with distinct names.
Setup failure needs an equally clear policy. If pre-integration-test cannot start the application, Maven should fail and any infrastructure created before the failure should have an out-of-band cleanup path. Failsafe cannot run tests against an environment that never became ready. Health checks belong in setup, with bounded timeouts and diagnostics captured before teardown.
Resource identity should be unique per build or shard. A fixed container name or database schema can make parallel jobs delete each other's fixtures, even when lifecycle phases are correct. Generate a run ID, pass it consistently to setup, tests, and teardown, and let infrastructure apply expiry labels. Maven orders phases within a build, but it does not coordinate unrelated builds sharing an external account.
6. Control Discovery, Tags, and One-Off Runs
Default class naming is the simplest separation. Explicit include patterns are useful when a legacy repository cannot rename files, but broad patterns can make both plugins discover the same class.
<configuration>
<includes>
<include>**/*ContractIT.java</include>
</includes>
<groups>contract</groups>
</configuration>
Provider semantics matter. With JUnit 5, Surefire and Failsafe groups values select JUnit tags. With TestNG, they select TestNG groups. Keep framework-specific details visible in build documentation.
For an isolated Surefire investigation, use the plugin's test user property:
mvn test -Dtest=PriceCalculatorTest
For Failsafe, use it.test and still request verify:
mvn verify -Dit.test=OrderApiIT
Do not turn an ad hoc selector into CI's permanent suite definition. Stable membership should be expressed by naming, tags, suite XML, or repository structure. Command-line selection is ideal for reproducing one failure.
Selection mechanisms combine. A class must survive includes, excludes, tag filters, engine rules, and provider discovery. If no tests run, inspect the effective POM and plugin debug output rather than adding random includes. Enable failIfNoTests where an empty suite is invalid.
7. Use Skip Flags Without Hiding Required Evidence
Maven's test skip options are often confused. -DskipTests skips execution while still compiling tests for the standard plugin behavior. -Dmaven.test.skip=true is broader and is honored by the compiler and test plugins, so test compilation is skipped too. Failsafe also exposes skipITs for integration-test execution.
| Command/property | Typical effect | Risk |
|---|---|---|
-DskipTests |
Skip test execution, compile test sources | Can skip both Surefire and Failsafe executions |
-Dmaven.test.skip=true |
Skip compiling and running tests | May hide broken test source |
-DskipITs |
Skip Failsafe integration tests | Useful only when intentionally configured |
| Profile-specific skip property | Project-defined behavior | Must be documented and validated |
Do not use skip flags to make an unreliable pipeline pass. A packaging-only job may intentionally skip execution, but a release gate should show which evidence ran elsewhere. Separate job responsibilities clearly.
If unit tests should run while integration tests are skipped, configure and use the integration-specific mechanism. Test the exact command after plugin upgrades because inherited POMs can map properties differently. Print test summaries so the pipeline makes omission visible.
The safest default is to run both when mvn verify is requested. Opting out should require an explicit, visible decision. A profile named quick-build is clearer than a hidden environment variable that silently disables integration coverage.
8. Read Reports and Diagnose Forked Test Failures
Surefire writes plain text and XML reports to target/surefire-reports by default. Failsafe writes corresponding reports to target/failsafe-reports and maintains failsafe-summary.xml. CI test publishers should ingest both directories when both plugins run.
Forked JVMs isolate tests from Maven's own process. System properties configured under systemPropertyVariables are passed to test JVMs. Environment variables come from the process environment. Maven properties are not automatically Java system properties unless the plugin maps them or command-line -D creates the expected user property.
When tests pass locally and fail in CI, record Java version, resolved target, working directory, relevant nonsecret configuration, fork count, and test IDs. Do not assume Surefire and Failsafe use the same configuration merely because versions match. Compare their final plugin blocks in help:effective-pom.
If the forked JVM terminates without saying goodbye, inspect crash files, native process exits, System.exit calls, memory limits, and agent termination. Retrying the entire suite is not a diagnosis. The Log4j2 test framework logging guide shows how to add per-test context without losing runner reports.
Keep report directories separate. Merging files into one folder can create name collisions for classes that appear in different phases, and it obscures whether a failure occurred before or after packaging.
9. Build a Clear CI Strategy
For a single application module, a strong default is mvn --batch-mode verify. It compiles, runs Surefire tests, packages the application, runs Failsafe integration tests, performs teardown, and verifies results. CI then publishes both report directories even when the Maven step fails.
Large repositories may split fast and integration jobs. If so, keep lifecycle correctness within each job. The integration job still calls verify, even if a profile or module selection skips unrelated fast tests. Share immutable build artifacts rather than rebuilding different commits.
./mvnw --batch-mode --no-transfer-progress verify -Pci
Archive logs and reports in an if: always() or equivalent post-step so failures retain evidence. Do not use testFailureIgnore to keep artifact upload alive. CI systems have post-step mechanisms for that purpose, while ignoring failures can corrupt the job result.
Use Maven profiles for stable suite policy, not to rename lifecycle phases. The Maven profiles for suites guide shows how a contract profile can feed Failsafe groups while preserving verify semantics.
Record the count of Surefire and Failsafe tests separately. A total of 120 tests says less than 100 fast tests plus 20 integration tests. Separate timing also tells the team where feedback cost is growing.
10. Migrate Java Testers Maven Surefire vs Failsafe Suites Safely
Begin by inventorying tests according to resources, packaging needs, speed, and cleanup responsibility. Rename clear integration classes to *IT, add Failsafe with both goals, and keep Surefire pinned. Move only a small slice first, then compare test counts before and after.
Run mvn test and confirm that integration classes do not execute. Run mvn verify and confirm that each class executes exactly once in the intended report directory. Force an integration failure and prove that post-integration-test cleanup still occurs and the final command exits nonzero.
Avoid configuring Surefire to exclude all *IT classes if its defaults already exclude them. Redundant patterns can be documentation, but they also become drift points. Prefer conventional names and minimal explicit configuration.
If a test module contains only remote end-to-end checks and no application package, decide whether Maven-managed teardown still matters. Failsafe may remain valuable for lifecycle and reporting semantics, but it is not mandatory merely because the tests are end to end. Document the choice so a future maintainer does not move classes based only on names.
After migration, remove obsolete runners and scripts. Two ways to launch the same suite often diverge in properties and reports. Make the Maven Wrapper command canonical, protect it with a smoke job, and update onboarding documentation.
Check code coverage aggregation if the build uses JaCoCo or another agent. Surefire and Failsafe may need the same agent configuration while writing execution data that is merged intentionally. A migration that moves tests but drops their coverage data can appear to reduce quality even when execution is correct. Verify report generation after both phases rather than copying an old argLine blindly.
Also inspect quarantine and retry settings. Moving a class between plugins can change inherited rerun limits or tag exclusions. Record the pre-migration behavior, then decide whether it belongs in the destination phase. The goal is not byte-for-byte configuration parity, but an explicit and reviewed policy for failure handling.
Interview Questions and Answers
Q: What is the main difference between Surefire and Failsafe?
Surefire runs tests in Maven's test phase and fails immediately on test failure. Failsafe runs integration tests in integration-test, allows post-integration-test to execute, and fails later in verify. The distinction is lifecycle and cleanup semantics, not merely class names.
Q: Why must Failsafe be invoked with mvn verify?
verify includes the integration-test, post-integration-test, and final result-verification phases. Invoking only integration-test can skip cleanup and can omit the goal that turns recorded failures into a failing build.
Q: What are the default naming conventions?
Surefire commonly discovers Test*, *Test, *Tests, and *TestCase. Failsafe commonly discovers IT*, *IT, and *ITCase. Conventional names make lifecycle intent visible and reduce explicit includes.
Q: Can I run Selenium tests with Surefire?
Yes. The browser API does not dictate the plugin. I choose based on when the test should run and whether Maven must manage setup and teardown after packaging.
Q: Why does Failsafe not fail during integration-test?
Immediate failure could prevent post-integration-test cleanup. Failsafe records the result, lets the lifecycle continue through teardown, and evaluates the summary during verify.
Q: What is the difference between skipTests and maven.test.skip?
skipTests normally skips execution but still compiles test sources. maven.test.skip also skips test compilation in the plugins that honor it. I use the narrowest explicit option and keep omissions visible in CI.
Q: Where are the reports?
Surefire defaults to target/surefire-reports. Failsafe defaults to target/failsafe-reports and also writes a summary used by its verify goal. CI should publish both when both plugins are configured.
Q: How would you prove a migration is correct?
I would compare counts, confirm each class executes once, inspect separate report folders, and intentionally fail an integration test. The intentional failure must still allow teardown and must make mvn verify exit nonzero.
Common Mistakes
- Choosing Failsafe only because a test is slow or uses a browser.
- Binding only
failsafe:integration-testand forgettingfailsafe:verify. - Running
mvn integration-testas the final CI command. - Letting Surefire and Failsafe discover the same classes.
- Using
testFailureIgnoreso CI can upload artifacts, then losing failure status. - Publishing only Surefire reports even though Failsafe also ran.
- Using broad skip flags without showing which coverage moved elsewhere.
- Deleting the Failsafe summary before the verify goal reads it.
- Relying on tags while class names imply the opposite lifecycle.
- Moving tests without comparing counts and forced-failure behavior.
Conclusion
The Java testers Maven surefire vs failsafe choice is a lifecycle design decision. Surefire belongs in the ordinary test feedback loop. Failsafe belongs where integration execution must be followed by teardown before Maven evaluates the result.
Pin both plugins, use conventional names, bind both Failsafe goals, and make mvn verify the canonical integration command. Then force one controlled failure to prove that cleanup runs and CI receives the correct nonzero result.
Interview Questions and Answers
Explain Surefire versus Failsafe in one answer.
Surefire executes fast or ordinary tests in Maven's test phase and stops the build on failure. Failsafe separates integration execution from verification so post-integration-test cleanup still runs. I choose based on lifecycle responsibility and run Failsafe through mvn verify.
Why are there two Failsafe goals?
The integration-test goal runs tests and records a summary. The verify goal reads that summary and fails the build if necessary. Separating them lets Maven execute post-integration-test teardown between execution and failure reporting.
Can a fast test belong in Failsafe?
Yes, if it verifies a packaged artifact or depends on lifecycle-managed setup. Duration is not the defining rule. A very short black-box check may still be an integration-phase test.
Can an API test belong in Surefire?
Yes, if it is self-contained and belongs in the normal test feedback loop. The transport does not determine the plugin. External lifecycle and packaging needs matter more.
How do you avoid a test running in both plugins?
I use conventional *Test and *IT names, minimal explicit includes, and separate report-count checks. After configuration changes, I run verify and confirm each class appears exactly once in the intended directory.
What happens if CI runs only mvn integration-test?
It may skip post-integration-test and verify. Resources can remain running, and recorded failures might not produce the expected final build status. The correct terminal command is mvn verify.
How do skipTests and maven.test.skip differ?
skipTests usually skips execution but still compiles test sources. maven.test.skip also prevents test compilation in plugins that honor it. I use a narrower integration-specific option when only Failsafe should be omitted.
How would you validate a Surefire-to-Failsafe migration?
I compare counts, confirm report locations, and prove every class runs once. Then I force an integration failure and verify that teardown still occurs and mvn verify exits nonzero.
Frequently Asked Questions
What is the difference between Maven Surefire and Failsafe?
Surefire executes in the test phase and fails there. Failsafe runs integration tests, allows post-integration-test teardown, and evaluates recorded results in verify.
Why should Failsafe tests run with mvn verify?
Verify includes the execution, teardown, and result-checking phases. Calling only integration-test can omit cleanup and can finish without the goal that turns a recorded test failure into a failing build.
What class names does Surefire discover by default?
Common defaults include Test*, *Test, *Tests, and *TestCase. Conventional naming reduces custom includes and makes the intended phase easy to see.
What class names does Failsafe discover by default?
Common defaults include IT*, *IT, and *ITCase. A class such as OrderApiIT therefore belongs to the later integration lifecycle under default configuration.
Can Selenium tests run with Surefire?
Yes. Browser use does not decide the plugin. Choose according to the repository's lifecycle, packaging, setup, and teardown responsibilities.
Where do Surefire and Failsafe write reports?
Surefire defaults to target/surefire-reports. Failsafe defaults to target/failsafe-reports and also maintains a summary file read by its verify goal.
Does Failsafe ignore integration-test failures?
No. It records them during integration-test and reports them as a build failure during verify. The delay exists so lifecycle cleanup can run first.