Maven 4 Immutable API: Thread-Safe Value Objects for Reliable Builds
Maven 4 introduces a purely immutable public API where all core model types—including Project, Artifact, Dependency, and XML nodes—are defined as @Immutable value objects that eliminate setters and mutable collections, ensuring thread-safe, side-effect-free data sharing across the build lifecycle.
The Apache Maven project restructured its internal architecture in version 4 by replacing the mutable JavaBeans model used through Maven 3.10 with a strictly immutable API. This redesign affects everything from POM parsing to dependency resolution, utilizing generated value objects and read-only collections to prevent unintended side effects and enable safe parallel execution.
What Is the Maven 4 Immutable API?
The Maven 4 immutable API is a complete redesign of the project's public interface that treats all data as immutable value objects. Every core model type—including Project, Artifact, Dependency, and the POM model itself—carries the @Immutable annotation defined in api/maven-api-annotations/src/main/java/org/apache/maven/api/annotations/Immutable.java. This contract guarantees that once an instance is created, its state cannot change, making objects inherently thread-safe and safe to share across concurrent build components without defensive copying.
How Immutable Value Objects Work
Generated from the MDO Model
Unlike previous versions where model classes were hand-maintained, Maven 4 generates its immutable POM classes from the Maven MDO (maven.mdo) definition. The generated source lives in the api/maven-api-model module, as documented in api/maven-api-model/src/main/java/org/apache/maven/api/model/package-info.java. This approach ensures consistency between the XML schema and the Java API while enforcing immutability at the code generation level.
Immutable Collections Implementation
The API never exposes mutable Java collections. Instead, api/maven-api-xml/src/main/java/org/apache/maven/api/xml/ImmutableCollections.java provides fast, read-only implementations including emptyMap(), singletonMap(), and copy(Collection) methods. These return instances of AbstractImmutableMap and AbstractImmutableSet that throw UnsupportedOperationException on any mutation attempt. The copy(Map) implementation at lines 71-87 creates efficient read-only views without the overhead of defensive copies.
Builder Pattern Construction
Since objects cannot be modified after creation, Maven 4 uses builders for complex construction. For example, XmlNode in api/maven-api-xml/src/main/java/org/apache/maven/api/xml/XmlNode.java provides a builder() method that returns a mutable builder, but the build() method returns an immutable instance. The constructor at line 89 uses ImmutableCollections.copy(...) to store attributes and children, ensuring deep immutability.
Thread Safety and Performance Benefits
Because model objects cannot change after construction, Maven components—including builders, resolvers, and plugins—can safely share them without explicit synchronization. This design eliminates an entire class of bugs caused by unintended side effects and simplifies caching and change detection in the new incremental build engine. The immutable API also provides a clean separation of data (immutable model objects) from behavior (services and components).
Backward Compatibility
While Maven 4 uses the immutable model internally, the mutable Maven 3 model remains available for legacy code. The two models are kept strictly separate to prevent accidental mutation of data that the new engine relies on, allowing gradual migration of existing plugins and extensions.
Working with the Immutable API
Creating Immutable XML Nodes
Use the XmlNode.builder() to construct XML trees, then call build() to receive an immutable instance stored via ImmutableCollections in the internal implementation:
// Build an XML node using the mutable builder
XmlNode node = XmlNode.builder()
.name("project")
.attribute("xmlns", "http://maven.apache.org/POM/4.0.0")
.child(XmlNode.builder()
.name("modelVersion")
.text("4.0.0")
.build())
.build(); // ← returns an immutable XmlNode
// Attempting to modify throws UnsupportedOperationException
// node.attributes().put("new", "value"); // UOE
Accessing Project Data
Objects returned by ProjectBuilder provide read-only views of collections backed by ImmutableCollections.copy(...):
// Project returned from ProjectBuilder
Project project = projectBuilder.build(...);
// Accessors provide immutable views
List<Artifact> artifacts = project.getArtifacts(); // immutable list
Map<String,String> properties = project.getProperties(); // immutable map
// artifacts.add(newArtifact); // UnsupportedOperationException
Using ImmutableCollections Directly
For custom implementations, use the utility class to wrap existing collections:
Map<String,String> map = Map.of("a","1", "b","2");
Map<String,String> immutable = ImmutableCollections.copy(map);
// Returns AbstractImmutableMap - mutating methods throw UOE
Key Source Files
api/maven-api-annotations/src/main/java/org/apache/maven/api/annotations/Immutable.java: Defines the@Immutableannotation marking types as thread-safe and immutable.api/maven-api-model/src/main/java/org/apache/maven/api/model/package-info.java: Documents the immutable POM model generated frommaven.mdo.api/maven-api-xml/src/main/java/org/apache/maven/api/xml/XmlNode.java: Immutable XML node representation with the builder pattern.api/maven-api-xml/src/main/java/org/apache/maven/api/xml/ImmutableCollections.java: Fast read-only collections utility (lines 71-87 forcopy(Map)).api/maven-api-core/src/main/java/org/apache/maven/api/Project.java: Immutable project model interface.impl/maven-xml/src/main/java/org/apache/maven/internal/xml/XmlNodeImpl.java: Internal implementation usingImmutableCollectionsfor storage.
Summary
- Maven 4 replaces mutable models with
@Immutablevalue objects for all core types includingProject,Artifact, andDependency. - Zero mutable collections are exposed; the API uses
ImmutableCollectionsto provide read-only maps and lists. - Builder pattern is required for object construction—objects are immutable after calling
build(). - Thread-safe by design eliminates synchronization needs and enables safe parallel processing.
- MDO-generated code ensures the POM model matches the XML schema while enforcing immutability.
Frequently Asked Questions
What is the difference between Maven 3 and Maven 4 API models?
Maven 3 relies on mutable JavaBeans with setters and getters, while Maven 4 uses strictly immutable value objects. The Maven 4 API annotates all model types with @Immutable and removes all setter methods, replacing them with builder patterns and copy methods.
How does the @Immutable annotation ensure thread safety?
The @Immutable annotation in api/maven-api-annotations/src/main/java/org/apache/maven/api/annotations/Immutable.java marks classes as inherently thread-safe by contract. Combined with the implementation in classes like XmlNode, which uses ImmutableCollections.copy(...) to store all data, it guarantees that no state changes can occur after construction, allowing safe sharing across threads without synchronization.
Can I modify a Maven 4 Project object after it is created?
No. Once constructed via ProjectBuilder or similar factories, Project objects are immutable. Any attempt to modify the returned collections—such as calling add() on project.getArtifacts() or put() on project.getProperties()—throws UnsupportedOperationException.
Where are the immutable POM model classes generated from?
The immutable POM classes are generated from the maven.mdo model definition during the build process. The generated code resides in api/maven-api-model and is documented in package-info.java as "Maven Immutable POM... generated from maven.mdo".
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →