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

Summary

  • Maven 4 replaces mutable models with @Immutable value objects for all core types including Project, Artifact, and Dependency.
  • Zero mutable collections are exposed; the API uses ImmutableCollections to 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →