Build Tools · Lesson 2
Maven Basics and pom.xml
Maven Project Structure and Conventions
Maven enforces a standard directory structure that reduces configuration. Projects follow conventions like src/main/java for source code, src/test/java for tests. This consistency lets Maven figure out what to do without explicit configuration. Understanding Maven's structure is essential for working effectively with Maven-based projects.
Standard Maven Directory Layout
my-app/
├── pom.xml # Maven configuration
├── src/
│ ├── main/
│ │ ├── java/ # Source code
│ │ │ └── com/example/App.java
│ │ └── resources/ # Configuration files, properties
│ │ └── application.properties
│ └── test/
│ ├── java/ # Test code
│ │ └── com/example/AppTest.java
│ └── resources/ # Test resources
│ └── test.properties
├── target/ # Build output (generated)
│ ├── classes/ # Compiled .class files
│ ├── test-classes/ # Compiled test files
│ └── my-app-1.0.0.jar # Packaged JAR
├── README.md
└── .gitignore
Maven automatically looks for source files in these directories. If you follow conventions, you don't need to configure paths.
Creating a Maven Project
Use Maven Archetype to generate a project template. Archetypes are project templates with standard structure already set up.
# Create a simple Java project
mvn archetype:generate \
-DgroupId=com.example \
-DartifactId=my-app \
-DarchetypeArtifactId=maven-archetype-quickstart \
-DinteractiveMode=false
# Create a web application (WAR)
mvn archetype:generate \
-DgroupId=com.example \
-DartifactId=my-webapp \
-DarchetypeArtifactId=maven-archetype-webapp \
-DinteractiveMode=false
This generates a complete project structure with pom.xml and sample code.
Building and Testing with Maven
Maven's build process follows phases. Each phase executes previous phases first.
// Sample source file: src/main/java/com/example/Calculator.java
package com.example;
public class Calculator {
public int add(int a, int b) {
return a + b;
}
public int subtract(int a, int b) {
return a - b;
}
}
// Sample test file: src/test/java/com/example/CalculatorTest.java
package com.example;
import org.junit.Test;
import static org.junit.Assert.*;
public class CalculatorTest {
@Test
public void testAdd() {
Calculator calc = new Calculator();
assertEquals(5, calc.add(2, 3));
}
@Test
public void testSubtract() {
Calculator calc = new Calculator();
assertEquals(1, calc.subtract(3, 2));
}
}
# Compile source code (target/classes/)
mvn compile
# Compile test code (target/test-classes/)
mvn test-compile
# Run all tests
mvn test
# Run specific test
mvn test -Dtest=CalculatorTest
# Package as JAR
mvn package
# View generated JAR
jar tf target/my-app-1.0.0.jar
# Run packaged JAR
java -jar target/my-app-1.0.0.jar
Plugins and Custom Build Tasks
Maven plugins extend functionality. Configure plugins in pom.xml to add custom build steps. Common plugins: compiler, surefire (tests), assembly (packaging), shade (fat JARs), javadoc.
<build>
<plugins>
<!-- Compiler plugin with Java 11 -->
<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 (runs tests) -->
<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>
<!-- Assembly plugin (create fat JAR with all dependencies) -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>com.example.App</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Generate Javadoc -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.3.1</version>
<executions>
<execution>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
Maven Profiles for Environment-Specific Builds
Use profiles to have different configurations for development, testing, and production environments. Activate profiles via command line or based on environment conditions.
<profiles>
<!-- Development profile -->
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<environment>development</environment>
<database.url>jdbc:mysql://localhost:3306/dev_db</database.url>
<debug>true</debug>
</properties>
</profile>
<!-- Production profile -->
<profile>
<id>prod</id>
<properties>
<environment>production</environment>
<database.url>jdbc:mysql://prod-server:3306/myapp</database.url>
<debug>false</debug>
</properties>
<build>
<plugins>
<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>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
# Build with dev profile (default)
mvn clean package
# Build with prod profile
mvn clean package -Pprod
# List active profiles
mvn help:active-profiles
| Maven Lifecycle Phase | Purpose | Output |
|---|---|---|
| validate | Check project structure | Errors if invalid |
| compile | Compile source code | target/classes/ |
| test | Run tests | target/test-reports/ |
| package | Create JAR/WAR | target/.jar or target/.war |
| install | Copy to local repository | ~/.m2/repository/ |
| deploy | Upload to remote repository | Remote server |
Common Mistake: Checking compiled target/ directory and JAR files into git. These are generated during build and should be in .gitignore. Also, avoid version conflicts by using
<dependencyManagement>in parent POMs. Don't hardcode URLs/passwords in pom.xml — use Maven settings.xml or environment variables.
Best Practices for Maven Structure
- Follow standard directory layout (src/main/java, src/test/java)
- Place resources in src/main/resources and src/test/resources
- Use profiles for environment-specific configuration
- Keep pom.xml clean: extract repeated configurations into parent POM
- Version your dependencies explicitly to avoid surprise updates
- Use
.gitignoreto exclude target/, .classpath, IDE files - Maintain README.md with build and run instructions
- Use meaningful artifact IDs that reflect project purpose
Real-World Example: Spring Boot Project Structure
A Spring Boot microservice project follows Maven conventions: src/main/java contains @SpringBootApplication and @RestController classes; src/main/resources contains application.yml with database URL (substituted from Maven profiles); src/test/java contains JUnit tests with @SpringBootTest. The pom.xml uses parent spring-boot-starter-parent for version management. Maven profiles define dev (H2 in-memory database) and prod (MySQL connection) settings. Running mvn clean package compiles, tests, and creates an executable JAR. Running with -Pprod uses production profile with real database.
Quiz Check