Beginner

Maven Fundamentals

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.

Maven version covered: 3.9 Java version covered: Java 21 (Java 17 compatible)

Table of Contents

  1. Course Overview
  2. Introduction to Maven
  1. Project structure with Maven
  1. Working with dependencies
  1. Using repositories
  1. Using plugins
  1. IDE Integration
  1. Using a BOM file

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:

  1. General introduction to Maven
  2. The structure: the folder structure and how everything is organized if you follow it
  3. Dependencies: if you use Maven for only one thing, it’s dependency management and handling transitive dependencies
  4. Repositories: why use them, what a local repository is and how it connects to the corporate repository
  5. Plugins: each action in Maven is actually a plugin
  6. IDE integration: how to connect Maven to your IDE
  7. 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 clean target is clearly defined
  • ${build.dir} is a variable evaluated at runtime
  • If we want to rename clean to clear, 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.xml is 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:

CriterionAntMaven
ModelDeclarative ScriptingConvention over configuration
Learning curveShorterLonger initially
DestinationScripting toolManagement of the complete project lifecycle
VerbosityVery verbose (everything must be defined)Minimal (the conventions take care of the rest)
ReusabilityLowHigh

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:

  1. Open a browser and go to maven.apache.org
  2. Go to the Download link and go down to the Files section
  3. Download the appropriate bin.zip file for your operating system
  4. 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-maven folder

  1. Configure environment variables:
  • Open system Environment Variables
  • Create a new system variable: JAVA_HOMEC:\dev\tools\java
  • Create a new system variable: MAVEN_HOMEC:\dev\tools\apache-maven
  • Edit the PATH variable and add:
  • %JAVA_HOME%\bin
  • %MAVEN_HOME%\bin
  • Move these entries to the top of environment variables
  1. 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:

  1. Go to the C:\ directory and create a workspace folder
  2. In workspace, create a SimpleProject folder
  3. In SimpleProject, create the following folder structure:
SimpleProject/
└── src/
    └── main/
        └── java/
  1. 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
  1. In the project, create the pom.xml 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.yourcompany</groupId>
    <artifactId>SimpleProject</artifactId>
    <version>1.0</version>

</project>

Note: The groupId represents the name of your company (eg: com.yourcompany). The artifactId must 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, target and pom.xml are all at the root level of the application
  • The SimpleProject project 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/groovy for Groovy
  • src/main/resources for resources (config files, XML, etc.) — frequently seen in Spring Boot projects
  • src/main/scala for Scala, src/main/kotlin for Kotlin

These directories help separate different types of source code, and different Maven plugins are used to access them.

Tests:

  • The src/test/java directory 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 from src/main/java
  • maven-archiver/: references the structure of pom.xml for packaging
  • surefire-reports/ and test-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:

  1. Project information (groupId, artifactId, version, packaging)
  2. Dependencies (section dependencies)
  3. Build (plugins, configuration)
  4. 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 release 1.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:

  1. Open the pom.xml file
  2. Under the packaging tag, before the project closing tag, add a dependencies section
  3. In the dependencies section, add a dependency element
  4. The IDE provides context-sensitive help for groupId and artifactId

Example:

<dependencies>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.12.0</version>
    </dependency>
</dependencies>
  1. Save and click on the Maven icon to refresh the project (resolve dependencies)
  2. In External Libraries, we can see that the commons-lang JAR 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:

GoalDescription
cleanRemove target directory and generated sources
compiledCompile 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.
packageRun compile first, then run all unit tests, then package the code according to the packaging type defined in the POM.
installRun package, then copy the JAR/WAR to the local repository (~/.m2).
deployRun install, then deploy to a remote repository (corporate repository). ⚠️ Do not confuse with application deployment on a web server.

Important: deploy does 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 directory
  • target: everything is compiled and packaged in this directory
  • Both of these are controlled by sections of the POM
  • Adding a dependency: a dependencies section 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:

ElementDescription
groupIdTypically the same as the artifact package structure
artifactIdThe name of the element we want to use
versionThe 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 final or release — 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:

TypeDescription
jarMost used default — Java archive
warWeb Application Archive
earEnterprise Application Archive
rarResource Adapter Archive
byPlugin Archive
pomProject Object Model — special case
maven-pluginMaven Plugin
ejbEnterprise 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:

  • antlr 2.7.7
  • byte-buddy
  • classmate
  • commons-lang3
  • dom4j 2.1.1
  • hibernate-commons-annotations
  • javassist
  • 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:

ScopeDescription
compiledDefault scope (implicit if omitted). All resources are available everywhere in the app. Included in the final artifact.
providedSimilar 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.
runtimeNot necessary for compilation, but necessary for execution. Example: dynamically loaded JDBC drivers (Class.forName(), DriverManager).
testAvailable only for compiling and running tests. Not included in the final artifact. Example: JUnit, Mockito.
systemSimilar to provided, but must specify the explicit location of the JAR on the system. Avoid if possible.
importUsed 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. test is 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 name is 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 repository elements in repositories

5.5 Demo: Adding a SNAPSHOT repository

To add the Spring Core SNAPSHOT repository:

  1. Navigate to https://repo.spring.io/libs-snapshot (redirects to JFrog Artifactory — Spring uses Artifactory to host its repos)
  2. Navigate to org/springframework/spring-core
  3. 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:// with https://.

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 pluginRepositories instead of repositories

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, deploy are 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 clean phase)

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:

#PhaseDescription
1validateValidates that the project is correct and that all necessary information is available (plugins, downloaded artifacts, structure in place, permissions)
2compiledCompile the main source code (src/main/java)
3testCompile the test code (src/test/java) and run the unit tests
4packagePackage the compiled code according to the defined type (JAR, WAR, etc.). Many link the generation of resources or Javadocs to this phase
5integration-testDeploys and runs integration tests (newer phase of Maven, less used)
6verifyRuns checks on the project to ensure it meets quality criteria before installing into the local repository
7installInstall the artifact in the local repository
8deployDeploy the artifact to the enterprise remote repository

Note: Do not confuse verify and validateverify runs before install and deploy to 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 javac but 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 thread
  • meminitial and maxmem: heap memory (useful for projects with a lot of code)
  • release: target Java version (replaces the old source and target options)

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 groupId is the same as other Maven plugins: org.apache.maven.plugins
  • The include uses the Ant pattern matching syntax (ex: **/*.xml includes all XML files)
  • To test from the command line:
mvn package

Phases executed during mvn package: Maven displays the phases in execution order: resourcescompiletestResourcestestCompiletestpackage (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: install or verify) 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 install or verify so as not to slow down frequent builds
  • Can be customized (company logo, colors, styles)
  • Also generate an apidocs folder in target

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:

  1. Via the Generate menu with Search Ahead capabilities
  2. Directly by editing the pom.xml file

7.4 Demo: Dependency search with Search Ahead

Steps:

  1. Open pom.xml in IntelliJ
  2. Place yourself in the dependencies section (or create space to add one)
  3. Press Cmd+N (Mac) or Ctrl+N (Windows) to open the Generate menu
  4. Select Dependency from the menu
  5. In the Search For Artifact window, type the name of the dependency (ex: spring-jdbc)
  6. Select the dependency and the desired version (ex: 6.0.11)
  7. Click on Add

Result automatically added in the POM:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>6.0.11</version>
</dependency>
  1. 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 groupId or artifactId, 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 in settings.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 -P flag

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 dev is 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:

  1. Open the Maven panel (icon on the right or via View → Tools Windows → Maven)
  2. Click on the magnifying glass icon (Analyze Dependencies)
  3. The Dependency Analyzer window opens

Reading Dependency Analyzer:

  • Click on a dependency to see where it comes from
  • Ex: spring-jdbc → comes from our pom.xml
  • Ex: spring-tx → pulled by spring-jdbc (transitive dependency)
  • Ex: spring-core → pulls spring-tx and spring-beans as transitive dependencies
  • test scope 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:

  1. In the Maven panel, select the project
  2. Right click → Show Effective POM

Content of the effective POM:

  • The <properties> section of the pom.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:

  1. The <modules> tag contains the submodules
  2. The packaging is of type pom
  3. 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:

  1. Menu → NewProject
  2. Name the project WebServiceProject
  3. Language: Java, Build system: Maven
  4. Configure Advanced Settings (groupId, etc.)
  5. 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:

  1. Select the project in the browser (not a file)
  2. Menu → NewModule
  3. Name the module WebServiceModel
  4. Java, Maven, uncheck “Add sample code”
  5. 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:

  1. 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 WebServiceModel module must be compiled before the WebServiceClient
  • Variables can be used in multi-module projects

7.16 Demo: WebServiceServer Module

Create the server module:

  1. 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:

  1. The packaging is of type pom
  2. 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 dependencies section 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 the dependencyManagement definitions 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:

  1. Using parent project with dependencyManagement: one place to define versions of shared dependencies
  2. Use of import: allows you to integrate an external BOM (e.g. Spring Boot) even when a parent is already defined

Reminder of concepts:

ConceptDescription
packaging=pomPOM does not contain Java source code — orchestrates child builds
dependencyManagementSets the versions of dependencies available to child modules (without forcing them)
scope=importImports dependencyManagement definitions from an external BOM
Simple inheritanceMaven, 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

ScopeCompilationTestRuntimeIncluded in the artifactExample
compiledcommons-lang3
providedServlet API
runtimeJDBC driver
testJUnit
systemLocal JARs (deprecated)
importSpring Boot BOM

Maven naming conventions

ElementAgreementExample
groupIdReverse domain notationcom.mycompany
artifactIdProject name in lowercase with hyphensmy-app
version SNAPSHOTSuffix -SNAPSHOT in uppercase1.0-SNAPSHOT
version ReleaseFree, but conventional1.0, 1.0.0, 1.0.Final
Source Directorysrc/main/java
Test directorysrc/test/java
Output directorytarget/
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

Interested in this course?

Contact us to book it or get a custom training plan for your team.