You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by gn...@apache.org on 2026/06/08 12:49:22 UTC

(maven) branch master updated: [#11767] Fix consumer POM builder failing to resolve BOM imports from settings.xml profile repositories

This is an automated email from the ASF dual-hosted git repository.

gnodet pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven.git


The following commit(s) were added to refs/heads/master by this push:
     new 3656bff80a [#11767] Fix consumer POM builder failing to resolve BOM imports from settings.xml profile repositories
3656bff80a is described below

commit 3656bff80acbbaeb9836fdb8635ba786b119d53c
Author: DavidTavoularis <76...@users.noreply.github.com>
AuthorDate: Mon Jun 8 12:49:13 2026 +0000

    [#11767] Fix consumer POM builder failing to resolve BOM imports from settings.xml profile repositories
    
    DefaultConsumerPomBuilder.buildModel() now passes repositories, profiles, and active profile IDs
    to the ModelBuilderRequest so that BOM imports from non-central repositories (e.g. defined in
    settings.xml profiles) can be resolved during consumer POM transformation.
    
    DefaultModelBuilder.derive() now respects the request's repositories instead of always reusing
    the parent session's, and caches the RepositoryFactory lookup.
    
    Includes unit tests for both fixes and an integration test (gh-11767).
    
    Contributed by DavidTavoularis
    
    Closes #11768
    Fixes #11767
---
 .../impl/DefaultConsumerPomBuilder.java            |  49 +++++++--
 .../impl/ConsumerPomBuilderTest.java               |  63 +++++++++++
 .../maven/impl/model/DefaultModelBuilder.java      |  29 +++++-
 .../maven/impl/model/DefaultModelBuilderTest.java  |  51 +++++++++
 .../resources/poms/factory/simple-standalone.xml   |  22 ++++
 .../MavenITConsumerPomBomFromSettingsRepoTest.java | 115 +++++++++++++++++++++
 .../pom.xml                                        |  62 +++++++++++
 .../apache/maven/its/cpbom/lib-a/2.0/lib-a-2.0.jar | Bin
 .../apache/maven/its/cpbom/lib-a/2.0/lib-a-2.0.pom |   9 ++
 .../maven/its/cpbom/the-bom/1.0/the-bom-1.0.pom    |  19 ++++
 .../settings-template.xml                          |  41 ++++++++
 11 files changed, 451 insertions(+), 9 deletions(-)

diff --git a/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java b/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java
index 6af477a690..b5203b282b 100644
--- a/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java
+++ b/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java
@@ -48,7 +48,9 @@
 import org.apache.maven.api.services.ModelBuilderResult;
 import org.apache.maven.api.services.ModelSource;
 import org.apache.maven.api.services.model.LifecycleBindingsInjector;
+import org.apache.maven.execution.MavenExecutionRequest;
 import org.apache.maven.impl.InternalSession;
+import org.apache.maven.internal.impl.InternalMavenSession;
 import org.apache.maven.model.v4.MavenModelVersion;
 import org.apache.maven.project.MavenProject;
 import org.apache.maven.project.SourceQueries;
@@ -182,14 +184,14 @@ on the <project> element (Maven 4 consumers only)
 
     protected Model buildPom(RepositorySystemSession session, MavenProject project, ModelSource src)
             throws ModelBuilderException {
-        ModelBuilderResult result = buildModel(session, src);
+        ModelBuilderResult result = buildModel(session, project, src);
         Model model = result.getRawModel();
         return transformPom(model, project);
     }
 
     protected Model buildBomWithoutFlatten(RepositorySystemSession session, MavenProject project, ModelSource src)
             throws ModelBuilderException {
-        ModelBuilderResult result = buildModel(session, src);
+        ModelBuilderResult result = buildModel(session, project, src);
         Model model = result.getRawModel();
         // For BOMs without flattening, we just need to transform the packaging from "bom" to "pom"
         // but keep everything else from the raw model (including unresolved versions)
@@ -198,20 +200,21 @@ protected Model buildBomWithoutFlatten(RepositorySystemSession session, MavenPro
 
     protected Model buildBom(RepositorySystemSession session, MavenProject project, ModelSource src)
             throws ModelBuilderException {
-        ModelBuilderResult result = buildModel(session, src);
+        ModelBuilderResult result = buildModel(session, project, src);
         Model model = result.getEffectiveModel();
         return transformBom(model, project);
     }
 
     protected Model buildNonPom(RepositorySystemSession session, MavenProject project, ModelSource src)
             throws ModelBuilderException {
-        Model model = buildEffectiveModel(session, src);
+        Model model = buildEffectiveModel(session, project, src);
         return transformNonPom(model, project);
     }
 
-    private Model buildEffectiveModel(RepositorySystemSession session, ModelSource src) throws ModelBuilderException {
+    private Model buildEffectiveModel(RepositorySystemSession session, MavenProject project, ModelSource src)
+            throws ModelBuilderException {
         InternalSession iSession = InternalSession.from(session);
-        ModelBuilderResult result = buildModel(session, src);
+        ModelBuilderResult result = buildModel(session, project, src);
         Model model = result.getEffectiveModel();
         boolean removeUnusedManagedDeps =
                 Features.consumerPomRemoveUnusedManagedDependencies(session.getConfigProperties());
@@ -312,7 +315,7 @@ private static String getDependencyKey(Dependency dependency) {
                 + (dependency.getClassifier() != null ? dependency.getClassifier() : "");
     }
 
-    private ModelBuilderResult buildModel(RepositorySystemSession session, ModelSource src)
+    private ModelBuilderResult buildModel(RepositorySystemSession session, MavenProject project, ModelSource src)
             throws ModelBuilderException {
         InternalSession iSession = InternalSession.from(session);
         ModelBuilderRequest.ModelBuilderRequestBuilder request = ModelBuilderRequest.builder();
@@ -323,6 +326,38 @@ private ModelBuilderResult buildModel(RepositorySystemSession session, ModelSour
         request.systemProperties(iSession.getSystemProperties());
         request.userProperties(iSession.getUserProperties());
         request.lifecycleBindingsInjector(lifecycleBindingsInjector::injectLifecycleBindings);
+        // Pass remote repositories so that the model builder can resolve BOM imports
+        // from non-central repositories (e.g., repositories defined in settings.xml profiles).
+        // Prefer project repositories, but fall back to session repositories if the project's
+        // remote repository list is not populated (e.g., during install/deploy phases).
+        if (project != null
+                && project.getRemoteProjectRepositories() != null
+                && !project.getRemoteProjectRepositories().isEmpty()) {
+            request.repositories(project.getRemoteProjectRepositories().stream()
+                    .map(iSession::getRemoteRepository)
+                    .toList());
+        } else {
+            request.repositories(iSession.getRemoteRepositories());
+        }
+        // Pass profiles and active/inactive profile IDs from the execution request
+        // so that settings.xml profiles are applied during consumer POM model building.
+        if (iSession instanceof InternalMavenSession mavenSession) {
+            MavenExecutionRequest executionRequest =
+                    mavenSession.getMavenSession().getRequest();
+            if (executionRequest.getProfiles() != null) {
+                request.profiles(executionRequest.getProfiles().stream()
+                        .map(org.apache.maven.model.Profile::getDelegate)
+                        .toList());
+            }
+            request.activeProfileIds(executionRequest.getActiveProfiles());
+            request.inactiveProfileIds(executionRequest.getInactiveProfiles());
+        } else {
+            LOGGER.debug(
+                    "Session is not an InternalMavenSession ({}); settings.xml profiles will not be "
+                            + "passed to the consumer POM model builder. BOM imports from repositories "
+                            + "defined only in settings.xml profiles may fail to resolve.",
+                    iSession.getClass().getName());
+        }
         ModelBuilder.ModelBuilderSession mbSession =
                 iSession.getData().get(SessionData.key(ModelBuilder.ModelBuilderSession.class));
         return mbSession.build(request.build());
diff --git a/impl/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilderTest.java b/impl/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilderTest.java
index c0491441d4..76a9752f55 100644
--- a/impl/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilderTest.java
+++ b/impl/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilderTest.java
@@ -49,7 +49,9 @@
 import org.apache.maven.internal.impl.InternalMavenSession;
 import org.apache.maven.internal.transformation.AbstractRepositoryTestCase;
 import org.apache.maven.project.MavenProject;
+import org.eclipse.aether.repository.RemoteRepository;
 import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
 
 import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -213,4 +215,65 @@ void testScmInheritance() throws Exception {
         assertNull(transformed.getScm().getChildScmUrlInheritAppendPath());
         assertNull(transformed.getScm().getChildScmDeveloperConnectionInheritAppendPath());
     }
+
+    /**
+     * Verifies that the consumer POM builder passes the project's remote repositories
+     * to the model builder request, so that BOM imports from non-central repositories
+     * (e.g. repositories defined in settings.xml profiles) can be resolved.
+     * <p>
+     * Without the fix in {@code DefaultConsumerPomBuilder.buildModel()}, the
+     * {@code ModelBuilderRequest} is constructed without repositories, profiles, or
+     * active profile IDs. This causes the model builder to only see Maven Central
+     * when resolving BOM imports, leading to "Non-resolvable import POM" failures
+     * for artifacts hosted in private/corporate repositories.
+     */
+    @Test
+    void testConsumerPomPassesProjectRepositoriesToModelBuilder() throws Exception {
+        setRootDirectory("trivial");
+        Path file = Paths.get("src/test/resources/consumer/trivial/child/pom.xml");
+
+        MavenProject project = getEffectiveModel(file);
+
+        // Add a custom remote repository to the project, simulating a repository
+        // injected from settings.xml profile (e.g. a corporate/private repository)
+        RemoteRepository customRepo =
+                new RemoteRepository.Builder("custom-repo", "default", "https://repo.example.com/maven2").build();
+        project.getRemoteProjectRepositories().add(customRepo);
+
+        // Spy on the ModelBuilderSession to capture the ModelBuilderRequest
+        ModelBuilder.ModelBuilderSession originalMbs = modelBuilder.newSession();
+        ModelBuilder.ModelBuilderSession spyMbs = Mockito.spy(originalMbs);
+        InternalSession.from(session).getData().set(SessionData.key(ModelBuilder.ModelBuilderSession.class), spyMbs);
+
+        // Build the consumer POM
+        builder.build(session, project, Sources.buildSource(file));
+
+        // Capture the ModelBuilderRequest passed to the ModelBuilderSession
+        ArgumentCaptor<ModelBuilderRequest> requestCaptor = ArgumentCaptor.forClass(ModelBuilderRequest.class);
+        Mockito.verify(spyMbs, Mockito.atLeastOnce()).build(requestCaptor.capture());
+
+        // Find the BUILD_CONSUMER request (there may be multiple calls)
+        ModelBuilderRequest consumerRequest = requestCaptor.getAllValues().stream()
+                .filter(r -> r.getRequestType() == ModelBuilderRequest.RequestType.BUILD_CONSUMER)
+                .findFirst()
+                .orElse(null);
+
+        assertNotNull(consumerRequest, "Expected a BUILD_CONSUMER request to be made");
+
+        // Verify that repositories were passed to the request.
+        // Without the fix, getRepositories() returns null because buildModel() never sets them.
+        assertNotNull(
+                consumerRequest.getRepositories(),
+                "Consumer POM model builder request should include repositories from the project. "
+                        + "Without this, BOM imports from non-central repositories (e.g. settings.xml profiles) "
+                        + "cannot be resolved, causing 'Non-resolvable import POM' errors.");
+        assertFalse(
+                consumerRequest.getRepositories().isEmpty(),
+                "Consumer POM model builder request should have at least one repository");
+
+        // Verify the custom repository is included
+        boolean hasCustomRepo =
+                consumerRequest.getRepositories().stream().anyMatch(r -> "custom-repo".equals(r.getId()));
+        assertTrue(hasCustomRepo, "Consumer POM model builder request should include the project's custom repository");
+    }
 }
diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java
index 5e917c76fd..22d6c26705 100644
--- a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java
+++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java
@@ -277,6 +277,14 @@ protected class ModelBuilderSessionState implements ModelProblemCollector {
         List<RemoteRepository> externalRepositories;
         List<RemoteRepository> repositories;
 
+        List<RemoteRepository> getRepositories() {
+            return repositories;
+        }
+
+        List<RemoteRepository> getExternalRepositories() {
+            return externalRepositories;
+        }
+
         // Cycle detection chain shared across all derived sessions
         // Contains both GAV coordinates (groupId:artifactId:version) and file paths
         final Set<String> parentChain;
@@ -345,6 +353,23 @@ ModelBuilderSessionState derive(ModelBuilderRequest request, DefaultModelBuilder
             }
             // Create a new parentChain for each derived session to prevent cycle detection issues
             // The parentChain now contains both GAV coordinates and file paths
+            // For BUILD_CONSUMER requests, use the request's explicit repositories so that
+            // BOM imports can be resolved from non-central repos (e.g., settings.xml profiles).
+            // This is scoped to BUILD_CONSUMER to avoid unintended side effects on other
+            // derived sessions (e.g., parent POM resolution during project builds).
+            List<RemoteRepository> derivedExtRepos = externalRepositories;
+            List<RemoteRepository> derivedRepos = repositories;
+            if (request.getRequestType() == ModelBuilderRequest.RequestType.BUILD_CONSUMER
+                    && request.getRepositories() != null
+                    && !request.getRepositories().isEmpty()) {
+                derivedExtRepos = List.copyOf(request.getRepositories());
+                if (pomRepositories.isEmpty()) {
+                    derivedRepos = derivedExtRepos;
+                } else {
+                    RepositoryFactory repositoryFactory = session.getService(RepositoryFactory.class);
+                    derivedRepos = repositoryFactory.aggregate(session, pomRepositories, derivedExtRepos, false);
+                }
+            }
             return new ModelBuilderSessionState(
                     session,
                     request,
@@ -352,8 +377,8 @@ ModelBuilderSessionState derive(ModelBuilderRequest request, DefaultModelBuilder
                     dag,
                     mappedSources,
                     pomRepositories,
-                    externalRepositories,
-                    repositories,
+                    derivedExtRepos,
+                    derivedRepos,
                     new LinkedHashSet<>());
         }
 
diff --git a/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelBuilderTest.java b/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelBuilderTest.java
index a0133344ae..4b5ba3fe95 100644
--- a/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelBuilderTest.java
+++ b/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelBuilderTest.java
@@ -41,6 +41,7 @@
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /**
  *
@@ -321,6 +322,56 @@ public void testMissingDependencyGroupIdInference() throws Exception {
         }
     }
 
+    /**
+     * Verifies that when a BUILD_CONSUMER derived session is created with explicit
+     * repositories, those repositories are propagated to the derived session's
+     * {@code repositories} and {@code externalRepositories}.
+     * <p>
+     * This is critical for consumer POM building: the consumer POM builder reuses the
+     * existing {@code ModelBuilderSession} and calls {@code build()} with a request
+     * containing the project's repositories (which may include non-central repos from
+     * settings.xml profiles). Without this, BOM imports from non-central repositories fail.
+     */
+    @Test
+    public void testBuildConsumerWithExplicitRepositories() {
+        // First build to create the mainSession (simulates project build phase)
+        ModelBuilderRequest firstRequest = ModelBuilderRequest.builder()
+                .session(session)
+                .requestType(ModelBuilderRequest.RequestType.BUILD_PROJECT)
+                .source(Sources.buildSource(getPom("simple-standalone")))
+                .build();
+        ModelBuilder.ModelBuilderSession mbs = builder.newSession();
+        mbs.build(firstRequest);
+
+        // Access the mainSession (package-private) to call derive() and verify state
+        DefaultModelBuilder.ModelBuilderSessionState mainState =
+                ((DefaultModelBuilder.ModelBuilderSessionImpl) mbs).mainSession;
+
+        // Verify the main session only has central
+        assertEquals(1, mainState.getRepositories().size());
+        assertEquals("central", mainState.getRepositories().get(0).getId());
+
+        // Derive a BUILD_CONSUMER session with explicit repositories
+        RemoteRepository customRepo = session.createRemoteRepository("custom-repo", "https://repo.example.com/maven2");
+        ModelBuilderRequest consumerRequest = ModelBuilderRequest.builder()
+                .session(session)
+                .requestType(ModelBuilderRequest.RequestType.BUILD_CONSUMER)
+                .source(Sources.buildSource(getPom("simple-standalone")))
+                .repositories(List.of(
+                        customRepo, session.createRemoteRepository("central", "https://repo.maven.apache.org/maven2")))
+                .build();
+
+        DefaultModelBuilder.ModelBuilderSessionState derived = mainState.derive(consumerRequest);
+
+        // Verify the derived session includes the custom repository
+        assertTrue(
+                derived.getRepositories().stream().anyMatch(r -> "custom-repo".equals(r.getId())),
+                "Derived session repositories should include the custom repo from the request");
+        assertTrue(
+                derived.getExternalRepositories().stream().anyMatch(r -> "custom-repo".equals(r.getId())),
+                "Derived session externalRepositories should include the custom repo from the request");
+    }
+
     private Path getPom(String name) {
         return Paths.get("src/test/resources/poms/factory/" + name + ".xml").toAbsolutePath();
     }
diff --git a/impl/maven-impl/src/test/resources/poms/factory/simple-standalone.xml b/impl/maven-impl/src/test/resources/poms/factory/simple-standalone.xml
new file mode 100644
index 0000000000..1ccfe631c1
--- /dev/null
+++ b/impl/maven-impl/src/test/resources/poms/factory/simple-standalone.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.1.0">
+    <groupId>org.apache.maven.tests</groupId>
+    <artifactId>simple-standalone</artifactId>
+    <version>1.0</version>
+</project>
diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITConsumerPomBomFromSettingsRepoTest.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITConsumerPomBomFromSettingsRepoTest.java
new file mode 100644
index 0000000000..22f9139109
--- /dev/null
+++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITConsumerPomBomFromSettingsRepoTest.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.it;
+
+import java.io.Reader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.apache.maven.api.model.Model;
+import org.apache.maven.model.v4.MavenStaxReader;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Verifies that the consumer POM builder can resolve BOM imports from repositories
+ * defined only in settings.xml profiles (not in the project POM itself).
+ * <p>
+ * This is a regression test for a bug where {@code DefaultConsumerPomBuilder.buildModel()}
+ * did not pass repositories, profiles, or active profile IDs to the {@code ModelBuilderRequest},
+ * and {@code DefaultModelBuilder.derive()} ignored the request's repositories when creating
+ * derived sessions. This caused "Non-resolvable import POM" failures during the install phase
+ * for artifacts hosted in private/corporate repositories configured via settings.xml.
+ *
+ * @since 4.0.0
+ */
+class MavenITConsumerPomBomFromSettingsRepoTest extends AbstractMavenIntegrationTestCase {
+
+    /**
+     * Verifies that consumer POM flattening works when the BOM is only available
+     * from a repository defined in a settings.xml profile.
+     * <p>
+     * Without the fix, this test fails with:
+     * <pre>
+     * Non-resolvable import POM: Could not find artifact
+     * org.apache.maven.its.cpbom:the-bom:pom:1.0 in central
+     * </pre>
+     */
+    @Test
+    void testConsumerPomWithBomFromSettingsProfileRepo() throws Exception {
+        Path basedir = extractResources("/gh-11767-consumer-pom-bom-from-settings-repo")
+                .toPath()
+                .toAbsolutePath();
+
+        Verifier verifier = newVerifier(basedir.toString());
+        verifier.deleteArtifacts("org.apache.maven.its.cpbom");
+
+        // Apply settings template with the custom repository URL
+        verifier.filterFile("settings-template.xml", "settings.xml");
+        verifier.addCliArgument("--settings");
+        verifier.addCliArgument("settings.xml");
+
+        // Enable consumer POM flattening to trigger full BOM resolution
+        // during the install phase consumer POM transformation
+        verifier.addCliArgument("-Dmaven.consumer.pom.flatten=true");
+        verifier.addCliArgument("install");
+        verifier.execute();
+        verifier.verifyErrorFreeLog();
+
+        // Verify the consumer POM was generated
+        Path consumerPom = basedir.resolve(Path.of(
+                "target",
+                "project-local-repo",
+                "org.apache.maven.its.cpbom",
+                "consumer-pom-bom-settings-repo",
+                "1.0",
+                "consumer-pom-bom-settings-repo-1.0-consumer.pom"));
+        assertTrue(Files.exists(consumerPom), "consumer POM not found at " + consumerPom);
+
+        // Read and validate the consumer POM content
+        Model consumerPomModel;
+        try (Reader r = Files.newBufferedReader(consumerPom)) {
+            consumerPomModel = new MavenStaxReader().read(r);
+        }
+
+        // The consumer POM should have the dependency with the version resolved from the BOM
+        assertNotNull(consumerPomModel.getDependencies(), "Consumer POM should have dependencies");
+        assertFalse(consumerPomModel.getDependencies().isEmpty(), "Consumer POM should have at least one dependency");
+
+        boolean hasLibA = consumerPomModel.getDependencies().stream()
+                .anyMatch(d -> "lib-a".equals(d.getArtifactId())
+                        && "org.apache.maven.its.cpbom".equals(d.getGroupId())
+                        && "2.0".equals(d.getVersion()));
+        assertTrue(
+                hasLibA,
+                "Consumer POM should contain lib-a with version 2.0 resolved from the BOM. "
+                        + "Actual dependencies: " + consumerPomModel.getDependencies());
+
+        // The BOM import should NOT appear in the consumer POM (it's been flattened)
+        if (consumerPomModel.getDependencyManagement() != null) {
+            boolean hasBomImport = consumerPomModel.getDependencyManagement().getDependencies().stream()
+                    .anyMatch(d -> "the-bom".equals(d.getArtifactId()) && "import".equals(d.getScope()));
+            assertFalse(hasBomImport, "Consumer POM should not contain the BOM import after flattening");
+        }
+    }
+}
diff --git a/its/core-it-suite/src/test/resources/gh-11767-consumer-pom-bom-from-settings-repo/pom.xml b/its/core-it-suite/src/test/resources/gh-11767-consumer-pom-bom-from-settings-repo/pom.xml
new file mode 100644
index 0000000000..4f92898f82
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/gh-11767-consumer-pom-bom-from-settings-repo/pom.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0">
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>org.apache.maven.its.cpbom</groupId>
+  <artifactId>consumer-pom-bom-settings-repo</artifactId>
+  <version>1.0</version>
+  <packaging>jar</packaging>
+
+  <name>Maven Integration Test :: Consumer POM BOM from Settings Repo</name>
+  <description>Verifies that the consumer POM builder can resolve BOM imports from
+    repositories defined only in settings.xml profiles (not in the project POM).</description>
+
+  <dependencyManagement>
+    <dependencies>
+      <dependency>
+        <groupId>org.apache.maven.its.cpbom</groupId>
+        <artifactId>the-bom</artifactId>
+        <version>1.0</version>
+        <type>pom</type>
+        <scope>import</scope>
+      </dependency>
+    </dependencies>
+  </dependencyManagement>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.maven.its.cpbom</groupId>
+      <artifactId>lib-a</artifactId>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <pluginManagement>
+      <plugins>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-surefire-plugin</artifactId>
+          <version>3.5.4</version>
+        </plugin>
+      </plugins>
+    </pluginManagement>
+  </build>
+</project>
diff --git a/its/core-it-suite/src/test/resources/gh-11767-consumer-pom-bom-from-settings-repo/repo/org/apache/maven/its/cpbom/lib-a/2.0/lib-a-2.0.jar b/its/core-it-suite/src/test/resources/gh-11767-consumer-pom-bom-from-settings-repo/repo/org/apache/maven/its/cpbom/lib-a/2.0/lib-a-2.0.jar
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/its/core-it-suite/src/test/resources/gh-11767-consumer-pom-bom-from-settings-repo/repo/org/apache/maven/its/cpbom/lib-a/2.0/lib-a-2.0.pom b/its/core-it-suite/src/test/resources/gh-11767-consumer-pom-bom-from-settings-repo/repo/org/apache/maven/its/cpbom/lib-a/2.0/lib-a-2.0.pom
new file mode 100644
index 0000000000..3151f79f11
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/gh-11767-consumer-pom-bom-from-settings-repo/repo/org/apache/maven/its/cpbom/lib-a/2.0/lib-a-2.0.pom
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0">
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>org.apache.maven.its.cpbom</groupId>
+  <artifactId>lib-a</artifactId>
+  <version>2.0</version>
+  <packaging>jar</packaging>
+</project>
diff --git a/its/core-it-suite/src/test/resources/gh-11767-consumer-pom-bom-from-settings-repo/repo/org/apache/maven/its/cpbom/the-bom/1.0/the-bom-1.0.pom b/its/core-it-suite/src/test/resources/gh-11767-consumer-pom-bom-from-settings-repo/repo/org/apache/maven/its/cpbom/the-bom/1.0/the-bom-1.0.pom
new file mode 100644
index 0000000000..e1ed38ccc0
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/gh-11767-consumer-pom-bom-from-settings-repo/repo/org/apache/maven/its/cpbom/the-bom/1.0/the-bom-1.0.pom
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0">
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>org.apache.maven.its.cpbom</groupId>
+  <artifactId>the-bom</artifactId>
+  <version>1.0</version>
+  <packaging>pom</packaging>
+
+  <dependencyManagement>
+    <dependencies>
+      <dependency>
+        <groupId>org.apache.maven.its.cpbom</groupId>
+        <artifactId>lib-a</artifactId>
+        <version>2.0</version>
+      </dependency>
+    </dependencies>
+  </dependencyManagement>
+</project>
diff --git a/its/core-it-suite/src/test/resources/gh-11767-consumer-pom-bom-from-settings-repo/settings-template.xml b/its/core-it-suite/src/test/resources/gh-11767-consumer-pom-bom-from-settings-repo/settings-template.xml
new file mode 100644
index 0000000000..e8a6f6d85a
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/gh-11767-consumer-pom-bom-from-settings-repo/settings-template.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0">
+  <profiles>
+    <profile>
+      <id>custom-repo</id>
+      <repositories>
+        <repository>
+          <id>custom-repo</id>
+          <url>@baseurl@/repo</url>
+          <releases>
+            <checksumPolicy>ignore</checksumPolicy>
+          </releases>
+          <snapshots>
+            <enabled>false</enabled>
+          </snapshots>
+        </repository>
+      </repositories>
+    </profile>
+  </profiles>
+  <activeProfiles>
+    <activeProfile>custom-repo</activeProfile>
+  </activeProfiles>
+</settings>