# How Maven's POM Model Merging and Interpolation Works: Inside the Model-Builder

> Understand Maven's POM model merging and interpolation. Learn how Maven builds the effective POM by merging parents and resolving placeholders within the model builder.

- Repository: [The Apache Software Foundation/maven](https://github.com/apache/maven)
- Tags: internals
- Published: 2026-07-05

---

**Maven constructs the effective POM by recursively merging parent POMs, imported BOMs, and the super-POM via `MavenModelMerger`, then resolves `${...}` placeholders through `StringVisitorModelInterpolator` using a hierarchical chain of value sources.**

The `apache/maven` repository contains the **model-builder** module, which orchestrates the transformation of raw [`pom.xml`](https://github.com/apache/maven/blob/main/pom.xml) files into an effective, ready-to-use model. Understanding **POM model merging and interpolation** is essential for debugging dependency conflicts, inheritance issues, and property resolution in multi-module builds.

## Phase 1: Model Merging and Inheritance Assembly

Maven builds the effective POM by constructing an inheritance tree and merging models from multiple sources. This phase occurs before any property substitution takes place.

### Locating the Parent and Building the Inheritance Chain

The process begins in `DefaultModelBuilder` (located in [`compat/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilder.java`](https://github.com/apache/maven/blob/main/compat/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilder.java)), which resolves the `<parent>` element coordinates and loads the parent POM from the repository or file system. 

The `DefaultInheritanceAssembler` recursively loads all ancestors until reaching the built-in **super-POM**—a default POM that defines standard values for plugins, reporting, and directory structures. The `DefaultSuperPomProvider` (see [`compat/maven-model-builder/src/main/java/org/apache/maven/model/superpom/DefaultSuperPomProvider.java`](https://github.com/apache/maven/blob/main/compat/maven-model-builder/src/main/java/org/apache/maven/model/superpom/DefaultSuperPomProvider.java)) supplies this ultimate parent, ensuring every POM inherits sensible defaults even without an explicit parent declaration.

### Merging Rules and Algorithm

The actual merging logic resides in `MavenModelMerger` (see [`compat/maven-model-builder/src/main/java/org/apache/maven/model/merge/MavenModelMerger.java`](https://github.com/apache/maven/blob/main/compat/maven-model-builder/src/main/java/org/apache/maven/model/merge/MavenModelMerger.java)). This class implements the POM specification's inheritance rules:

- **Scalar values**: Child overrides parent (e.g., `<version>`, `<name>`)
- **Lists**: Concatenated (e.g., `<dependencies>`, `<plugins>`)
- **Maps**: Merged (e.g., `<properties>`)
- **Management sections**: `dependencyManagement` and `pluginManagement` are injected before the child's own declarations

Helper injectors like `DefaultDependencyManagementInjector`, `DefaultPluginManagementInjector`, and `DefaultProfileInjector` modularize the merging of specific sections.

### Importing BOMs and Dependency Management

When a POM contains `<dependencyManagement>` with `<scope>import</scope>` dependencies (BOMs), the `MavenModelMerger` treats these imported models as if they were part of the parent's dependency management section. This allows projects to inherit version constraints from external Bill of Materials POMs without direct parent-child relationships.

## Phase 2: Model Interpolation and Property Resolution

After merging, the model contains unresolved placeholders like `${project.version}` or `${env.JAVA_HOME}`. Maven resolves these through a two-step interpolation process.

### Pre-Processing and Value Sources

Before interpolation begins, the `UrlNormalizingPostProcessor` cleans up `file:` and `http:` URLs, while `MavenBuildTimestamp` supplies the `${maven.build.timestamp}` property.

The `ModelInterpolator` (entry point in [`compat/maven-model-builder/src/main/java/org/apache/maven/model/interpolation/ModelInterpolator.java`](https://github.com/apache/maven/blob/main/compat/maven-model-builder/src/main/java/org/apache/maven/model/interpolation/ModelInterpolator.java)) constructs a **value source chain** that determines lookup priority:

1. **Object-based sources**: `ObjectBasedValueSource` exposes model fields like `${project.version}`, `${project.artifactId}`, and `${project.build.directory}`
2. **Prefixed sources**: `PrefixedObjectValueSource` handles `${env.*}`, `${settings.*}`, and `${java.*}` lookups
3. **System properties**: JVM system properties like `${user.home}` and `${java.version}`
4. **User-defined properties**: Values from the `<properties>` section of the effective POM

### The Interpolation Engine

The `StringVisitorModelInterpolator` (see [`compat/maven-model-builder/src/main/java/org/apache/maven/model/interpolation/StringVisitorModelInterpolator.java`](https://github.com/apache/maven/blob/main/compat/maven-model-builder/src/main/java/org/apache/maven/model/interpolation/StringVisitorModelInterpolator.java)) traverses the model tree and applies a regular expression search (`\\$\\{([^}]+)\\}`) to locate placeholders in string values.

For each match, the interpolator consults the value-source chain in order. If a placeholder cannot be resolved, Maven records a warning or fails if the value is required for build execution. This algorithm runs recursively until all `${...}` tokens are replaced with concrete values.

## Practical Implementation Examples

### Building an Effective Model Programmatically

You can trigger the full merge and interpolation pipeline programmatically:

```java
ModelBuilder builder = new DefaultModelBuilderFactory().newInstance();
ModelBuildingRequest request = new DefaultModelBuildingRequest()
        .setPomFile( new File("my-module/pom.xml") )
        .setModelResolver( new DefaultModelResolver( repoSystem, session ) )
        .setProcessingModelInterpolator( true )
        .setProcessingModelValidation( true );

ModelBuildingResult result = builder.build( request );
Model effective = result.getEffectiveModel();

System.out.println( "Effective version: " + effective.getVersion() );
System.out.println( "Resolved property: " + effective.getProperties().getProperty("my.prop") );

```

This code produces a fully resolved `Model` object with all parent inheritance and property interpolation applied.

### Manual Interpolation of Specific Strings

To interpolate a string against an already-merged model:

```java
Model model = …;                     // already merged
ModelInterpolator interpolator = new StringVisitorModelInterpolator();
String raw = "${project.groupId}:${project.artifactId}:${project.version}";
String resolved = interpolator.interpolate( raw, model, Collections.emptyMap() );

System.out.println( resolved );   // e.g. "org.example:my-app:1.2.3"

```

The `interpolate` method uses the same value-source chain Maven applies internally during the build process.

### Inspecting Parent-Child Property Overrides

When debugging inheritance, you can compare values before and after merging:

```java
Model child = …;    // loaded from child pom.xml
Model parent = …;   // loaded from parent pom.xml

System.out.println( "Child property: " + child.getProperties().getProperty("my.prop") );
System.out.println( "Parent property: " + parent.getProperties().getProperty("my.prop") );

```

After the merge, the child's value wins, and any `${my.prop}` placeholder in the child will resolve to the child's definition, not the parent's.

## Summary

- **Model merging** combines parent POMs, imported BOMs, and the super-POM using `MavenModelMerger`, with child values overriding scalars and lists being concatenated.
- **Inheritance assembly** is handled by `DefaultInheritanceAssembler` and `DefaultModelBuilder`, which recursively load the POM hierarchy starting from the specified parent and ending with the super-POM.
- **Interpolation** occurs in two phases: pre-processing (URL normalization, timestamp injection) and resolution via `StringVisitorModelInterpolator`, which uses a regex pattern to find and replace `${...}` placeholders.
- **Value resolution** follows a strict hierarchy: object-based sources (`${project.*}`), prefixed sources (`${env.*}`, `${settings.*}`), system properties, and user-defined POM properties.
- **Key source files** include [`MavenModelMerger.java`](https://github.com/apache/maven/blob/main/MavenModelMerger.java), [`DefaultInheritanceAssembler.java`](https://github.com/apache/maven/blob/main/DefaultInheritanceAssembler.java), [`ModelInterpolator.java`](https://github.com/apache/maven/blob/main/ModelInterpolator.java), and [`StringVisitorModelInterpolator.java`](https://github.com/apache/maven/blob/main/StringVisitorModelInterpolator.java) within the `compat/maven-model-builder` module.

## Frequently Asked Questions

### What is the difference between model merging and interpolation in Maven?

**Model merging** combines multiple POM files (parent, child, imported BOMs, super-POM) into a single effective model by aggregating lists, overriding scalars, and injecting management sections. **Model interpolation** is the subsequent phase that replaces `${...}` placeholders with actual values from properties, environment variables, and the project object itself. Merging occurs first to assemble the complete model, then interpolation resolves dynamic values within that merged structure.

### How does Maven resolve ${project.version} and other object-based properties?

Maven uses `ObjectBasedValueSource` (located in [`compat/maven-model-builder/src/main/java/org/apache/maven/model/interpolation/ObjectBasedValueSource.java`](https://github.com/apache/maven/blob/main/compat/maven-model-builder/src/main/java/org/apache/maven/model/interpolation/ObjectBasedValueSource.java)) to expose fields of the `Model` object as interpolation sources. When the `StringVisitorModelInterpolator` encounters `${project.version}`, it accesses the `getVersion()` method on the model instance. This works for any getter method on the model, including `${project.build.directory}` and `${project.artifactId}`.

### Can I programmatically build an effective POM without running a full Maven build?

Yes. The `DefaultModelBuilder` API allows you to construct an effective POM programmatically by creating a `ModelBuildingRequest`, setting the POM file, and calling `builder.build(request)`. This triggers the same merge and interpolation logic used during `mvn` execution, returning a fully resolved `Model` object without executing lifecycle phases or plugin goals.

### What happens when a property is defined in both parent and child POMs?

During the merge phase, `MavenModelMerger` combines the properties maps using a merge strategy where child values take precedence over parent values. After merging, if the child defines `<my.prop>child-value</my.prop>` and the parent defines `<my.prop>parent-value</my.prop>`, the effective model contains the child's value. Consequently, any reference to `${my.prop}` in either POM will resolve to "child-value" during interpolation.