Maven: Philosophy, Coordinates & Directory Layout

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Apache Maven 3.10.0 (released on 27 September 2026). Every example marked as run was executed on JDK 26 on 2026-10-16, on macOS, in a scratch directory, with Maven 3.10.0 unless the text names another version; the 3.9.16 and 4.0.0-rc-7 runs are called out where their behaviour differs. Output is shown as printed, with the long scratch path shortened to <scratch> or <module>. The examples use the running scenario of this section, tidepool (see the section landing page), as a Maven build: a parent POM io.tidepool:tidepool-parent:1.0.0 that aggregates tidepool-model, tidepool-engine and tidepool-cli.

Maven is the tool of the three in this section that asks the most of the project and gives the most back for it. Almost everything it does follows from two ideas: a project describes what it is (its coordinates, its dependencies, its packaging) rather than how to build it, and every project is laid out in the same way unless it says otherwise. This page covers those two ideas, the vocabulary that goes with them, and what to do when a project cannot follow the layout. The next pages build on it: Getting Started runs a first project end to end, POM Fundamentals goes through the POM element by element, and Build Lifecycle explains what mvn package actually runs. The ideas behind every build tool, not only Maven, are in Build Automation Fundamentals, and the dependency side of coordinates is in Dependency Management Fundamentals.

Maven’s objectives

The Maven site states the goal in one sentence: to allow a developer to organise and build a Java project in the shortest period of time. It says Maven pursues that in four ways, and each one is visible in how the tool behaves:

  • Making the build process easy. Maven does not remove the need to understand compilation, packaging or testing, but it hides the mechanics. A project that follows the defaults has a POM of about fifteen lines (the generated one on the Getting Started page).

  • Providing a uniform build system. A build is the POM (the project object model) plus a set of plugins. Once you know how one Maven project builds, you know how all of them build: mvn package means the same thing in every repository.

  • Providing quality project information. The same metadata drives reports and a project site: dependencies, test results, a change log from source control, cross-referenced sources, mailing lists. Third-party analysis tools add their reports through plugins.

  • Encouraging better development practices. The tool takes opinions: test sources in a separate but parallel tree, test classes found by a naming convention, test cases that set up their own environment instead of the build preparing it. It also suggests how to lay out a project so that anyone can navigate it.

The same page is candid about the cost. Maven takes an opinionated approach to layout, some projects cannot fit it for historical reasons, and the site says that a project with an unusual structure that cannot be reorganised may have to give up some features, or Maven altogether. That statement is the honest summary of the trade-off: Maven’s value is in the conventions, and the more a project departs from them the less value the tool returns. The last section of this page shows how far a departure can go in practice.

The name is a Yiddish word meaning accumulator of knowledge, and the project began as an attempt to simplify the build processes of the Jakarta Turbine project: several projects, each with its own slightly different Ant build files, with JARs checked into CVS. The wish was for a standard way to build them, a clear definition of what a project consists of, an easy way to publish project information and a way to share JARs across projects. The tool grew from there into what the release history traces from Maven 1.0 (2004) to Maven 3.10.0 and the Maven 4 release candidates.

What Maven is not

The Maven site names three things people say about Maven that are true but miss the point: that it is a site and documentation tool, that it extends Ant to download dependencies, and that it is a set of reusable Ant scriptlets. Maven does all three, but its objectives are different, and treating it as "Ant with downloads" leads to build files that fight the tool. The practical consequence is the mental switch on which the rest of this section depends: in Ant you write targets that do things, in Maven you declare the project and bind plugin goals to a fixed lifecycle. The comparison page puts the three models side by side.

The feature summary

The "feature summary" of the Maven site is a list of claims; reading it as a list of things you can rely on and where each one is covered in this section is more useful than repeating it:

Claim on the Maven site What it means in practice Where it is covered

Simple project setup that follows best practices

archetypes and the standard layout give a new module in seconds

Getting Started, Archetypes

Consistent usage across all projects

the same commands and the same directories everywhere

this page, Build Lifecycle

Superior dependency management, including transitive dependencies

a dependency declares its own dependencies in its POM, and Maven pulls the closure

Dependencies, Scopes & Mediation

Working with multiple projects at the same time

the reactor builds modules in dependency order

Multi-Module Builds & the Reactor

A large repository of libraries and metadata

Maven Central, plus mirrors and repository managers

Repositories, Mirrors & Repository Managers

Extensible, with plugins in Java or scripting languages

every piece of build behaviour is a plugin goal

Plugins & Their Configuration, Writing Plugins

Ant tasks for dependency management and deployment outside Maven

the Maven Resolver Ant Tasks

Maven Resolver Ant Tasks

Model-based builds into predefined output types (JAR, WAR, distribution) without scripting

the packaging element selects a lifecycle mapping

Packaging JARs, WARs & Manifests

A coherent site of project information from the same metadata

the site lifecycle and the project-info reports

Site & Reporting

Release management and distribution publication

tag-driven releases and publishing to a repository

Releasing, Publishing to Maven Central

Convention over configuration

"Convention over configuration" means the tool supplies a default for everything it can, so that a project states only what differs from the default. In Maven the defaults live in a built-in parent that every POM inherits from, the super POM. The super POM fixes the directory structure, the output directories and the final name of the artifact; the default lifecycle bindings (which plugin goal runs in which phase for a jar project) are supplied separately by the packaging, and the effective POM page shows how the two merge.

You can see the defaults without reading any documentation, by asking Maven to evaluate the expressions it will use. The tidepool-model POM does not mention a single directory, yet:

for e in project.build.directory project.build.sourceDirectory project.build.testSourceDirectory \
         project.build.outputDirectory project.build.testOutputDirectory project.build.scriptSourceDirectory \
         project.build.finalName 'project.build.resources[0].directory' 'project.build.testResources[0].directory' \
         project.reporting.outputDirectory; do
  printf '%s = ' "$e"
  mvn -q -B -ntp -pl tidepool-model help:evaluate -Dexpression="$e" -DforceStdout | tail -1; echo
done
project.build.directory = <module>/target
project.build.sourceDirectory = <module>/src/main/java
project.build.testSourceDirectory = <module>/src/test/java
project.build.outputDirectory = <module>/target/classes
project.build.testOutputDirectory = <module>/target/test-classes
project.build.scriptSourceDirectory = <module>/src/main/scripts
project.build.finalName = tidepool-model-1.0.0
project.build.resources[0].directory = <module>/src/main/resources
project.build.testResources[0].directory = <module>/src/test/resources
project.reporting.outputDirectory = <module>/target/site

(<module> stands for the scratch path of the tidepool-model directory.) Every value above comes from the super POM, and an mvn help:effective-pom run on Maven 4.0.0-rc-7 prints the same directories, so the layout is stable across the lines. The finalName is the artifact id and version joined by a hyphen, which is the file name convention that the packaging page and the repository layout below both rely on. The same effective POM also shows what Maven 3.10.0 changed in the defaults: the properties project.build.sourceEncoding and project.reporting.outputEncoding are UTF-8, and project.build.outputTimestamp is 1980-02-01T00:00:00Z. That last property is why every entry of a JAR built by Maven 3.10.0 is dated 1980-02-01 (the unzip -l runs on the Getting Started page show it): reproducible builds are on by default in 3.10, and Security & Reproducible Builds explains the mechanism.

The convention has a price, and it is worth stating plainly because it is the source of most "why does Maven do that" questions: the defaults are right for the common case and invisible. When a project needs something else, you override one element, and that override is the whole configuration. The last section of this page does exactly that for the directory layout.

How the other two tools compare

Gradle also follows conventions, but they come from plugins (the java plugin sets src/main/java and an output under build/) rather than from a built-in parent, and a Gradle build file is a program that can change them freely. Ant has no convention at all: every directory is a property you choose, and the layout recommended in the Ant build file structure page (src, build, dist) is a habit, not a rule of the tool.

Concept Ant Maven 3.10 Gradle 9.8

Where the layout comes from

nowhere; each build file declares its own properties

the super POM, inherited by every POM

the plugins applied (for example java)

Main sources

a property such as src.dir, by habit src

src/main/java

src/main/java

Test sources

a property such as test.dir

src/test/java

src/test/java

Compiled main classes

a property such as build.dir, by habit build/classes

target/classes

build/classes/java/main

All build output

a property, by habit build

target

build

Identity of the project

none built in; Ivy adds organisation, module and revision

groupId:artifactId:version in the POM

group, name and version in the build script, rootProject.name in settings

The Gradle side of the layout is in Java Projects.

The standard directory layout

The standard directory layout is the most visible convention. The Maven guide gives the reason in one sentence: a common layout lets a user who knows one Maven project feel at home in another, with the same advantages as a site-wide look and feel. It asks you to conform as far as you can, and says that where you cannot, the settings can be overridden in the project descriptor (the last section of this page).

The Maven standard directory layout: source directories under src, and the target directory the build writes, with arrows from the main and test source directories to classes and test-classes

The guide lists these directories:

Path Contents

src/main/java

application or library sources

src/main/resources

application or library resources, copied to target/classes (optionally filtered)

src/main/filters

resource filter files

src/main/webapp

web application sources

src/test/java

test sources

src/test/resources

test resources, copied to target/test-classes

src/test/filters

test resource filter files

src/it

integration tests, primarily for plugins

src/assembly

assembly descriptors

src/site

site sources

LICENSE.txt, NOTICE.txt, README.txt

the project’s license, the notices and attributions required by its dependencies, and its readme

pom.xml

the project descriptor, at the top level

The top level of a Maven project holds the pom.xml, the descriptive text files, and two directories at most: src and target. The only others expected are version-control metadata (.git, .svn, CVS) and the subprojects of a multi-project build, each laid out the same way. Under src there is a subdirectory per kind: main for the main artifact, test for unit test code and resources, site and so on. Inside an artifact-producing directory (main and test) there is one directory for the language, java, under which the normal package hierarchy exists, and one for resources, whose structure is copied as it is to the class path. Other contributing sources get their own directory under the same parent: src/main/antlr would hold ANTLR grammar files, for instance. The target directory holds all the output of the build.

Two details are not in the guide’s list but are visible in the runs. src/main/scripts is the default scriptSourceDirectory, shown by the help:evaluate output above. And the Maven 4.0.0-rc-7 build of a project with a 4.1.0 POM model (the last section) logged "skip non existing resourceDirectory" for a second default test resource directory, src/test/resources-filtered; the Maven 3.10.0 builds did not look for it. The Maven 4 POM model page covers the model.

The tidepool module on disk

This is tidepool-model as it is checked into version control, produced by running find in the project and leaving out target and compiled classes. It has exactly the directories it needs and no more. The only non-source files are the POM, and two placeholder resource files for the demonstration:

./tidepool-model/pom.xml
./tidepool-model/src/main/java/io/tidepool/model/Constituent.java
./tidepool-model/src/main/java/io/tidepool/model/Height.java
./tidepool-model/src/main/java/io/tidepool/model/Station.java
./tidepool-model/src/main/java/io/tidepool/model/TideEvent.java
./tidepool-model/src/main/resources/model.properties
./tidepool-model/src/test/java/io/tidepool/model/HeightTest.java
./tidepool-model/src/test/resources/fixture.properties

The Java package directory (io/tidepool/model) is the package hierarchy; Maven does not require the group id to match it, but the convention is the reverse domain name in both.

What the build writes: the target directory

After mvn clean package, tidepool-model/target holds this (listing from the run, abbreviated at the compiled .class files):

tidepool-model/target
tidepool-model/target/classes                                   (1)
tidepool-model/target/classes/model.properties                  (2)
tidepool-model/target/generated-sources/annotations             (3)
tidepool-model/target/generated-test-sources/test-annotations
tidepool-model/target/maven-archiver/pom.properties             (4)
tidepool-model/target/maven-status/maven-compiler-plugin/...    (5)
tidepool-model/target/surefire-reports/TEST-io.tidepool.model.HeightTest.xml
tidepool-model/target/surefire-reports/io.tidepool.model.HeightTest.txt
tidepool-model/target/test-classes
tidepool-model/target/test-classes/fixture.properties
tidepool-model/target/tidepool-model-1.0.0-tests.jar            (6)
tidepool-model/target/tidepool-model-1.0.0.jar                  (7)
1 compiled main classes, src/main/java through the compiler plugin
2 a resource copied unchanged by the resources plugin
3 where annotation processors write generated sources, an empty directory here (it is where picocli’s processor would write in tidepool-cli)
4 the metadata the archiver used when it built the JAR
5 the compiler plugin’s incremental-build state (the files it compiled and the dependencies it saw)
6 the test classes as a JAR, because the module’s POM binds the jar plugin’s test-jar goal
7 the main artifact, named from finalName

Everything in target is generated and is deleted by mvn clean (the clean plugin removes the build directory). That is why target is always in .gitignore and why a clean build is the first thing to try when a build behaves strangely. Gradle’s build directory and an Ant project’s build directory play the same role.

Coordinates

Maven identifies everything it handles by coordinates. The Maven site’s definition of an artifact is the clearest starting point: an artifact is a file (a sequence of bytes) that can be addressed by its coordinates and which Maven downloads, installs or deploys. Most artifacts are POMs and JARs, but an artifact can be any file; what makes it an artifact rather than "just a file" is that it has coordinates and is therefore addressable. The tool uses the short form groupId:artifactId:version, or GAV, although the real coordinates have more fields.

The six properties

Property Meaning Default

groupId

the project group; by convention a reversed domain name that you control

none, required (inherited from the parent if not given)

artifactId

the artifact id, the base of the file name

none, required

version

the artifact version

none, required (inherited from the parent if not given)

baseVersion

the non-timestamped version, linked to version

same as version, differs only for snapshots

classifier

an optional extra label that distinguishes artifacts built from the same POM (sources, javadoc, tests)

empty

extension

the file extension

jar

baseVersion is the one that surprises people. For a release it is the same as version; for a snapshot it is the version without the timestamp. The Maven page gives the example of the snapshot version 1.0-20220119.164608-1, whose base version is 1.0-SNAPSHOT. The two are linked, derived from each other, and differ only for snapshots. This is easy to see by deploying the generated project of the Getting Started page to a local file: repository, which stands in for a remote one. The -DaltDeploymentRepository property takes id::url for the deploy plugin 3.x (the run used the pinned deploy plugin 3.1.2):

mvn -B -ntp -DaltDeploymentRepository=local::file:../snaprepo deploy
find ../snaprepo -type f | sort | grep -v '\.sha\|\.md5'
./com/mycompany/app/my-app/1.0-SNAPSHOT/maven-metadata.xml
./com/mycompany/app/my-app/1.0-SNAPSHOT/my-app-1.0-20261016.104005-1.jar
./com/mycompany/app/my-app/1.0-SNAPSHOT/my-app-1.0-20261016.104005-1.pom
./com/mycompany/app/my-app/maven-metadata.xml

The directory is named by the base version (1.0-SNAPSHOT); the files carry the timestamped version (1.0-20261016.104005-1: the UTC date and time of the deployment, then a build number). The maven-metadata.xml in that directory records the latest timestamp and build number, which is how a consumer resolves "the latest 1.0-SNAPSHOT`". A plain `mvn install to the local repository keeps the -SNAPSHOT name and does not timestamp, as the Getting Started page shows. The snapshot lifecycle is in Versions, Snapshots & Ranges.

A word about uniqueness. GAV coordinates identify an artifact uniquely, but only within one repository: two repositories can hold different files with the same GAV (the Maven site says this is normal with mirrors), and when those files differ it can cause serious problems that go unnoticed. The practical rule is to never publish two different files under one GAV; the supply-chain consequences (checksums, signatures, repository managers that refuse overwrites) are in Supply-Chain Security.

Packaging

The fourth identifying label in a POM is packaging. With the address groupId:artifactId:version there is one more standard label, the packaging, which defaults to jar when it is not given. The core packaging values are pom, jar, maven-plugin, ejb, war, ear and rar; they are Plexus role hints of the component role org.apache.maven.lifecycle.mapping.LifecycleMapping, and each one defines the default list of goals bound to the lifecycle phases. The packaging therefore decides what the build does, not what the file is called: a jar project runs the compiler, the surefire and the jar plugins, a pom project (the tidepool-parent) runs almost nothing. The evaluate run of the engine module confirms the inheritance rules that apply to coordinates and packaging:

project.groupId = io.tidepool
project.artifactId = tidepool-engine
project.version = 1.0.0
project.packaging = jar
project.parent.artifactId = tidepool-parent

tidepool-engine/pom.xml declares no groupId or version of its own: both are inherited from the parent. This is also why the coordinates computed for an artifact are taken from the effective POM, after inheritance.

Where an artifact is stored

Once installed, an artifact lives in a repository under a path computed from its coordinates: the group id with dots turned into slashes, the artifact id, the version, then artifactId-version[-classifier].extension. The run of mvn install against a fresh, private local repository (-Dmaven.repo.local) left this for the tidepool modules (checksum files hidden):

io/tidepool/tidepool-parent/1.0.0/tidepool-parent-1.0.0.pom
io/tidepool/tidepool-model/1.0.0/tidepool-model-1.0.0.jar
io/tidepool/tidepool-model/1.0.0/tidepool-model-1.0.0-tests.jar
io/tidepool/tidepool-model/1.0.0/tidepool-model-1.0.0.pom
io/tidepool/tidepool-model/maven-metadata-local.xml
io/tidepool/tidepool-engine/1.0.0/tidepool-engine-1.0.0.jar
io/tidepool/tidepool-engine/1.0.0/tidepool-engine-1.0.0.pom
io/tidepool/tidepool-cli/1.0.0/tidepool-cli-1.0.0.jar
io/tidepool/tidepool-cli/1.0.0/tidepool-cli-1.0.0.pom

(_remote.repositories files, which record where each file came from, are left out, and so are the other maven-metadata-local.xml files.) The parent POM is an artifact too, with the extension pom and no JAR: a child can be built only when its parent can be found. The local and remote repository layouts, mirrors, and the checksum files are covered in Repositories, Mirrors & Repository Managers; the same layout is shared by Ant (through Ivy or the Resolver tasks) and Gradle, as shown in Repositories & Publishing Across Tools.

Classifier, type and extension

A POM has no extension element on a dependency. It has type, which Maven uses to derive both the extension and, sometimes, the classifier. The Maven site gives the full mapping for a dependency element: the groupId, artifactId and version map directly; the classifier is the classifier element, or the one provided by the type handler; the extension is provided by the type handler, or equal to the type value. Out of the box Maven Core defines eleven types, handled by ArtifactHandler components with the same names:

Type Extension Classifier

pom

pom

jar

jar

maven-plugin

jar

ear

ear

ejb

jar

ejb-client

jar

client

javadoc

jar

javadoc

java-source

jar

sources

rar

rar

test-jar

jar

tests

war

war

If a type is not one of these names, its value is used as the extension, and the classifier is whatever the classifier element says: a <type>tar.gz</type> dependency has the extension tar.gz. Plugins and extensions can add types.

The consequence, which the Maven site flags as surprising, is that one artifact can be described two ways. The tidepool-engine module depends on the test classes of tidepool-model (a test JAR, published by the test-jar goal). Declared with the test-jar type, as the module’s POM does:

<dependency>
  <groupId>io.tidepool</groupId>
  <artifactId>tidepool-model</artifactId>
  <version>${project.version}</version>
  <type>test-jar</type>
  <scope>test</scope>
</dependency>

mvn dependency:list -DincludeScope=test (dependency plugin 3.11.0) reports:

io.tidepool:tidepool-model:jar:1.0.0:compile -- module tidepool.model (auto)
org.apache.commons:commons-csv:jar:1.14.1:compile -- module org.apache.commons.csv
commons-io:commons-io:jar:2.20.0:compile -- module org.apache.commons.io
commons-codec:commons-codec:jar:1.19.0:compile -- module org.apache.commons.codec
io.tidepool:tidepool-model:test-jar:tests:1.0.0:test -- module tidepool.model (auto)

(The run of the tidepool-engine module with its reactor dependency; the list was cut to the dependency lines.) Replacing <type>test-jar</type> with <classifier>tests</classifier> gives the same file, and the list reports it as jar:tests:

io.tidepool:tidepool-model:jar:1.0.0:compile -- module tidepool.model (auto)
io.tidepool:tidepool-model:jar:tests:1.0.0:test -- module tidepool.model (auto)

Adding an explicit <type>jar</type> next to the classifier gives exactly the same two lines. The artifact is the same tidepool-model-1.0.0-tests.jar; only the way the type and classifier are printed differs (the first form prints the type test-jar and the classifier tests, the second prints the type jar and the classifier tests). The Maven site recommends the explicit classifier form for a plain JAR with a classifier, and type when the type handler carries important extra information such as a custom packaging; the test-jar type behaves like an alias for an ordinary JAR with the tests classifier.

The site also notes that "extension" means two different things in a POM. The extension coordinate is the file extension of an artifact (jar, zip, txt), usually implicit. A build extension is a JAR added to the project class loader, referenced by an extension element in build/extensions (see Extensions). They share a name and nothing else.

How a POM, a plugin and an extension get their coordinates

Maven identifies four kinds of thing by coordinates in a POM, and the site spells out how each one is computed:

Thing Where in the POM Classifier Extension

a dependency

project/dependencies/dependency (or the same element under dependencyManagement)

the classifier element, or from the type

from the type (default jar)

the POM itself

the top-level groupId, artifactId, version

always empty

always pom

a build plugin

project/build/plugins/plugin (or under pluginManagement)

always empty

always jar

a build extension

project/build/extensions/extension

always empty

always jar

The coordinates are computed after the POM is made into an effective POM, that is, after the values of the parent have been inherited. A plugin has a groupId that defaults to org.apache.maven.plugins, which is why maven-compiler-plugin can be written without one in the pluginManagement of tidepool-parent.

Coordinates in the other two tools

The Maven triple is the coordinate system of the whole ecosystem, because the repositories are Maven repositories. The other tools map onto it:

Part Ant with Ivy Maven Gradle

Group

organisation

groupId

group

Name

module

artifactId

name

Version

revision

version

version

Extra file of the same module

an artifact with its own type and ext

classifier and type

a classifier on the dependency, or a variant with Gradle Module Metadata

Dependency written as

<dependency org="io.tidepool" name="tidepool-model" rev="1.0.0"/>

groupId, artifactId, version elements

"io.tidepool:tidepool-model:1.0.0" (group, name, version)

The mapping, with Ivy’s configurations and Gradle’s capabilities and variants, is in Dependency Management Fundamentals.

Naming conventions

The Maven site has two documents on naming: a short conventions page and a guide to the naming conventions of coordinates. They agree, and together they say:

  • groupId. It identifies a project group across all others. It should follow Java’s package name rules, which means it starts with a reversed domain name that you control: org.apache.maven, org.apache.commons, com.google.guava. Subgroups append an identifier to the parent’s group id (org.apache.maven.plugins, org.apache.maven.reporting). Legacy single-word group ids exist, but the guide warns that a new single-word one is hard to get approved for Maven Central.

  • artifactId. It is the name of the artifact and the basis of the file name. It should consist of lowercase letters, digits and hyphens only (commons-math, maven-clean-plugin). Maven builds the file name from the artifact id and the version, and the guide strongly recommends keeping the version in the file name so that it can be read at a glance.

  • version. The guide recommends Semantic Versioning 1.0.0: major, minor and patch, numeric and dot-separated (1.0.0, 2.3.2). Labels for pre-releases (1.0.0-beta, 1.0.0-M1, 1.0.0-rc2) or build metadata (1.2.3+dfc0c87) can follow, but the guide advises against dates in those labels because they are usually associated with unstable versions. A version string is limited to 256 characters (a limit the guide says exists to stop pathological inputs from costing too much to parse); longer ones raise an IllegalArgumentException at parse time.

  • SNAPSHOT. A snapshot is an artifact built between releases, a picture of the project at one point in time, mostly used for testing. Usually it is the next anticipated release followed by -SNAPSHOT (1.0.1-SNAPSHOT), and Maven stores snapshots in the snapshotRepository of the POM if one is defined.

These are conventions, and Maven enforces only a part of them. The difference between what is recommended and what is rejected is worth knowing exactly, so here is a POM that breaks the artifact id convention in two ways (an uppercase letter and an underscore) and then in a third (a space):

<modelVersion>4.0.0</modelVersion>
<groupId>io.tidepool</groupId>
<artifactId>Tide_Pool</artifactId>
<version>1.0/beta</version>

With the artifact id Tide_Pool and the version 1.0 beta (a space), mvn validate printed BUILD SUCCESS on Maven 3.10.0, 3.9.16 and 4.0.0-rc-7: uppercase letters and underscores are accepted, and so, in these three versions, is a space in a version. With the artifact id Tide_Pool Core (a space) the build stops:

[ERROR] 'artifactId' with value 'Tide_Pool Core' does not match a valid id pattern. @ line 5, column 15

(The same message appears for Maven 3.9.16; Maven 4.0.0-rc-7 prints it twice, as does not match a valid coordinate id pattern, once for each validation pass.) So the artifact id pattern allows letters, digits, underscores, hyphens and dots, which is wider than the convention. The version is different: the characters \ / : " < > | ? * are rejected, but in different ways by different lines. With the version 1.0/beta:

Maven 3.10.0:
[ERROR] 'version' must not contain any of these characters \/:"<>|?* but found / @ line 6, column 12

Maven 3.9.16:
[WARNING] 'version' must not contain any of these characters \/:"<>|?* but found / @ line 6, column 12
[WARNING] It is highly recommended to fix these problems because they threaten the stability of your build.

Maven 4.0.0-rc-7:
[ERROR] 'version' must not contain any of these characters \/:"<>|?* but found / @ line 6, column 3

(Messages trimmed to the lines that matter.) This is one of the "stricter POM validation" changes of Maven 3.10: what 3.9 accepts with a warning, 3.10 refuses. The release table in Versions & Release History lists the others, and Versions, Snapshots & Ranges explains how Maven orders the version strings that pass validation.

Glossary

The glossary of the Maven site explains the terms that have an explicit meaning in Maven and confuse newcomers. The table keeps the site’s definitions and adds the evidence from the runs on this page where a term shows up in the output. Two entries a reader would expect, goal and reactor, are not in the site’s glossary page; they are defined from the plugin and multi-module guides.

Term Meaning Where you see it

Project

any Java project built with Maven; its top-level directory has at least src and pom.xml. Projects in subdirectories, each with its own pom.xml, are subprojects

tidepool-model is a subproject of tidepool-parent

Project Object Model (POM)

the metadata Maven needs to work with a project, in pom.xml at the root of each project. (The glossary page still says its name is project.xml: that is Maven 1, and is wrong for every Maven since 2.0)

<scratch>/tidepool-model/pom.xml

Artifact

something produced or used by a project: a JAR, a source or binary distribution, a WAR. Identified by group id, artifact id, version, extension and classifier (the extension plus the classifier may be named by a type)

tidepool-model-1.0.0.jar

GroupId

a universally unique identifier of a project, preferably a fully-qualified package name such as org.apache.maven rather than a short name

io.tidepool

Dependency

a library a project needs to build or run; usually the JAR artifact of another project, referenced through the POM that describes it

the commons-csv dependency of tidepool-engine

Plugin

Maven is organised in plugins; every piece of functionality is provided by one. Plugins provide goals and use the POM metadata to do their task

maven-jar-plugin

Mojo

a plugin written in Java consists of one or more mojos; a mojo is a Java class and the implementation of one goal. The name is a pun on POJO

org.apache.maven.plugins.jar.JarMojo, the implementation of jar:jar

Goal

one task a plugin offers, named plugin-prefix:goal-name; it has a default lifecycle phase and parameters

jar:jar is bound to package

Repository

a place where artifacts are stored and found, local (~/.m2/repository) and remote

https://repo.maven.apache.org/maven2/

Snapshot

a version with the suffix SNAPSHOT, meaning work in progress: always looked up in all remote repositories and downloaded again if newer than the local copy

1.0-SNAPSHOT

Reactor

the part of the core that handles multi-module projects: it collects the modules to build, sorts them into build order, and builds them

the "Reactor Build Order" in the output

The glossary also defines two documentation formats of the Maven site, APT and XDoc (the formats currently used for site content are in Site & Reporting). Its examples of plugins, jar, eclipse and war, include one that is retired: the Maven site’s plugin list shows the eclipse plugin (last version 2.10) retired on 2015-10-07, the idea plugin (2.2.1) on 2013-07-26 and the reactor plugin (1.1) on 2014-03-24. IDEs read the POM directly.

Two of those terms can be verified by running Maven. help:describe reports the mojo behind a goal, and its class:

mvn -N -B -ntp help:describe -Dcmd=jar:jar -Ddetail
[INFO] --- help:3.5.2:describe (default-cli) @ tidepool-parent ---
[INFO] 'jar:jar' is a plugin goal (aka mojo).
Mojo: 'jar:jar'
jar:jar
  Description: Build a JAR from the current project.
  Implementation: org.apache.maven.plugins.jar.JarMojo
  Language: java
  Bound to phase: package

(Trimmed to the first lines; the real output goes on with every parameter of the goal. -N runs the goal on the root project only. Without it, the same command on Maven 3.10.0 ran the goal once, on tidepool-parent, and the reactor summary reported the three modules as SKIPPED.) And the reactor announces itself at the start of every multi-module build:

[INFO] Reactor Build Order:
[INFO]
[INFO] tidepool parent                                                    [pom]
[INFO] tidepool model                                                     [jar]
[INFO] tidepool engine                                                    [jar]
[INFO] tidepool cli                                                       [jar]

The order is not the order of <modules> in the parent POM by accident: the reactor sorts projects so that any project is built before the ones that need it (a project dependency on another module, a plugin declared from another module, a build extension), and falls back to the declared <modules> order when no other rule applies. Only references that are used count; dependencyManagement and pluginManagement do not change the order. The module list of tidepool-parent happens to be in dependency order already (model, engine, cli), so the run does not demonstrate the sorting; the page on multi-module builds and the reactor does.

Source: Maven: Glossary

Using a non-standard layout

The Maven guide for this situation starts with a warning that is worth repeating before the technique: use it as a last resort. It is a misconception that Maven cannot build a project that does not follow the layout, but some features or plugins, especially third-party ones, may not work or may not work completely, and the defaults exist because they save every future reader from learning another layout. The guide covers three cases: several source directories feeding one artifact, several artifacts built from one source directory, and several artifacts from one POM (which it discourages in favour of one artifact per POM).

For the simple case, a legacy project whose sources are in src/, tests in test/ and resources in etc/, the technique is to override the directories in the POM’s build element. The experiment copies the tidepool-model sources into that Ant-style layout, with a POM that declares nothing about directories. The default layout finds nothing, and the build still succeeds, which is the dangerous part:

[INFO] skip non existing resourceDirectory <scratch>/legacy/src/main/resources
[INFO] No sources to compile
[INFO] skip non existing resourceDirectory <scratch>/legacy/src/test/resources
[INFO] No sources to compile
[WARNING] JAR will be empty - no content was marked for inclusion!
[INFO] Building jar: <scratch>/legacy/target/tidepool-legacy-1.0.0.jar
[INFO] BUILD SUCCESS

Maven did not fail: there was nothing to compile, nothing to test and the JAR is empty but for its META-INF entries, which is why that one WARNING must not be ignored. Adding the directories to the POM:

<build>
  <sourceDirectory>src</sourceDirectory>
  <testSourceDirectory>test</testSourceDirectory>
  <resources>
    <resource>
      <directory>etc</directory>
    </resource>
  </resources>
  <directory>build</directory>
</build>

and running mvn package again, now with the output under build because of <directory>build</directory>:

[INFO] Copying 1 resource from etc to build/classes
[INFO] Compiling 4 source files with javac [debug release 21] to build/classes
[INFO] skip non existing resourceDirectory <scratch>/legacy/src/test/resources
[INFO] Compiling 1 source file with javac [debug release 21] to build/test-classes
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] Building jar: <scratch>/legacy/build/tidepool-legacy-1.0.0.jar
[INFO] BUILD SUCCESS

(Lines of the output that did not carry the information were left out. The line "skip non existing resourceDirectory …​ src/test/resources`" is there because the default test resources directory still applies: only `resources was overridden.) The JAR holds the four classes and model.properties. The directories you can override this way are exactly the ones evaluated earlier: sourceDirectory, testSourceDirectory, scriptSourceDirectory, outputDirectory, testOutputDirectory, resources, testResources, directory (the build directory) and finalName. They live in the parent build element, so, as the POM reference says, they cannot be changed by a profile.

Several source directories: Maven 3 and Maven 4

Maven 3 has exactly one sourceDirectory and one testSourceDirectory. To add a second source directory, the usual route is a third-party plugin (the Build Helper Maven Plugin from MojoHaus) or a generator plugin that registers the generated directory. Maven 4 adds a new sources element in the 4.1.0 POM model for this. Multiple source elements can be repeated, with a scope (main or test) and a directory each, and it also offers include and exclude filters, support for multi-release projects and the Java module source hierarchy. The same src, test and etc layout, written with sources and built with Maven 4.0.0-rc-7, compiled and tested:

<project xmlns="http://maven.apache.org/POM/4.1.0">
  <modelVersion>4.1.0</modelVersion>
  ...
  <build>
    <sources>
      <source>
        <scope>main</scope>
        <directory>src</directory>
      </source>
      <source>
        <scope>test</scope>
        <directory>test</directory>
      </source>
    </sources>
    <resources>
      <resource>
        <directory>etc</directory>
      </resource>
    </resources>
  </build>
</project>
[WARNING] Version not locked for default bindings plugins [maven-resources-plugin, maven-compiler-plugin, maven-surefire-plugin, maven-jar-plugin], you should define versions in pluginManagement section of your pom.xml or parent
[INFO] --- resources:3.5.0:resources (default-resources) @ tidepool-legacy ---
[INFO] Copying 1 resource from etc to target/classes
[INFO] --- compiler:3.16.0:compile (default-compile) @ tidepool-legacy ---
[INFO] Compiling 4 source files with javac [debug release 21] to target/classes
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS

The 4.1.0 model is available only for the build POM; the consumer POM that is published stays on 4.0.0, so consumers on Maven 3 can still use the library. The 4.0.0-rc-7 run also shows the plugin versions it chose by default (the compiler plugin 3.16.0, as in Maven 3.10.0) and warns that they are not locked, which is Maven 4’s nudge to pin versions in pluginManagement as tidepool-parent does. The model change and the consumer POM are in Maven 4: POM Model & Consumer POM.

What you give up

Overriding a directory moves one thing in the build and leaves all the plugins that assume the default to guess. The first-party plugins read the project model, so they follow the override (as the legacy run showed for the compiler, resources, surefire and jar plugins). Third-party plugins and your own scripts may hard-code src/main/... or target/..., and the filtered resource files (src/main/filters), the site (src/site), the assembly descriptors (src/assembly) and the integration tests (src/it) each keep their default location unless the plugin that uses them is configured too. The cost of a non-standard layout is therefore paid in configuration, in plugins that do not understand it, and in readers who must learn it, which is the reason for the guide’s warning. The alternative the guide points to for "several artifacts from one source tree" is a multi-module build with one POM per artifact, which keeps the layout of the existing sources and adds a POM directory per artifact. The multi-module structure itself is in Inheritance, Aggregation & the Effective POM. The guide’s own example of that technique pins the compiler plugin to version 2.0.2, a very old release that should not be copied: the plugin versions to use are those of Versions & Release History.

Where the book is outdated

Apache Maven 2 Effective Implementation (Ching and Porter, 2009, written for Maven 2.2) is right about the ideas on this page: the standard layout, the coordinates, the repository layout and the best practice of one artifact per POM have not changed since Maven 2.0 (2005). It is out of date in the details around them. It installs Maven 2.2.1 (the 2.x line has been end of life since 2014-02-18; the maintained lines are 3.10.0 and 3.9.16, with 4.0.0-rc-7 as a release candidate); it sets M2_HOME and unpacks Maven per machine (M2_HOME is unnecessary, and the Maven Wrapper is the per-project approach); it assumes Java 5 (Maven 3.9 and 3.10 need Java 8 or later, Maven 4 needs Java 17); it uses plugins without versions and relies on the RELEASE and LATEST plugin metaversions (always pin plugin versions); and it uses archetype:create, which no longer exists: on Maven 3.10.0 mvn archetype:create fails with Could not find goal 'create' in plugin org.apache.maven.plugins:maven-archetype-plugin:3.4.1 among available goals crawl, create-from-project, generate, help, integration-test, jar, update-local-catalog. The glossary page of the Maven site itself still describes the POM file as project.xml, which is Maven 1.

Where to go next