Maven version covered: 3.9 Java version covered: Java 21 (Java 17 compatible)
Table of Contents
- 2.1 Version check
- 2.2 General course overview
- 2.3 Topics covered
- 2.4 What is Maven?
- 2.5 Ant vs Maven
- 2.6 Gradle
- 2.7 Demo: Installing Maven
- 2.8 Demo: Creation of the first project
- 2.9 Module 2 Summary
- 3.1 Default Maven structure
- 3.2 The src/main/java directory
- 3.3 The target directory
- 3.4 The pom.xml file
- 3.5 Dependencies in the POM
- 3.6 pom.xml with new dependency
- 3.7 Demo: Adding a dependency
- 3.8 Les goals Maven
- 3.9 Demo: Execution of goals
- 3.10 The directory ~/.m2/repository
- 3.11 Module 3 Summary
- 4.1 Maven dependencies
- 4.2 The elements of a dependency
- 4.3 Versions
- 4.4 Types
- 4.5 Transitive dependencies
- 4.6 Scopes
- 4.7 Demo: Adding dependencies
- 4.8 Module 4 Summary
- 5.1 Les Maven repositories
- 5.2 Le repository local ~/.m2/repository
- 5.3 Remote repositories
- 5.4 The dependency repository
- 5.5 Demo: Adding a SNAPSHOT repository
- 5.6 The plugin repository
- 5.7 Releases and Snapshots
- 5.8 Module 5 Summary
- 6.1 Maven plugins
- 6.2 Goals and phases
- 6.3 The phases of the Maven lifecycle
- 6.4 The Compiler Plugin
- 6.5 Demo: Addition of the Compiler Plugin
- 6.6 The JAR Plugin
- 6.7 Demo: Addition of the JAR Plugin
- 6.8 Le Source Plugin
- 6.9 Demo: Addition of the Source Plugin
- 6.10 The Javadoc Plugin
- 6.11 Demo: Addition of the Javadoc Plugin
- 6.12 Module 6 Summary
- 7.1 IDE integration overview
- 7.2 Dependency management in IntelliJ
- 7.3 Demo: Adding a dependency via IntelliJ
- 7.4 Demo: Dependency search with Search Ahead
- 7.5 Les profiles
- 7.6 Demo: Using profiles in IntelliJ
- 7.7 Demo: Command-line profiles
- 7.8 The dependency hierarchy
- 7.9 Demo: Access the Dependency Hierarchy
- 7.10 Leffective POM
- 7.11 Demo: Access the effective POM
- 7.12 Multi-module projects
- 7.13 Demo: Creation of a multi-module project
- 7.14 Demo: WebServiceModel Module
- 7.15 Demo: WebServiceClient Module
- 7.16 Demo: WebServiceServer Module
- 7.17 Module 7 Summary
- 8.1 What is a BOM?
- 8.2 Le BOM packaging
- 8.3 Demo: BOM Packaging
- 8.4 Demo: BOM Dependency Management
- 8.5 Importing a BOM
- 8.6 Demo: Importing a BOM
- 8.7 Module 8 Summary
1. Course Overview
This course, Maven Fundamentals, is presented by longtime Pluralsight author Bryan Hansen, covering Java and configuration with Maven as well as other tools.
Maven is one of the primary tools used to build and configure Java code. This course covers the fundamentals of developing with Maven. Major topics covered include:
- Dependency management
- Integration with IDEs (IDE integration)
- Lifecycles
- Multi-module projects — new in this update
At the end of this course, you will know everything you need to use Maven comfortably in daily development. It is recommended to have basic knowledge of Java before starting.
2. Introduction to Maven
2.1 Version check
This course was created with Maven version 3.9. Earlier versions should work as well, but all examples were made with Maven 3.9 and should be backwards compatible.
The course has also been updated to use the most recent LTS version of Java, Java 21. Java 17 should also work as no Java 21 specific features are used.
2.2 General overview of the course
The course covers the following topics:
- Introduction to Maven: applicable whether you are new to Maven or have used other build tools like Ant.
- Detailed overview of the pros and cons between build tools like Ant or other scripting tools and what makes Maven different.
- The key concepts of Maven: in particular the principle of convention over configuration. If you follow the Maven methodology, life becomes much simpler.
- Everyday Programming Concepts: How to integrate Maven into a typical workday’s code.
- Basic IDE Integration: How to connect Maven to your IDE and make it work with other tools already in use. Maven can be used standalone or in an IDE like Eclipse, Spring or IntelliJ.
- Multi-module builds: Maven can handle sophisticated build structures, but it can also be very simple to use.
2.3 Topics covered
The topics covered in this course are:
- General introduction to Maven
- The structure: the folder structure and how everything is organized if you follow it
- Dependencies: if you use Maven for only one thing, it’s dependency management and handling transitive dependencies
- Repositories: why use them, what a local repository is and how it connects to the corporate repository
- Plugins: each action in Maven is actually a plugin
- IDE integration: how to connect Maven to your IDE
- The Bill of Materials (BOM) or parent POM
2.4 What is Maven?
Maven is, in its simplest form, a build tool. Like Ant or custom batch files, it is a tool for building source code and producing an artifact (an output of your application).
Key features of Maven:
- Maven always produces a single output called Artifact — think of it as a component, a JAR, or even a ZIP file.
- Dependency Management: This is the number one reason people use Maven.
- Project management tool: Maven manages versions, describes source control location, documentation, developers and other meta information.
- Documentation Generation: Maven can produce Javadocs or site information about your project.
Who owns Maven? Maven is owned by the Apache Software Foundation. It can be downloaded from maven.apache.org. It’s open source and free. Maven sites are themselves built with Maven.
Why use Maven?
- Reproducible builds: builds can be repeated identically
- Transitive dependencies: Maven automatically pulls the necessary dependencies
- Convention over configuration: if you follow their conventions, everything works naturally
- SCM integration: no need to store JARs in source control
- Project management: versioning, documentation, meta-information
2.5 Ant vs Maven
Many think of Ant and Maven as competitors, but they are actually trying to solve two different problems and could be used together.
Ant:
- Originally developed as a replacement for the Make build tool
- Make was not cross-platform compatible
- Ant is built on Java and XML — tools designed to work across platforms
- Make was designed for UNIX environments and was fragile (whitespace, hidden character issues)
- With Ant, you must code everything explicitly: define targets, goals, goal chains
Example of target Ant (clean):
<target name="clean" description="Clean build directory">
<!-- Deletes the build directory -->
<delete dir="${build.dir}" />
</target>
In this Ant example:
- The
cleantarget is clearly defined ${build.dir}is a variable evaluated at runtime- If we want to rename
cleantoclear, we must modify all Ant build files
Maven — Convention over Configuration:
- Maven uses a model based on conventions
- If you follow the Maven naming convention things work automatically
- A simple minimal
pom.xmlis enough for Maven to know how to compile, test and package your code - Maven manages the complete project lifecycle: versions, code structure, generation of application information
Key Differences:
| Criterion | Ant | Maven |
|---|---|---|
| Model | Declarative Scripting | Convention over configuration |
| Learning curve | Shorter | Longer initially |
| Destination | Scripting tool | Management of the complete project lifecycle |
| Verbosity | Very verbose (everything must be defined) | Minimal (the conventions take care of the rest) |
| Reusability | Low | High |
2.6 Gradle
Since this course was updated, it is worth mentioning Gradle, which has gained popularity. Gradle is essentially Maven without the XML.
- Nearly identical functionality to Maven
- Based on YAML — slightly more permissive syntax in terms of declaration
- Very popular, especially in the Android ecosystem
Example of Gradle file comparable to a Maven POM:
plugins {
id 'java'
}
group = 'com.pluralsight'
version = '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.3'
implementation 'org.apache.commons:commons-lang3:3.12.0'
}
Gradle syntax is more error prone for those accustomed to Maven, but may be a valid option to consider.
2.7 Demo: Installing Maven
Steps to install Maven on Windows:
- Open a browser and go to maven.apache.org
- Go to the Download link and go down to the Files section
- Download the appropriate
bin.zipfile for your operating system - Extract this file to a development directory, for example
C:\dev\tools\apache-maven
⚠️ Do not extract to the directory itself — extract to the
apache-mavenfolder
- Configure environment variables:
- Open system Environment Variables
- Create a new system variable:
JAVA_HOME→C:\dev\tools\java - Create a new system variable:
MAVEN_HOME→C:\dev\tools\apache-maven - Edit the
PATHvariable and add: %JAVA_HOME%\bin%MAVEN_HOME%\bin- Move these entries to the top of environment variables
- Verification:
mvn -version
This command should show the version of Maven and the version of Java used.
2.8 Demo: Creation of the first project
Manually creating the project structure:
- Go to the
C:\directory and create aworkspacefolder - In
workspace, create aSimpleProjectfolder - In
SimpleProject, create the following folder structure:
SimpleProject/
└── src/
└── main/
└── java/
- Open IntelliJ Community Edition and create a new project in
C:\dev\workspace
- Name the project
SimpleProject - Language: Java
- Build system: Maven
- Click on Create
- In the project, create the
pom.xmlfile:
<?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>
<groupId>com.yourcompany</groupId>
<artifactId>SimpleProject</artifactId>
<version>1.0</version>
</project>
Note: The
groupIdrepresents the name of your company (eg:com.yourcompany). TheartifactIdmust match the project name exactly, with the same case.
2.9 Module 2 Summary
- Ant is very declarative — it’s a scripting tool. You have to define everything, which results in a short learning curve, but more tedious configuration.
- Maven follows a convention over configuration model: if you follow the naming convention, things work more smoothly.
- Ant is easier to learn but is only intended as a scripting tool.
- Maven is focused on managing the complete project lifecycle: versions, code structure, generation of application information.
- The simple example created shows the power of the convention over configuration model in relation to the scripted nature of Ant.
3. Project structure with Maven
3.1 Default Maven structure
Maven looks by default for a src/main/java directory under our project. It compiles all code into a target directory, referring to the defaults and overloads defined in pom.xml.
HelloWorld structure example:
HelloWorld/
├── src/
│ ├── main/
│ │ └── java/
│ │ └── HelloWorld.java
│ └── test/
│ └── java/
├── target/
└── pom.xml
Important points:
src,targetandpom.xmlare all at the root level of the application- The
SimpleProjectproject follows the same convention - This illustrates the convention over configuration throughout the project
3.2 The src/main/java directory
The src/main/java directory is where all Java code is stored. This is also the start of the package declaration.
Other supported languages:
src/main/groovyfor Groovysrc/main/resourcesfor resources (config files, XML, etc.) — frequently seen in Spring Boot projectssrc/main/scalafor Scala,src/main/kotlinfor Kotlin
These directories help separate different types of source code, and different Maven plugins are used to access them.
Tests:
- The
src/test/javadirectory is for unit test code (unit tests) - Allows test code to be kept separate from production code while still being able to reference the same package structure
- This directory is specifically for automated unit tests, not for integration tests or other types of tests
3.3 The target directory
The target directory is where everything is compiled and packaged. This is also where the tests are run.
Contents of the target directory after running mvn package:
target/
├── classes/
│ └── (classes compilées)
├── maven-archiver/
│ └── (référence le pom.xml pour la structure du package)
├── surefire-reports/
├── test-classes/
│ └── (classes de test compilées)
└── HelloWorld-1.0-SNAPSHOT.jar
The target directory contains:
classes/: compiled code fromsrc/main/javamaven-archiver/: references the structure ofpom.xmlfor packagingsurefire-reports/andtest-classes/: for unit tests- The final artifact (ex:
HelloWorld-1.0-SNAPSHOT.jar)
3.4 The pom.xml file
The pom.xml (Project Object Model) file is the central file of Maven. Here is a basic example for the HelloWorld application:
<?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>
<groupId>com.pluralsight</groupId>
<artifactId>HelloWorld</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
</project>
The four basic parts of a POM:
- Project information (
groupId,artifactId,version,packaging) - Dependencies (section
dependencies) - Build (plugins, configuration)
- Repositories (dependency and plugin repositories)
POM Key Elements:
groupId: often the same as the package — represents the name of the company or application (eg:com.acme,com.pluralsight,com.yourcompany). Used as a web address to refer to your business.artifactId: synonym for the application name. Must match the product module name. Ex:HelloWorld(same case).version: simply the desired version number —1.0,2.0,3.0, or for a maintenance release1.0.1.packaging: the type of packaging (optional, default value:jar)
Convention over configuration: The minimal POM above does not mention package structure or directory structure, as everything is assumed to follow Maven defaults.
3.5 Dependencies in the POM
Dependencies are imported by their naming convention. This is often considered the most confusing part of Maven because one needs to know the artifactId, the groupId and the version.
Benefit: Maven automatically pulls transitive dependencies for us.
To add a dependency, we simply add a dependencies section in the POM with a dependency element containing the three required pieces of information: groupId, artifactId and version.
3.6 pom.xml with new dependency
<?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>
<groupId>com.pluralsight</groupId>
<artifactId>HelloWorld</artifactId>
<version>1.0</version>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
</dependencies>
</project>
Observation: The naming convention is identical between the parent project and its dependencies:
- Our project:
groupId=com.pluralsight,artifactId=HelloWorld - Dependency:
groupId=org.apache.commons,artifactId=commons-lang3
3.7 Demo: Adding a dependency
To add a dependency via IntelliJ:
- Open the
pom.xmlfile - Under the
packagingtag, before the project closing tag, add adependenciessection - In the
dependenciessection, add adependencyelement - The IDE provides context-sensitive help for
groupIdandartifactId
Example:
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
</dependencies>
- Save and click on the Maven icon to refresh the project (resolve dependencies)
- In External Libraries, we can see that the
commons-langJAR has been downloaded
Command line check:
mvn clean
mvn compile
The dependency is automatically available in the classpath — no manual configuration of the classpath is necessary.
3.8 Maven goals
Here are the most common Maven goals:
| Goal | Description |
|---|---|
clean | Remove target directory and generated sources |
compiled | Compile all source code. Also generates stub/skeleton files if using libraries like Lombok or web services. Copies .properties and other resource files to target/classes. |
package | Run compile first, then run all unit tests, then package the code according to the packaging type defined in the POM. |
install | Run package, then copy the JAR/WAR to the local repository (~/.m2). |
deploy | Run install, then deploy to a remote repository (corporate repository). ⚠️ Do not confuse with application deployment on a web server. |
Important:
deploydoes NOT mean deploying the application to a server. This means copying the artifact to the remote enterprise repository.
Chain goals:
mvn clean install
This command deletes the target directory, recompiles everything, runs the tests and installs into the local repository.
3.9 Demo: Goal execution
# Supprimer le répertoire target et les sources générées
mvn clean
# Recompiler le code source et copier les ressources
mvn compile
# Packager le code compilé dans l'artifact désigné (JAR, WAR, etc.)
mvn package
# Installer l'artifact dans le repository local ~/.m2
mvn install
# Chaîner : nettoyer, puis installer
mvn clean install
Output from mvn package: Maven needs to download a few plugins on first run, then compiles the code and packages it. The target directory is recreated each time.
Output of mvn install: The artifact is installed in ~/.m2/repository. The output path is displayed in the console.
3.10 The directory ~/.m2/repository
The directory ~/.m2/repository is Maven’s local repository.
Features:
- Local storage by default: located in the user’s home directory, under the hidden directory
.m2 - Same path on all OS:
~/.m2/repository(works on Windows, Mac, Linux) - Uses the same naming convention:
groupId/artifactId/version/~/.m2/repository/ └── org/ └── apache/ └── commons/ └── commons-lang3/ └── 3.12.0/ ├── commons-lang3-3.12.0.jar ├── commons-lang3-3.12.0.pom └── commons-lang3-3.12.0-sources.jar - Avoid duplication: all applications can reference these JARs from this single location
- Avoid bloating the SCM: no need to store JARs in Git/Bitbucket/SVN
Note: 95% of Maven users leave the repository in the default location. It is recommended to do the same to avoid headaches during Maven updates.
3.11 Module 3 Summary
src/main/java: all source code is taken and compiled from this directorytarget: everything is compiled and packaged in this directory- Both of these are controlled by sections of the POM
- Adding a dependency: a
dependenciessection in the POM is enough - Goals:
clean,compile,package,install,deploy— each generates parts of the application ~/.m2/repository: the local repository where the artifacts are installed and stored- Next module explores dependencies in more depth
4. Working with dependencies
4.1 Maven dependencies
This module covers all major aspects of dependencies and their impact on an application:
- Versions
- Types
- Transitive dependencies and their usefulness
- Dependency scopes
Dependency management is usually the number one reason people start using Maven.
4.2 The elements of a dependency
Dependencies are simply other resources that one wants to use in the application. Maven will pull transitive dependencies based on the declared dependency.
Three elements required for any dependency:
| Element | Description |
|---|---|
groupId | Typically the same as the artifact package structure |
artifactId | The name of the element we want to use |
version | The desired version number |
Example of dependency declaration:
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
</dependency>
</dependencies>
History: This example is chosen intentionally because Apache did not follow standard naming conventions in the past, but has since adapted to industry practices.
4.3 Versions
Versions are the release number of the artifact we want to use.
The only version that deserves special attention: SNAPSHOT
- SNAPSHOT: All internal development should start as SNAPSHOT. You should be aware that you can also use SNAPSHOTs from third-party libraries (e.g. the latest version of Spring).
- SNAPSHOTs allow you to push new code to a repository or development team each time
- The word SNAPSHOT must be in uppercase — it does not work in lowercase
- On each compilation, Maven checks if there is new code to download
Naming example:
myapp-1.0-SNAPSHOT.jar ← Artifact en développement
myapp-1.0.jar ← Release finale
myapp-1.0.1.jar ← Bugfix release
myapp-1.1.jar ← Nouvelle fonctionnalité
myapp-2.0.jar ← Changement majeur
Important rules:
- ⚠️ Never deploy in production with a SNAPSHOT: you cannot reproduce the exact code — the next time you compile, the functionality could be different
- The naming convention for releases is not imposed by Maven (except SNAPSHOT) — it is left to company strategy
- You can name a release
finalorrelease— the important thing is consistency
4.4 Types
Types refer to the type of resource you want to include in the application or the type you package yourself.
Supported packaging types:
| Type | Description |
|---|---|
jar | Most used default — Java archive |
war | Web Application Archive |
ear | Enterprise Application Archive |
rar | Resource Adapter Archive |
by | Plugin Archive |
pom | Project Object Model — special case |
maven-plugin | Maven Plugin |
ejb | Enterprise Java Bean |
Note: JAR, WAR, EAR, RAR and PAR are essentially enhanced ZIP files.
The POM type — special case:
If we declare a dependency of type pom, all the dependencies listed in this POM are downloaded into our application. This is called a dependency POM or bill of materials (BOM). This is a very widespread practice, particularly with Spring Boot.
4.5 Transitive dependencies
Transitive dependencies are the main reason people start using Maven.
How it works:
- If you add a dependency (eg: Hibernate), Maven automatically pulls all the transitive dependencies that Hibernate needs
- In case of conflicts, Maven tries to resolve them by always choosing the closest version
Example with Hibernate Core:
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
<version>6.2.0.Final</version>
</dependency>
By declaring only hibernate-core, Maven automatically pulls all its transitive dependencies, for example:
antlr2.7.7byte-buddyclassmatecommons-lang3dom4j2.1.1hibernate-commons-annotationsjavassist- etc.
Why trust transitive dependencies? The people who created Hibernate know exactly which versions of the libraries are needed for their framework to work. The POM they publish defines these dependencies. Only in case of conflict does Maven try to overload with a newer version.
4.6 Scopes
Scopes allow you to control when and how dependencies are available during the build cycle and in the final artifact.
Six scopes available:
| Scope | Description |
|---|---|
compiled | Default scope (implicit if omitted). All resources are available everywhere in the app. Included in the final artifact. |
provided | Similar to compile — available throughout the build cycle, but not included in the final artifact as it will be provided by the deployment container. Example: Servlet API, XML APIs. |
runtime | Not necessary for compilation, but necessary for execution. Example: dynamically loaded JDBC drivers (Class.forName(), DriverManager). |
test | Available only for compiling and running tests. Not included in the final artifact. Example: JUnit, Mockito. |
system | Similar to provided, but must specify the explicit location of the JAR on the system. Avoid if possible. |
import | Used only with dependencies of type pom. Allows you to import dependencyManagement from another POM. |
Example with multiple scopes:
<dependencies>
<!-- Scope compile (défaut) -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<!-- Scope test -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.9.3</version>
<scope>test</scope>
</dependency>
<!-- Scope provided -->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
<scope>provided</scope>
</dependency>
<!-- Scope runtime -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
<scope>runtime</scope>
</dependency>
</dependencies>
4.7 Demo: Adding dependencies
Added JUnit with test scope:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.9.3</version>
<scope>test</scope>
</dependency>
After reloading in the IDE (IntelliJ Dependency Analyzer), dependencies with scope=test appear distinctly from compile dependencies.
Added Hibernate Core:
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
<version>6.2.0.Final</version>
</dependency>
In IntelliJ’s Dependency Analyzer, we can see Hibernate and all its transitive dependencies automatically downloaded.
Observing conflicts: Maven automatically resolves them by choosing the most recent version, which is generally the desired behavior.
4.8 Summary of Module 4
- Versions: the naming convention is free (1.0, final, release), except for SNAPSHOT which has a special meaning — always in capitals, never in production
- Supported types: JAR (99% of the time), but the POM/BOM type is very powerful for importing a set of dependencies
- Transitive dependencies: Maven automatically pulls all necessary dependencies. In case of conflict, it chooses the most recent version
- Scopes: allow you to control what is compiled, tested and included in the final artifact.
testis the most important scope to master — it allows you to use JUnit without including it in the final JAR
5. Using repositories
5.1 Maven repositories
repositories (or repos) are the locations that Maven consults to download code and other artifacts. This module covers:
- The difference between a dependency repo and a plugin repo
- How to override these repos from their default locations
- How to tell Maven where to look for releases versus snapshots
5.2 The local repository ~/.m2/repository
Maven first consults the local repository. This is where everything is stored. If the code is not found locally, Maven will download it from a remote repository.
Features:
- Stored in home directory:
~/.m2 - Linux/UNIX style paths also work on Windows for a few years
- May be overloaded (not recommended — causes headaches during Maven updates)
- All artifacts are stored according to
groupId/artifactId/version - Avoid storing JARs in SCM tools (Git, SVN, Bitbucket)
5.3 Remote repositories
What is a remote repository? A repository is simply a location accessible via HTTP from which files can be downloaded. It often does not have security, although an internal enterprise repository may have some.
The Maven central repository:
- Defined in the Super POM (included in Maven installation)
- Default location:
repo.maven.apache.org - Contains 95% of everything you might want to download
- ⚠️ Do not modify the Super POM — override via the project POM or a global parent POM
Multiple repositories:
- Multiple repositories are allowed and often encouraged
- It is common to have to download resources from more than one repository
- Corporate Repository: to host the company’s internal libraries
Popular enterprise repository tools:
- Nexus (uses repo.maven.apache.org as backend)
- Artifactory (used by Spring on repo.spring.io)
Anecdote: When Maven became very popular and no one was using enterprise repositories yet, developers caused a denial of service attack on the main site repo.maven.apache.org.
5.4 The dependency repository
A dependency repository is exactly what its name suggests: the place where dependencies are downloaded.
Features:
- May contain releases, snapshots, or both
- It is not uncommon to have separate repositories for releases and snapshots
Example of configuring a dependency repository in the POM:
<repositories>
<repository>
<id>spring-snapshot</id>
<name>Spring Maven SNAPSHOT Repository</name>
<url>https://repo.spring.io/libs-snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
Observations:
- Each repository has a unique
id - The
nameis optional but descriptive - Snapshot repository URLs generally contain the word “snapshot” (ex:
libs-snapshot) - Snapshots and releases can be enabled/disabled independently
- To have both snapshots and releases in the same repository: set both to
true - For multiple repositories: add additional
repositoryelements inrepositories
5.5 Demo: Adding a SNAPSHOT repository
To add the Spring Core SNAPSHOT repository:
- Navigate to
https://repo.spring.io/libs-snapshot(redirects to JFrog Artifactory — Spring uses Artifactory to host its repos) - Navigate to
org/springframework/spring-core - Select the latest SNAPSHOT version (ex:
6.1.0-SNAPSHOT)
Observation on snapshots:
In the repository, instead of a file named 6.1.0-SNAPSHOT.jar, there are files with a date/time suffix (eg: spring-core-6.1.0-20230901.123456-1.jar). This is how Maven knows which version of the snapshot to download — this is what ensures that you always have the latest snapshotted code.
Complete configuration in POM:
<repositories>
<repository>
<id>spring-snapshot</id>
<name>Spring Maven SNAPSHOT Repository</name>
<url>https://repo.spring.io/libs-snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>6.1.0-SNAPSHOT</version>
</dependency>
</dependencies>
⚠️ Since Maven 3.8.1, HTTP is not allowed — always use HTTPS. If you use HTTP, you will get an error. The correction is simple: replace
http://withhttps://.
5.6 The repository plugin
Plugin repositories are almost identical to dependency repositories. The only difference is that they deal with plugins.
Main difference:
- We search for plugins in a separate repository for reasons of code cleanliness
- XML tag is
pluginRepositoriesinstead ofrepositories
Repository plugin example:
<pluginRepositories>
<pluginRepository>
<id>corp-internal-plugins</id>
<name>Corporate Internal Plugin Repository</name>
<url>https://repo.mycorporation.com/plugins</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
<releases>
<enabled>true</enabled>
</releases>
</pluginRepository>
</pluginRepositories>
The same rules apply: you can have snapshots and releases, and you can have several plugin repositories.
5.7 Releases and Snapshots
Why not publish everything to the central repository?
The release process is complex, similar to deploying a mobile application on a store. You need to make sure everything is finalized before pushing to the central repository.
Items like snapshots, milestones, release candidates are best hosted on a clean enterprise repository, because:
- It’s a bit more work to publish them to the central repository
- They change often before reaching a final stable version
Example: Spring does not want to put all its snapshots on repo.maven.org. They host their snapshots on their own corporate repository.
<!-- Repository de snapshots uniquement -->
<repository>
<id>spring-snapshot</id>
<url>https://repo.spring.io/libs-snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
<releases>
<enabled>false</enabled>
</releases>
</repository>
5.8 Module 5 Summary
- dependency repositories and plugin repositories can be the same or separate repositories
- Projects often do not publish their SNAPSHOTs to the central repo due to complexity and frequent changes before final release
- Plugins are usually in the same repository as dependencies, but can be separated — this is often the case for enterprise repositories and custom plugins
- Companies should use their own enterprise repository to alleviate the load on the central repository and host their internal libraries
6. Using plugins
6.1 Maven plugins
Plugins are what Maven uses to build and package our application, as well as anything beyond just downloading and storing artifacts.
This module covers:
- Goals and their link with the phases
- The Compile Plugin
- The JAR Plugin and the Plugin Sources
- The Javadoc Plugin
6.2 Goals and phases
Goals:
Throughout the training, when we execute compile, clean, package, these are goals. Goals are actually plugins configured in the Maven installation.
- The goals
clean,compile,test,package,install,deployare all base goals defined in the Super POM - The Super POM is installed with Maven and defines these goals, which are then added to your effective POM
Difference between goal and phase:
- Goal: what we type on the command line (eg:
mvn clean) - Phase: to which stage of the Maven lifecycle the goal is attached (e.g. the
cleanphase)
Example with the Clean Plugin:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>3.3.1</version>
<executions>
<execution>
<id>auto-clean</id>
<phase>initialize</phase> <!-- Peut être changé de 'clean' à 'initialize' -->
<goals>
<goal>clean</goal>
</goals>
</execution>
</executions>
</plugin>
Plugins can be reconfigured so that they run during different phases. For example, force a clean at each compile by linking it to the initialize phase.
6.3 The phases of the Maven lifecycle
The phases execute in the following order:
| # | Phase | Description |
|---|---|---|
| 1 | validate | Validates that the project is correct and that all necessary information is available (plugins, downloaded artifacts, structure in place, permissions) |
| 2 | compiled | Compile the main source code (src/main/java) |
| 3 | test | Compile the test code (src/test/java) and run the unit tests |
| 4 | package | Package the compiled code according to the defined type (JAR, WAR, etc.). Many link the generation of resources or Javadocs to this phase |
| 5 | integration-test | Deploys and runs integration tests (newer phase of Maven, less used) |
| 6 | verify | Runs checks on the project to ensure it meets quality criteria before installing into the local repository |
| 7 | install | Install the artifact in the local repository |
| 8 | deploy | Deploy the artifact to the enterprise remote repository |
Note: Do not confuse
verifyandvalidate—verifyruns beforeinstallanddeployto check the quality of the project.
6.4 Compiler Plugin
The Compiler Plugin is the plugin used to compile the source code.
Features:
- Used for source code AND test code (in different phases)
- Invokes
javacbut does a lot more — including setting up the classpath with dependencies and their scopes - The most often overloaded plugin because it uses an old version of JVM by default
- Current configuration includes Java version specification
Configuration structure:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<fork>true</fork>
<meminitial>128m</meminitial>
<maxmem>512m</maxmem>
<release>17</release>
</configuration>
</plugin>
Configuration options:
fork: spin the compiler in its own threadmeminitialandmaxmem: heap memory (useful for projects with a lot of code)release: target Java version (replaces the oldsourceandtargetoptions)
6.5 Demo: Added Compiler Plugin
To add the Compiler Plugin to the POM:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<release>17</release>
</configuration>
</plugin>
</plugins>
</build>
Location in POM:
- After the closing
</repositories>tag but before the closing</project>tag - The
<build>tag contains a<plugins>tag (not<pluginManagement>)
Note: After saving, IntelliJ may take a moment to resolve dependencies and rebuild caches — this is normal.
6.6 The JAR Plugin
The JAR Plugin is used to package code into a JAR file.
Features:
- Result: a JAR file
- Linked by default to the package phase
- Allows you to configure inclusions/exclusions (eg: include XML, exclude generated code)
- Can generate a manifest automatically
Configuration structure:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifest>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
</manifest>
</archive>
<includes>
<include>**/*.xml</include>
</includes>
</configuration>
</plugin>
6.7 Demo: Adding the JAR Plugin
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<includes>
<include>**/*.xml</include>
</includes>
</configuration>
</plugin>
Important points:
- The
groupIdis the same as other Maven plugins:org.apache.maven.plugins - The
includeuses the Ant pattern matching syntax (ex:**/*.xmlincludes all XML files) - To test from the command line:
mvn package
Phases executed during mvn package:
Maven displays the phases in execution order: resources → compile → testResources → testCompile → test → package (jar)
6.8 The Source Plugin
The Source Plugin packages the source code to distribute it for:
- Contextual help in the IDE
- More complete Javadocs
Features:
- Linked by default to the package phase
- Often overloaded towards a later phase (eg:
installorverify) so as not to slow down frequent builds - Creates a sources JAR (ex:
MonApp-1.0-SNAPSHOT-sources.jar)
6.9 Demo: Adding the Source Plugin
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
<phase>install</phase>
</execution>
</executions>
</plugin>
Difference with the Compiler Plugin: Instead of a <configuration> tag, we use <executions> to link the plugin to a specific goal and phase.
Command line test:
mvn install
cd target
# On voit maintenant deux JARs :
# SimpleProject-1.0-SNAPSHOT.jar
# SimpleProject-1.0-SNAPSHOT-sources.jar
6.10 The Javadoc Plugin
The Javadoc Plugin is almost identical to the Source Plugin — it packages Javadocs in a JAR.
Features:
- Linked by default to the package phase
- Often overloaded to
installorverifyso as not to slow down frequent builds - Can be customized (company logo, colors, styles)
- Also generate an
apidocsfolder intarget
6.11 Demo: Added Javadoc Plugin
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
<phase>install</phase>
</execution>
</executions>
</plugin>
Command line test:
mvn install
After execution, in the target/apidocs directory, the index.html file contains the generated Javadocs. Maven displays warnings if code doesn’t have documentation comments — it’s a useful reminder to produce complete Javadocs.
Complete POM with all plugins:
<build>
<plugins>
<!-- Compiler Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<release>17</release>
</configuration>
</plugin>
<!-- JAR Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<includes>
<include>**/*.xml</include>
</includes>
</configuration>
</plugin>
<!-- Source Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
<phase>install</phase>
</execution>
</executions>
</plugin>
<!-- Javadoc Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
<phase>install</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
6.12 Summary of module 6
- goals are plugins configured in the application — we accept the default values of the Super POM, but we can override them
- The Compiler Plugin is set by default but usually needs to be overridden to specify the Java version
- The JAR Plugin is configured by default but can be overridden to generate a manifest or set file inclusions/exclusions
- The Source Plugin and Javadoc Plugin can be configured to generate and install source code and Javadocs with the enterprise repository — a best practice for debugging and code navigation, increasingly important with microservices architectures
7. IDE Integration
7.1 IDE Integration Overview
This module covers the integration of IntelliJ Community Edition with Maven. All modern IDEs integrate Maven and can be configured to import or use Maven.
Topics covered:
- The Dependency Overview tool
- profiles to run Maven differently
- The dependency hierarchy and how they are resolved
- The effective POM and what it represents
- multi-module projects
7.2 Managing dependencies in IntelliJ
Adding dependencies is much easier in the IDE — that’s a major advantage.
The Dependencies tab in IntelliJ allows you to:
- View installed dependencies
- Manipulate dependencies
- Find and add new dependencies with search functionality
The Dependency Management section in this tab is a more advanced topic, used only with a parent POM or BOM file (covered in the next module).
7.3 Demo: Adding a dependency via IntelliJ
Different ways to add a dependency in IntelliJ Community Edition:
- Via the Generate menu with Search Ahead capabilities
- Directly by editing the
pom.xmlfile
7.4 Demo: Dependency search with Search Ahead
Steps:
- Open
pom.xmlin IntelliJ - Place yourself in the
dependenciessection (or create space to add one) - Press
Cmd+N(Mac) orCtrl+N(Windows) to open the Generate menu - Select Dependency from the menu
- In the Search For Artifact window, type the name of the dependency (ex:
spring-jdbc) - Select the dependency and the desired version (ex:
6.0.11) - Click on Add
Result automatically added in the POM:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>6.0.11</version>
</dependency>
- After saving, click on the blue Maven icon to reload the Maven changes
Note: Contextual search can be useful if one does not know the
groupIdorartifactId, but can become confusing if it suggests irrelevant items.
7.5 Profiles
profiles are a useful tool for configuring environment-specific build properties.
Features:
- Usually created in
pom.xml, but can be configured insettings.xml - Allows you to pass parameters specific to the environment (e.g. database connections for tests)
- Can be activated from the command line with the
-Pflag
Worked example: Use different resources for development and production environments.
7.6 Demo: Using profiles in IntelliJ
Configuration of a profile in the POM:
<profiles>
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<build>
<resources>
<resource>
<directory>src/main/resources/dev</directory>
</resource>
</resources>
</build>
</profile>
<profile>
<id>prod</id>
<build>
<resources>
<resource>
<directory>src/main/resources/prod</directory>
</resource>
</resources>
</build>
</profile>
</profiles>
Resource structure:
src/main/resources/
├── dev/
│ └── logging.properties (contient: logging.level=dev)
└── prod/
└── logging.properties (contient: logging.level=prod)
Behavior:
- Profile
devis active by default (activeByDefault=true) - During the build, Maven copies the resources from the directory corresponding to the active profile to
target/classes
7.7 Demo: Command Line Profiles
# Utiliser le profile 'dev'
mvn clean compile -P dev
# Vérifier le fichier copié
more target/classes/logging.properties
# Affiche: logging.level=dev
# Utiliser le profile 'prod'
mvn clean compile -P prod
# Vérifier le fichier copié
more target/classes/logging.properties
# Affiche: logging.level=prod
The -P flag followed by the profile ID allows you to activate the desired profile. This is a convenient way to have different resources or configuration files depending on the build environment.
7.8 The dependency hierarchy
The Dependency Hierarchy displays the dependency tree, including transitive and overloaded dependencies, and the scope associated with each.
Information available:
- All direct project dependencies
- All transitive dependencies (and which direct dependency they are derived from)
- The scopes of each dependency
- Version conflicts and their resolution
7.9 Demo: Access the Dependency Hierarchy
Via IntelliJ:
- Open the Maven panel (icon on the right or via View → Tools Windows → Maven)
- Click on the magnifying glass icon (Analyze Dependencies)
- The Dependency Analyzer window opens
Reading Dependency Analyzer:
- Click on a dependency to see where it comes from
- Ex:
spring-jdbc→ comes from ourpom.xml - Ex:
spring-tx→ pulled byspring-jdbc(transitive dependency) - Ex:
spring-core→ pullsspring-txandspring-beansas transitive dependencies testscope dependencies appear distinctly
Command line alternative:
mvn dependency:tree
This command displays the full dependency tree in the terminal.
7.10 The effective POM
The effective POM is the complete POM which combines:
- Settings inherited from
settings.xml - Maven settings itself (Super POM)
- Everything we defined in our
pom.xml
Usage:
- This is a great debugging tool
- Not used frequently, but invaluable when needed
- Shows everything that Maven will actually use during the build
7.11 Demo: Access the effective POM
Via IntelliJ:
- In the Maven panel, select the project
- Right click → Show Effective POM
Content of the effective POM:
- The
<properties>section of thepom.xml - All dependencies declared
- The
<repositories>section with the default location of the central repository <pluginRepositories>for plugins- Build configuration:
sourceDirectory,scriptSourceDirectory,testOutputDirectory, all plugin and plugin management configurations
Use case:
If you want to override a configuration (eg: change the sourceDirectory from src/main/java to src/java for an old Ant project migrated to Maven), you can see the current value in the effective POM and override it explicitly in the project POM:
<build>
<sourceDirectory>src/java</sourceDirectory>
<!-- Ou pour le répertoire de sortie -->
<outputDirectory>build/classes</outputDirectory>
</build>
7.12 Multi-module projects
Multi-module projects used to be more tedious to set up, but the latest IDE features have made them much more intuitive.
Typical structure of a multi-module parent POM:
<?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>
<groupId>com.pluralsight</groupId>
<artifactId>WebServiceProject</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>WebServiceModel</module>
<module>WebServiceClient</module>
<module>WebServiceServer</module>
</modules>
</project>
Three key points:
- The
<modules>tag contains the submodules - The
packagingis of typepom - Each submodule will have a
<parent>reference pointing to that parent project
Typical use case: A web services project with a parent and subprojects for the client and the service.
7.13 Demo: Creating a multi-module project
Create parent project:
- Menu → New → Project
- Name the project
WebServiceProject - Language: Java, Build system: Maven
- Configure Advanced Settings (groupId, etc.)
- Click on Create
IntelliJ creates a Java framework by default — remove the src folder from the parent project because the source code will be in the child modules.
Parent pom.xml must have <packaging>pom</packaging>.
7.14 Demo: WebServiceModel module
Create model module:
- Select the project in the browser (not a file)
- Menu → New → Module
- Name the module
WebServiceModel - Java, Maven, uncheck “Add sample code”
- Click on Create
POM of child module (WebServiceModel) automatically generated:
<?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>
<parent>
<groupId>com.pluralsight</groupId>
<artifactId>WebServiceProject</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>WebServiceModel</artifactId>
</project>
Parent POM updated automatically:
<modules>
<module>WebServiceModel</module>
</modules>
Observation: IntelliJ displays Maven icons to show the parent-child relationship between POMs.
Create a Customer class in the model module:
// Dans src/main/java/com/pluralsight/Customer.java
package com.pluralsight;
public class Customer {
private int id;
private String firstName;
private String lastName;
// ... getters, setters
}
7.15 Demo: WebServiceClient Module
Create client module:
- On the parent project, do New → Module →
WebServiceClient
POM of the WebServiceClient with dependency on the model module:
<?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>
<parent>
<groupId>com.pluralsight</groupId>
<artifactId>WebServiceProject</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>WebServiceClient</artifactId>
<dependencies>
<dependency>
<groupId>com.pluralsight</groupId>
<artifactId>WebServiceModel</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>
Important points:
- Client dependency on model uses
${project.version}— a Maven variable that inherits the parent’s version - Maven understands that the
WebServiceModelmodule must be compiled before theWebServiceClient - Variables can be used in multi-module projects
7.16 Demo: WebServiceServer Module
Create the server module:
- On the parent project, do New → Module →
WebServiceServer
WebServiceServer POM:
<?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>
<parent>
<groupId>com.pluralsight</groupId>
<artifactId>WebServiceProject</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>WebServiceServer</artifactId>
<dependencies>
<dependency>
<groupId>com.pluralsight</groupId>
<artifactId>WebServiceModel</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>
Advantage of multi-module project:
- Shared model (
WebServiceModel) is shared between client and server - If a change is made to the model, it is automatically compiled and used in the client and server
- Separation of responsibilities: the model contains shared objects (e.g.
Customer), the client exposes an API, the server implements the business logic - Maven automatically determines build order based on dependencies
Final Multi-Module Project Overview:
WebServiceProject/
├── pom.xml (parent, packaging=pom, modules=[Model, Client, Server])
├── WebServiceModel/
│ ├── pom.xml (parent: WebServiceProject)
│ └── src/main/java/
│ └── com/pluralsight/
│ └── Customer.java
├── WebServiceClient/
│ ├── pom.xml (parent: WebServiceProject, depends on WebServiceModel)
│ └── src/main/java/
├── WebServiceServer/
│ ├── pom.xml (parent: WebServiceProject, depends on WebServiceModel)
│ └── src/main/java/
7.17 Module 7 Summary
- IDE integration with Maven has simplified a lot of things
- Adding and viewing dependencies is much easier with the IDE
- Running profiles is more common and easily accessible in Maven view
- Dependency hierarchy shows import order in project and associated scopes
- The effective POM helps to understand how things are pulled in the project and what is overloaded
- The IDE has made multi-module projects much more common
- The structure presented (parent + model + client + server) is the way we package all web services projects
- This structure will be used in the next module to show a bill of materials (BOM)
8. Using a BOM file
8.1 What is a BOM?
A BOM (Bill of Materials) is also called a parent POM or dependency POM.
Why use a BOM?
- Allows explicitly set specified versions for subprojects or imported POM
- Many frameworks (especially Spring Boot) use BOM files to distribute their libraries with appropriate dependencies that projects can choose to use
- Do not force the use of all dependencies — they are available but optional
Important distinction:
- Multi-module ≠ BOM required
- BOM ≠ multi-module required
- A BOM can be used independently, just for dependency management (
dependencyManagement)
8.2 The packaging BOM
Differences of a BOM compared to a classic POM:
- The
packagingis of typepom - Using the
<dependencyManagement>tag
Structure of a BOM file:
<?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>
<groupId>com.pluralsight</groupId>
<artifactId>WebServiceProject</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.7</version>
</dependency>
<!-- Autres dépendances gérées -->
</dependencies>
</dependencyManagement>
</project>
Behavior:
- Syntax is same as
dependenciessection but wrapped in<dependencyManagement> - This tells POMs that inherit from this BOM which versions they should use
- Child modules can choose to use these dependencies, but are not required
8.3 Demo: BOM Packaging
By opening the previously created WebServiceProject, we observe that the parent POM already has <packaging>pom</packaging>. This means that there is no Java code in this project — it is the child modules that contain the source code.
This project is already in essence a BOM because it orchestrates the build of its child projects. The difference is that we don’t yet use the <dependencyManagement> section.
Reminder: In the client and the server, a dependency on WebServiceModel was specified. We added it manually in each module. With <dependencyManagement>, we can centralize version management.
8.4 Demo: BOM Dependency Management
Add dependencyManagement in parent POM:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.7</version>
</dependency>
</dependencies>
</dependencyManagement>
Use in a child module (WebServiceServer): In the child module, we can now add the dependency without specifying the version:
<!-- Dans WebServiceServer/pom.xml -->
<dependencies>
<!-- Dépendance avec version héritée du parent BOM -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<!-- Pas de version ici ! Elle est héritée du parent -->
</dependency>
<!-- Dépendance sur le module model -->
<dependency>
<groupId>com.pluralsight</groupId>
<artifactId>WebServiceModel</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
Verification: Using the effective POM view or the Dependency Analyzer, we can confirm that the version of slf4j-api is indeed inherited from the parent BOM.
Main benefit: A single place to manage versions of all shared dependencies. If a version needs to be updated, we only change it once in the BOM.
8.5 Importing a BOM
There is another way to use a BOM: via an import declaration.
Why import is necessary: Like Java, Maven uses simple inheritance — a POM can only inherit from one parent. If your project already has a parent defined and you want to use another BOM (e.g. Spring Boot Starter), you cannot define a second parent.
Solution: scope import
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.2.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Features:
<type>pom</type>: indicates that this dependency is a BOM file<scope>import</scope>: imports thedependencyManagementdefinitions from the BOM into the current project
8.6 Demo: Importing a BOM
Context: Spring Boot projects usually force the use of spring-boot-starter-parent as the parent. In a multi-module project where the parent is already defined, you cannot change the parent. Importing fixes this problem.
Add Spring Boot BOM import to parent POM:
<dependencyManagement>
<dependencies>
<!-- Dépendance interne -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.7</version>
</dependency>
<!-- Import du BOM Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.2.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Result: All child modules (WebServiceModel, WebServiceClient, WebServiceServer) can now use Spring Boot Starter libraries without specifying versions, because these are defined in the imported Spring Boot BOM.
Real use case: In business, we often configure a hierarchical corporate POM and we always want to use the Spring Boot Starter libraries. Importing the Spring Boot BOM solves this problem elegantly without changing the parent structure.
Full parent POM of WebServiceProject with BOM:
<?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>
<groupId>com.pluralsight</groupId>
<artifactId>WebServiceProject</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>WebServiceModel</module>
<module>WebServiceClient</module>
<module>WebServiceServer</module>
</modules>
<dependencyManagement>
<dependencies>
<!-- Bibliothèque de logging interne -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.7</version>
</dependency>
<!-- Import du BOM Spring Boot pour accès aux bibliothèques Spring -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.2.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
8.7 Summary of module 8
This module focused entirely on the Bill of Materials (BOM). The different ways to use it:
- Using parent project with
dependencyManagement: one place to define versions of shared dependencies - Use of import: allows you to integrate an external BOM (e.g. Spring Boot) even when a parent is already defined
Reminder of concepts:
| Concept | Description |
|---|---|
packaging=pom | POM does not contain Java source code — orchestrates child builds |
dependencyManagement | Sets the versions of dependencies available to child modules (without forcing them) |
scope=import | Imports dependencyManagement definitions from an external BOM |
| Simple inheritance | Maven, like Java, only supports one parent — importing bypasses this limitation |
Author’s real-world use case: Setting up an enterprise hierarchical POM but still wanting to use Spring Boot Starter libraries — import solves this problem elegantly. This multi-module structure with BOM is used in many real customer projects.
9. Appendix: Quick Reference
Essential Maven Commands
# Vérifier l'installation de Maven
mvn -version
# Nettoyer le répertoire target
mvn clean
# Compiler le code source
mvn compile
# Exécuter les tests unitaires
mvn test
# Packager l'application (JAR, WAR, etc.)
mvn package
# Installer dans le repository local (~/.m2)
mvn install
# Déployer vers le repository distant
mvn deploy
# Chaîner des commandes
mvn clean install
mvn clean package
# Utiliser un profile spécifique
mvn clean compile -P dev
mvn clean compile -P prod
# Afficher l'arbre des dépendances
mvn dependency:tree
# Afficher les plugins effectifs
mvn help:effective-pom
Standard Maven directory structure
MonProjet/
├── pom.xml
├── src/
│ ├── main/
│ │ ├── java/ ← Code source principal
│ │ │ └── com/monentreprise/
│ │ │ └── Main.java
│ │ └── resources/ ← Fichiers de ressources (properties, XML, etc.)
│ │ ├── dev/
│ │ └── prod/
│ └── test/
│ ├── java/ ← Code de tests unitaires
│ │ └── com/monentreprise/
│ │ └── MainTest.java
│ └── resources/ ← Ressources pour les tests
└── target/ ← Généré par Maven (ne pas committer dans SCM)
├── classes/
├── test-classes/
├── surefire-reports/
└── MonProjet-1.0-SNAPSHOT.jar
Complete pom.xml template
<?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>
<!-- Informations du projet -->
<groupId>com.monentreprise</groupId>
<artifactId>mon-application</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.release>17</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<!-- Dépendances -->
<dependencies>
<!-- Compile scope (défaut) -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<!-- Test scope -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.9.3</version>
<scope>test</scope>
</dependency>
<!-- Provided scope -->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<!-- Build et plugins -->
<build>
<plugins>
<!-- Compiler Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<release>17</release>
</configuration>
</plugin>
<!-- Source Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
<phase>install</phase>
</execution>
</executions>
</plugin>
<!-- Javadoc Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
<phase>install</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
<!-- Repositories (si nécessaire) -->
<repositories>
<repository>
<id>spring-snapshot</id>
<name>Spring Maven SNAPSHOT Repository</name>
<url>https://repo.spring.io/libs-snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
<!-- Profiles -->
<profiles>
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<build>
<resources>
<resource>
<directory>src/main/resources/dev</directory>
</resource>
</resources>
</build>
</profile>
<profile>
<id>prod</id>
<build>
<resources>
<resource>
<directory>src/main/resources/prod</directory>
</resource>
</resources>
</build>
</profile>
</profiles>
</project>
Summary of dependency scopes
| Scope | Compilation | Test | Runtime | Included in the artifact | Example |
|---|---|---|---|---|---|
compiled | ✅ | ✅ | ✅ | ✅ | commons-lang3 |
provided | ✅ | ✅ | ✅ | ❌ | Servlet API |
runtime | ❌ | ✅ | ✅ | ✅ | JDBC driver |
test | ❌ | ✅ | ❌ | ❌ | JUnit |
system | ✅ | ✅ | ✅ | ❌ | Local JARs (deprecated) |
import | — | — | — | — | Spring Boot BOM |
Maven naming conventions
| Element | Agreement | Example |
|---|---|---|
groupId | Reverse domain notation | com.mycompany |
artifactId | Project name in lowercase with hyphens | my-app |
version SNAPSHOT | Suffix -SNAPSHOT in uppercase | 1.0-SNAPSHOT |
version Release | Free, but conventional | 1.0, 1.0.0, 1.0.Final |
| Source Directory | src/main/java | — |
| Test directory | src/test/java | — |
| Output directory | target/ | — |
| Local repository | ~/.m2/repository/ | — |
Search Terms
maven · fundamentals · java · backend · architecture · full-stack · web · dependency · plugin · bom · dependencies · repository · directory · intellij · pom · pom.xml · profiles · repositories · access · added · compiler · effective · goals · hierarchy