Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

Using Maven Lifecycle Goals to Compile, Test, and Package Java Applications

Welcome back. Last lesson, you added a library dependency and configured the compiler plugin in pom.xml. You also used mvn compile as a quick check. Now you’ll place that command in Maven’s build lifecycle, run a test, and produce a JAR for the small expense-tracker project. These are the same commands you’ll use repeatedly as the backend application grows.


Choose a phase, not a list of tasks

Maven’s default lifecycle is an ordered set of build phases. Three phases matter immediately:

  • compile compiles application code from src/main/java.
  • test compiles test code from src/test/java and runs discovered tests.
  • package creates a distributable artifact, usually a JAR for a basic Java project.

When you request a phase, Maven runs the preceding phases it needs in order. Thus mvn package already includes compilation and testing; you do not have to type all three commands to make one JAR.

Watch the command-line demonstration in Maven Tutorial - Crash Course by Marco Codes. It uses a different project, but pay attention to the commands and the changing contents of target/.

Maven Tutorial - Crash Course

Maven Tutorial - Crash Course by Marco Codes shows what Maven writes to disk as each build command runs.

Watch clean and target to see what mvn clean removes, then compile and test for the compiled classes and test summary. Finish with package, focusing on why that command also runs the earlier build phases. Stop before the discussion of install; you do not need it to create a JAR.

There is a useful vocabulary distinction behind the commands. compile, test, and package in these examples are lifecycle phases. The work within a phase is done by plugin goals: for example, the compiler plugin compiles Java, while the Surefire plugin runs tests. Last lesson’s compiler-plugin declaration configured work Maven performs during the lifecycle; it did not create a second compilation step.

Read Maven’s short lifecycle explanation to confirm the ordering and the rule for choosing a command.

Introduction to the Build Lifecycle - Apache Maven

Apache Maven’s Introduction to the Build Lifecycle explains why requesting one phase can run several build tasks.

In “Build Lifecycle Basics,” read the lifecycle explanation through the package entry under “A Build Lifecycle is Made Up of Phases.” Then, in “Usual Command Line Calls,” read the command guidance. Focus on why you request the last phase you need, rather than issuing every preceding command separately.


Give the test phase something to run

The POM from last lesson has application code but no test dependency or test class yet. You can still invoke mvn test, but that would not demonstrate a test actually passing. For this lesson, add one small test; you’ll study test design in depth later.

Keep the existing Commons Lang dependency. Inside the same <dependencies> element, add JUnit Jupiter for test code:

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.10.2</version>
    <scope>test</scope>
</dependency>

Keep the existing compiler plugin, too. Inside the same <build><plugins> element, add a Surefire version that can run this JUnit test:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>3.2.5</version>
</plugin>

These are example versions for the lesson, not claims about the latest releases. The test dependency scope makes JUnit available to test code without making it an application-code dependency.

Create src/test/java/org/example/expenses/ExpenseLabelCheckTest.java. Its package matches the application class from last lesson:

package org.example.expenses;

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

class ExpenseLabelCheckTest {
    @Test
    void recognizesMissingLabels() {
        assertTrue(ExpenseLabelCheck.isMissing("   "));
        assertFalse(ExpenseLabelCheck.isMissing("Groceries"));
    }
}

This test checks both sides of ExpenseLabelCheck.isMissing: a whitespace-only label is missing, while a useful category label is not. Its Test suffix also lets Maven’s test runner discover it using a standard naming convention.


Run the build and inspect its outputs

Open a terminal in the directory containing pom.xml. If needed, run mvn -version first and confirm Maven is using a JDK compatible with last lesson’s <release>17</release> setting. Then run each command separately so you can observe the differences:

mvn compile
mvn test
mvn package

The official Maven Getting Started Guide shows representative output for the same commands. Read its examples alongside your terminal output; the guide’s project name and plugin versions may differ from yours.

Maven Getting Started Guide – Maven

Apache Maven’s Maven Getting Started Guide connects each command to a concrete output location and shows what a successful test run looks like.

In “How do I compile my application sources?”, read the compile example, especially the destination for .class files. In “How do I compile my test sources and run my unit tests?”, read the test example; compare its test summary with yours. Finally, in “How do I create a JAR and install it in my local repository?”, read the package example. Stop after the JAR’s location; installing it into a repository is a separate outcome.

After mvn compile, look for target/classes/org/example/expenses/ExpenseLabelCheck.class. This is compiled application bytecode, not the original .java file. A successful mvn test should also put compiled test code under target/test-classes and show a Surefire summary with one test run and zero failures. Test reports are written under target/surefire-reports.

Finally, mvn package should produce target/expense-tracker-0.1.0.jar, using the artifactId and version in your POM. It runs the earlier default-lifecycle phases on its way to packaging, even if Maven reports that some classes are already up to date. Check the test summary as well as BUILD SUCCESS: a successful build alone does not tell you whether a test was discovered.

An ordinary Maven JAR contains compiled application classes and applicable resources. It does not automatically bundle your JUnit tests or the Commons Lang dependency, and it is not automatically executable with java -jar. “Packaged” means Maven produced an artifact—not necessarily a standalone application.


Clean builds and common surprises

The target/ directory holds generated files. If you want to discard those outputs and rebuild from scratch, run:

mvn clean package

clean belongs to a separate Maven lifecycle; here Maven runs it first, then runs the default lifecycle through package. This is useful when you want to rule out stale output. For routine edits, you can often use mvn test for quick feedback and mvn package when you need the JAR.

If a command does not give the expected result, use the stage where it failed to narrow the cause. A compilation error in ExpenseLabelCheck.java stops the build before tests run. A failing assertion makes mvn test fail and normally prevents mvn package from producing a fresh JAR. If the output says no tests ran, check that the file is under src/test/java, its name ends in Test, and the JUnit dependency and Surefire plugin are in the correct POM elements. On a first run, Maven may also spend time downloading build plugins and dependencies.


Key takeaways

Run mvn compile to build application classes, mvn test to compile and run tests, and mvn package to run the preceding phases and create a JAR. Look inside target/ and read the test summary to verify what actually happened. Use mvn clean package when you need a build without prior generated output.

Next, you’ll inspect Maven’s dependency tree to diagnose what happens when libraries bring in conflicting versions of the same dependency.

Can't find a good explanation? Sign up and we'll make it for you

Sign up