Introduction
Maven is more than just a build tool—it’s a complete project management framework that enforces a standard structure, manages dependencies, and automates the build lifecycle. In this lecture, we’ll explore how to set up a proper Maven project, understand the pom.xml
file, and configure plugins for building, testing, and reporting.
infoThis lecture assumes you’ve completed the previous lectures on setting up Java and learning Java basics. Make sure Maven is installed before proceeding.
Why Use Maven?
Before diving into the structure, let’s understand what Maven solves:
- Dependency Management: Automatically downloads and manages external libraries
- Standard Project Structure: Everyone follows the same conventions
- Build Automation: Consistent compilation, testing, and packaging
- Plugin Ecosystem: Extends functionality without writing custom scripts
- Multi-Module Projects: Manages complex projects with multiple components
Maven Standard Directory Structure
Maven follows a convention-over-configuration philosophy. Here’s the standard structure:
my-project/
├── pom.xml # Project Object Model (configuration)
├── src/
│ ├── main/
│ │ ├── java/ # Java source code
│ │ │ └── com/example/app/
│ │ │ ├── Main.java
│ │ │ ├── model/
│ │ │ │ └── Note.java
│ │ │ └── service/
│ │ │ └── NoteService.java
│ │ ├── resources/ # Configuration files, properties
│ │ │ ├── application.properties
│ │ │ └── logback.xml
│ │ └── webapp/ # Web resources (for web apps)
│ │ ├── WEB-INF/
│ │ └── index.html
│ └── test/
│ ├── java/ # Test source code
│ │ └── com/example/app/
│ │ ├── NoteTest.java
│ │ └── NoteServiceTest.java
│ └── resources/ # Test resources
│ └── test.properties
├── target/ # Build output (generated)
│ ├── classes/ # Compiled classes
│ ├── test-classes/ # Compiled test classes
│ ├── my-project-1.0.0.jar # Packaged JAR file
│ └── surefire-reports/ # Test reports
└── README.md # Project documentation
errorNever commit the ‘target/’ directory to version control! It contains generated files that should be rebuilt. Add it to your .gitignore file.
Directory Purpose
Directory | Purpose |
---|---|
src/main/java |
Production Java source files |
src/main/resources |
Production configuration files |
src/test/java |
Test source files |
src/test/resources |
Test configuration files |
target |
Compiled output and packages (auto-generated) |
Creating a Maven Project
Method 1: Using Maven Archetype (Recommended)
Maven provides archetypes (project templates) for quick starts:
mvn archetype:generate \
-DgroupId=com.example \
-DartifactId=notes-app \
-DarchetypeArtifactId=maven-archetype-quickstart \
-DarchetypeVersion=1.4 \
-DinteractiveMode=false
Parameters explained:
groupId
: Organization/company identifier (reverse domain)artifactId
: Project namearchetypeArtifactId
: Template to useinteractiveMode=false
: Skip prompts (use provided values)
Method 2: Manual Creation
Create Project Directory
mkdir notes-app
cd notes-app
Create Directory Structure
mkdir -p src/main/java/notes_app
mkdir -p src/main/resources
mkdir -p src/test/java/notes_app
mkdir -p src/test/resources
Create pom.xml
Create a pom.xml
file in the project root (we’ll cover this next).
Understanding pom.xml
The pom.xml
(Project Object Model) is Maven’s configuration file. Let’s build one from scratch, explaining each section.
Basic Project Information
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- Project Coordinates -->
<groupId>com.example</groupId>
<artifactId>notes-app</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<!-- Project Metadata -->
<name>Notes App</name>
<description>A simple note management application</description>
- groupId: Unique identifier for your organization (e.g., com.example, org.apache)
- artifactId: Project name/identifier (e.g., notes-app, commons-lang)
- version: Project version (e.g., 1.0.0, 2.1-SNAPSHOT)
- packaging: Output format (jar, war, pom)
Properties
Properties define reusable values and project settings:
<properties>
<!-- Java version -->
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<!-- Character encoding -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- Dependency versions -->
<junit.version>5.8.2</junit.version>
</properties>
Common properties:
maven.compiler.source
: Source Java versionmaven.compiler.target
: Target bytecode versionproject.build.sourceEncoding
: File encoding (always use UTF-8)
infoDefine version numbers as properties! This makes it easy to update versions in one place.
Dependencies
Dependencies are external libraries your project needs:
<dependencies>
<!-- JUnit 5 for testing -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
Dependency Scopes
Scope | Description | Available In |
---|---|---|
compile |
Default, available everywhere | Main code, tests, runtime |
test |
Only for testing | Test code only |
provided |
Provided by container (e.g., servlet API) | Compile & test, not packaged |
runtime |
Not needed for compilation | Runtime & tests only |
Finding Dependencies
Search for dependencies on Maven Central:
- Search for the library name
- Copy the Maven dependency block
- Paste into your
pom.xml
Example: Adding Apache Commons Lang:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
Build Configuration
The build section configures plugins that perform specific tasks:
<build>
<plugins>
<!-- Compiler Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
<!-- Surefire Plugin (for running tests) -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<!-- JaCoCo Plugin (for code coverage) -->
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.10</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
<execution>
<id>check</id>
<goals>
<goal>check</goal>
</goals>
<configuration>
<rules>
<rule>
<element>BUNDLE</element>
<limits>
<limit>
<counter>LINE</counter>
<value>COVEREDRATIO</value>
<minimum>0.60</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Essential Maven Plugins
1. Maven Compiler Plugin
Compiles Java source code.
Configuration options:
<configuration>
<source>11</source> <!-- Source Java version -->
<target>11</target> <!-- Target bytecode version -->
<encoding>UTF-8</encoding> <!-- Source file encoding -->
<compilerArgs>
<arg>-parameters</arg> <!-- Keep parameter names in bytecode -->
</compilerArgs>
</configuration>
2. Maven Surefire Plugin
Runs unit tests during the build.
Features:
- Automatically finds tests ending in
Test
,Tests
, orTestCase
- Generates reports in
target/surefire-reports/
- Fails the build if tests fail
Configuration example:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<includes>
<include>**/*Test.java</include>
<include>**/*Tests.java</include>
</includes>
</configuration>
</plugin>
3. JaCoCo Plugin
Generates code coverage reports.
What it does:
- Tracks which lines of code are executed during tests
- Generates HTML reports showing coverage
- Can enforce minimum coverage thresholds
Key configuration:
<configuration>
<rules>
<rule>
<element>BUNDLE</element>
<limits>
<limit>
<counter>LINE</counter>
<value>COVEREDRATIO</value>
<minimum>0.60</minimum> <!-- 60% minimum coverage -->
</limit>
</limits>
</rule>
</rules>
</configuration>
View coverage report:
mvn test
open target/site/jacoco/index.html
4. Maven JAR Plugin
Creates JAR files with additional configuration:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<archive>
<manifest>
<mainClass>notes_app.Main</mainClass>
<addClasspath>true</addClasspath>
</manifest>
</archive>
</configuration>
</plugin>
5. Maven Shade Plugin
Creates an “uber JAR” with all dependencies included:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>notes_app.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
Maven Build Lifecycle
Maven has three built-in lifecycles, each with phases:
Default Lifecycle (Build)
Phase | Description |
---|---|
validate |
Validate project structure |
compile |
Compile source code |
test |
Run unit tests |
package |
Create JAR/WAR file |
verify |
Run integration tests |
install |
Install to local repository |
deploy |
Deploy to remote repository |
Common Maven Commands
# Clean (delete target directory)
mvn clean
# Compile source code
mvn compile
# Run tests
mvn test
# Package into JAR
mvn package
# Skip tests while packaging
mvn package -DskipTests
# Install to local repository
mvn install
# Run a specific phase (includes all previous phases)
mvn clean install
# Run the application
mvn exec:java -Dexec.mainClass="notes_app.Main"
Creating the Notes App Project
Let’s create a complete Maven project for a notes application:
Create Project Structure
mkdir -p notes-app/src/main/java/notes_app
mkdir -p notes-app/src/test/java/notes_app
cd notes-app
Create pom.xml
Use the complete pom.xml
example from earlier in this lecture.
Create Java Classes
Create src/main/java/notes_app/Note.java
, NoteService.java
, and Main.java
with the implementation from your project.
Build the Project
mvn clean compile
Run the Application
mvn exec:java -Dexec.mainClass="notes_app.Main"
Package Management
Java Packages
Packages organize classes into namespaces:
package notes_app; // Package declaration (must be first line)
import java.util.List; // Import from Java standard library
import java.time.LocalDateTime;
public class Note {
// Class implementation
}
Package naming conventions:
- Use lowercase letters only
- Reverse domain name:
com.example.project
- Sub-packages for organization:
com.example.project.model
,com.example.project.service
Directory Structure Matches Packages
src/main/java/
└── com/
└── example/
└── project/
├── Main.java (package com.example.project)
├── model/
│ └── Note.java (package com.example.project.model)
└── service/
└── NoteService.java (package com.example.project.service)
Best Practices
1. Use Properties for Versions
<properties>
<junit.version>5.8.2</junit.version>
<jackson.version>2.13.0</jackson.version>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
2. Add .gitignore
# Maven
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
# IDE
.idea/
*.iml
.vscode/
.classpath
.project
.settings/
# OS
.DS_Store
Thumbs.db
3. Use Semantic Versioning
Format: MAJOR.MINOR.PATCH
1.0.0
→ Initial release1.0.1
→ Bug fix1.1.0
→ New features (backwards compatible)2.0.0
→ Breaking changes
4. Document Dependencies
Add comments explaining why dependencies are needed:
<!-- JUnit 5 - Modern testing framework with assertions and annotations -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
5. Keep Dependencies Updated
Check for updates:
mvn versions:display-dependency-updates
Troubleshooting
“Package does not exist”
Problem: Import statements can’t find classes.
Solution:
- Verify directory structure matches package names
- Run
mvn clean compile
- Refresh your IDE project
“Cannot find symbol”
Problem: Classes aren’t compiled or in wrong location.
Solution:
- Ensure files are in
src/main/java/
(notsrc/
) - Package declaration matches directory structure
- Run
mvn clean compile
Dependency Not Found
Problem: Maven can’t download a dependency.
Solution:
- Check spelling in
pom.xml
- Verify version exists on Maven Central
- Delete local repository cache:
rm -rf ~/.m2/repository/com/example/dependency-name
- Run
mvn clean install -U
(force update)
Summary
You’ve learned how to structure and configure professional Java projects using Maven!
Key takeaways:
- Maven enforces a standard structure that all Java developers understand
- pom.xml is the heart of Maven configuration (coordinates, dependencies, plugins)
- Dependencies are external libraries Maven manages automatically
- Plugins extend Maven’s functionality (compilation, testing, packaging)
- Build lifecycle has phases that execute in order (compile → test → package)
- Package structure should match directory structure
What’s Next?
In the next lecture, we’ll cover:
- Unit Testing with JUnit 5 - Writing effective tests
- Test-Driven Development - Writing tests first
- Assertions and Test Lifecycle - JUnit features
- Code Coverage - Measuring test effectiveness with JaCoCo
Quick Reference
# Create project
mvn archetype:generate -DgroupId=com.example -DartifactId=my-app
# Build commands
mvn clean # Delete target/
mvn compile # Compile source
mvn test # Run tests
mvn package # Create JAR
mvn install # Install to local repo
# Run application
mvn exec:java -Dexec.mainClass="com.example.Main"
# Check for updates
mvn versions:display-dependency-updates
infoUnderstanding Maven’s conventions saves countless hours of configuration. Follow the standard structure, and Maven handles the rest!