Recommended resources
The freely available resources this lesson will direct you to.
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:
compilecompiles application code fromsrc/main/java.testcompiles test code fromsrc/test/javaand runs discovered tests.packagecreates 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 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.