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 4ff22094bd [#12301] Fix StackOverflowError with internal parent and
CI-friendly revision (#12323)
4ff22094bd is described below
commit 4ff22094bd645ef4fea671524fa8c81eb30e847b
Author: Guillaume Nodet <[email protected]>
AuthorDate: Fri Jun 19 11:00:54 2026 +0200
[#12301] Fix StackOverflowError with internal parent and CI-friendly
revision (#12323)
* [#12301] Fix StackOverflowError with internal parent and CI-friendly
revision
Re-forward-port the GH-12301 fix from maven-4.0.x to master, using
ThreadLocal<Set<Path>> instead of the shared ConcurrentHashMap.newKeySet()
that caused a race condition with PhasingExecutor parallel model loading.
The original forward-port (#12317) used a shared set to track which POM
files
were being read by doReadFileModel(). This prevented the StackOverflowError
from GH-12301, but caused false cycle detection when parallel threads in
loadFilePom() read the same model simultaneously — breaking
ConsumerPomBuilderTest.testSimpleConsumer.
The fix uses ThreadLocal so each thread tracks only its own call stack,
preventing actual recursive cycles within the same thread without blocking
legitimate parallel reads across threads.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* Refactor: pass activeModelReads through call stack instead of ThreadLocal
Replace ThreadLocal<Set<Path>> with a stack-passed Set<Path> parameter
threaded through readFileModel() → doReadFileModel() →
getEnhancedProperties().
Each readFileModel() entry point creates a fresh HashSet, avoiding any
shared mutable state between concurrent model-loading threads.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
---
.../maven/impl/model/DefaultModelBuilder.java | 375 +++++++++++----------
...301StackOverflowInternalParentRevisionTest.java | 44 +++
.../.mvn/.gitkeep | 0
.../parent/pom.xml | 29 ++
.../gh-12301-stackoverflow-internal-parent/pom.xml | 38 +++
5 files changed, 308 insertions(+), 178 deletions(-)
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 7a5b84b7ee..28a917c948 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
@@ -686,7 +686,7 @@ String replaceCiFriendlyVersion(Map<String, String>
properties, String version)
* are available for CI-friendly version processing and repository URL
interpolation.
* It also includes directory-related properties that may be needed
during profile activation.
*/
- private Map<String, String> getEnhancedProperties(Model model, Path
rootDirectory) {
+ private Map<String, String> getEnhancedProperties(Model model, Path
rootDirectory, Set<Path> activeModelReads) {
Map<String, String> properties = new HashMap<>();
// Add directory-specific properties first, as they may be needed
for profile activation
@@ -712,10 +712,14 @@ private Map<String, String> getEnhancedProperties(Model
model, Path rootDirector
if (rootModelPath != null) {
// Check if the root model path is within the root
directory to prevent infinite loops
// This can happen when a .mvn directory exists in a
subdirectory and parent inference
- // tries to read models above the discovered root directory
- if (isParentWithinRootDirectory(rootModelPath,
rootDirectory)) {
+ // tries to read models above the discovered root
directory.
+ // Also skip if the root model is already being read in an
outer call frame
+ // to prevent StackOverflowError when a project has an
internal parent in a
+ // subdirectory with CI-friendly ${revision} and a .mvn/
root marker (GH-12301).
+ if (isParentWithinRootDirectory(rootModelPath,
rootDirectory)
+ &&
!activeModelReads.contains(rootModelPath.normalize())) {
Model rootModel =
-
derive(Sources.buildSource(rootModelPath)).readFileModel();
+
derive(Sources.buildSource(rootModelPath)).readFileModel(activeModelReads);
properties.putAll(getPropertiesWithProfiles(rootModel,
properties));
}
}
@@ -1528,48 +1532,42 @@ private List<Profile> getActiveProfiles(
}
Model readFileModel() throws ModelBuilderException {
- Model model = cache(request.getSource(), FILE,
this::doReadFileModel);
+ return readFileModel(new HashSet<>());
+ }
+
+ Model readFileModel(Set<Path> activeModelReads) throws
ModelBuilderException {
+ Model model = cache(request.getSource(), FILE, () ->
doReadFileModel(activeModelReads));
// set the file model in the result outside the cache
result.setFileModel(model);
return model;
}
@SuppressWarnings("checkstyle:methodlength")
- Model doReadFileModel() throws ModelBuilderException {
+ Model doReadFileModel(Set<Path> activeModelReads) throws
ModelBuilderException {
ModelSource modelSource = request.getSource();
Model model;
Path rootDirectory;
boolean rootDirectoryFromSession = false;
setSource(modelSource.getLocation());
logger.debug("Reading file model from " +
modelSource.getLocation());
+ Path sourcePath = modelSource.getPath();
+ Path normalizedPath = sourcePath != null ? sourcePath.normalize()
: null;
+ boolean trackRead = normalizedPath != null &&
activeModelReads.add(normalizedPath);
try {
- boolean strict = isBuildRequest();
try {
- rootDirectory = request.getSession().getRootDirectory();
- rootDirectoryFromSession = true;
- } catch (IllegalStateException ignore) {
- rootDirectory = modelSource.getPath();
- while (rootDirectory != null &&
!Files.isDirectory(rootDirectory)) {
- rootDirectory = rootDirectory.getParent();
- }
- }
- try (InputStream is = modelSource.openStream()) {
- model = modelProcessor.read(XmlReaderRequest.builder()
- .strict(strict)
- .location(modelSource.getLocation())
- .modelId(modelSource.getModelId())
- .path(modelSource.getPath())
- .rootDirectory(rootDirectory)
- .inputStream(is)
- .transformer(new InterningTransformer(session))
- .build());
- } catch (XmlReaderException e) {
- if (!strict) {
- throw e;
+ boolean strict = isBuildRequest();
+ try {
+ rootDirectory =
request.getSession().getRootDirectory();
+ rootDirectoryFromSession = true;
+ } catch (IllegalStateException ignore) {
+ rootDirectory = modelSource.getPath();
+ while (rootDirectory != null &&
!Files.isDirectory(rootDirectory)) {
+ rootDirectory = rootDirectory.getParent();
+ }
}
try (InputStream is = modelSource.openStream()) {
model = modelProcessor.read(XmlReaderRequest.builder()
- .strict(false)
+ .strict(strict)
.location(modelSource.getLocation())
.modelId(modelSource.getModelId())
.path(modelSource.getPath())
@@ -1577,180 +1575,201 @@ Model doReadFileModel() throws ModelBuilderException {
.inputStream(is)
.transformer(new InterningTransformer(session))
.build());
- } catch (XmlReaderException ne) {
- // still unreadable even in non-strict mode, rethrow
original error
- throw e;
- }
+ } catch (XmlReaderException e) {
+ if (!strict) {
+ throw e;
+ }
+ try (InputStream is = modelSource.openStream()) {
+ model =
modelProcessor.read(XmlReaderRequest.builder()
+ .strict(false)
+ .location(modelSource.getLocation())
+ .modelId(modelSource.getModelId())
+ .path(modelSource.getPath())
+ .rootDirectory(rootDirectory)
+ .inputStream(is)
+ .transformer(new
InterningTransformer(session))
+ .build());
+ } catch (XmlReaderException ne) {
+ // still unreadable even in non-strict mode,
rethrow original error
+ throw e;
+ }
+ add(
+ Severity.ERROR,
+ Version.V20,
+ "Malformed POM " + modelSource.getLocation() +
": " + e.getMessage(),
+ e);
+ }
+ } catch (XmlReaderException e) {
add(
- Severity.ERROR,
- Version.V20,
- "Malformed POM " + modelSource.getLocation() + ":
" + e.getMessage(),
+ Severity.FATAL,
+ Version.BASE,
+ "Non-parseable POM " + modelSource.getLocation() +
": " + e.getMessage(),
e);
- }
- } catch (XmlReaderException e) {
- add(
- Severity.FATAL,
- Version.BASE,
- "Non-parseable POM " + modelSource.getLocation() + ":
" + e.getMessage(),
- e);
- throw newModelBuilderException();
- } catch (IOException e) {
- String msg = e.getMessage();
- if (msg == null || msg.isEmpty()) {
- // NOTE: There's java.nio.charset.MalformedInputException
and sun.io.MalformedInputException
- if
(e.getClass().getName().endsWith("MalformedInputException")) {
- msg = "Some input bytes do not match the file
encoding.";
- } else {
- msg = e.getClass().getSimpleName();
+ throw newModelBuilderException();
+ } catch (IOException e) {
+ String msg = e.getMessage();
+ if (msg == null || msg.isEmpty()) {
+ // NOTE: There's
java.nio.charset.MalformedInputException and sun.io.MalformedInputException
+ if
(e.getClass().getName().endsWith("MalformedInputException")) {
+ msg = "Some input bytes do not match the file
encoding.";
+ } else {
+ msg = e.getClass().getSimpleName();
+ }
}
+ add(Severity.FATAL, Version.BASE, "Non-readable POM " +
modelSource.getLocation() + ": " + msg, e);
+ throw newModelBuilderException();
}
- add(Severity.FATAL, Version.BASE, "Non-readable POM " +
modelSource.getLocation() + ": " + msg, e);
- throw newModelBuilderException();
- }
- if (model.getModelVersion() == null) {
- String namespace = model.getNamespaceUri();
- if (namespace != null &&
namespace.startsWith(NAMESPACE_PREFIX)) {
- model =
model.withModelVersion(namespace.substring(NAMESPACE_PREFIX.length()));
+ if (model.getModelVersion() == null) {
+ String namespace = model.getNamespaceUri();
+ if (namespace != null &&
namespace.startsWith(NAMESPACE_PREFIX)) {
+ model =
model.withModelVersion(namespace.substring(NAMESPACE_PREFIX.length()));
+ }
}
- }
- if (isBuildRequest()) {
- model = model.withPomFile(modelSource.getPath());
-
- Parent parent = model.getParent();
- if (parent != null) {
- String groupId = parent.getGroupId();
- String artifactId = parent.getArtifactId();
- String version = parent.getVersion();
- String path = parent.getRelativePath();
- boolean versionContainsExpression = version != null &&
version.contains("${");
- if ((groupId == null || artifactId == null || version ==
null || versionContainsExpression)
- && (path == null || !path.isEmpty())) {
- Path pomFile = model.getPomFile();
- Path relativePath = Paths.get(path != null ? path :
"..");
- Path pomPath =
pomFile.resolveSibling(relativePath).normalize();
- if (Files.isDirectory(pomPath)) {
- pomPath =
modelProcessor.locateExistingPom(pomPath);
- }
- if (pomPath != null && Files.isRegularFile(pomPath)) {
- if (rootDirectoryFromSession &&
!isParentWithinRootDirectory(pomPath, rootDirectory)) {
- add(
- Severity.FATAL,
- Version.BASE,
- "Parent POM " + pomPath + " is located
above the root directory "
- + rootDirectory
- + ". This setup is invalid
when a .mvn directory exists in a subdirectory.",
- parent.getLocation("relativePath"));
- throw newModelBuilderException();
+ if (isBuildRequest()) {
+ model = model.withPomFile(modelSource.getPath());
+
+ Parent parent = model.getParent();
+ if (parent != null) {
+ String groupId = parent.getGroupId();
+ String artifactId = parent.getArtifactId();
+ String version = parent.getVersion();
+ String path = parent.getRelativePath();
+ boolean versionContainsExpression = version != null &&
version.contains("${");
+ if ((groupId == null || artifactId == null || version
== null || versionContainsExpression)
+ && (path == null || !path.isEmpty())) {
+ Path pomFile = model.getPomFile();
+ Path relativePath = Paths.get(path != null ? path
: "..");
+ Path pomPath =
pomFile.resolveSibling(relativePath).normalize();
+ if (Files.isDirectory(pomPath)) {
+ pomPath =
modelProcessor.locateExistingPom(pomPath);
}
+ if (pomPath != null &&
Files.isRegularFile(pomPath)) {
+ if (rootDirectoryFromSession &&
!isParentWithinRootDirectory(pomPath, rootDirectory)) {
+ add(
+ Severity.FATAL,
+ Version.BASE,
+ "Parent POM " + pomPath + " is
located above the root directory "
+ + rootDirectory
+ + ". This setup is invalid
when a .mvn directory exists in a subdirectory.",
+
parent.getLocation("relativePath"));
+ throw newModelBuilderException();
+ }
- Model parentModel =
-
derive(Sources.buildSource(pomPath)).readFileModel();
- String parentGroupId = getGroupId(parentModel);
- String parentArtifactId =
parentModel.getArtifactId();
- String parentVersion = getVersion(parentModel);
- if ((groupId == null ||
groupId.equals(parentGroupId))
- && (artifactId == null ||
artifactId.equals(parentArtifactId))
- && (version == null
- || version.equals(parentVersion)
- || versionContainsExpression)) {
- model = model.withParent(parent.with()
- .groupId(parentGroupId)
- .artifactId(parentArtifactId)
- .version(parentVersion)
- .build());
+ Model parentModel =
+
derive(Sources.buildSource(pomPath)).readFileModel(activeModelReads);
+ String parentGroupId = getGroupId(parentModel);
+ String parentArtifactId =
parentModel.getArtifactId();
+ String parentVersion = getVersion(parentModel);
+ if ((groupId == null ||
groupId.equals(parentGroupId))
+ && (artifactId == null ||
artifactId.equals(parentArtifactId))
+ && (version == null
+ ||
version.equals(parentVersion)
+ || versionContainsExpression))
{
+ model = model.withParent(parent.with()
+ .groupId(parentGroupId)
+ .artifactId(parentArtifactId)
+ .version(parentVersion)
+ .build());
+ } else {
+ mismatchRelativePathAndGA(model, parent,
parentGroupId, parentArtifactId);
+ }
} else {
- mismatchRelativePathAndGA(model, parent,
parentGroupId, parentArtifactId);
- }
- } else {
- if
(!MODEL_VERSION_4_0_0.equals(model.getModelVersion()) && path != null) {
- wrongParentRelativePath(model);
+ if
(!MODEL_VERSION_4_0_0.equals(model.getModelVersion()) && path != null) {
+ wrongParentRelativePath(model);
+ }
}
}
}
- }
- // subprojects discovery
- if (!hasSubprojectsDefined(model)
- // only discover subprojects if POM > 4.0.0
- && !MODEL_VERSION_4_0_0.equals(model.getModelVersion())
- // and if packaging is POM (we check type, but the
session is not yet available,
- // we would require the project realm if we want to
support extensions
- && Type.POM.equals(model.getPackaging())) {
- List<String> subprojects = new ArrayList<>();
- try (Stream<Path> files =
Files.list(model.getProjectDirectory())) {
- for (Path f : files.toList()) {
- if (Files.isDirectory(f)) {
- Path subproject =
modelProcessor.locateExistingPom(f);
- if (subproject != null) {
-
subprojects.add(f.getFileName().toString());
+ // subprojects discovery
+ if (!hasSubprojectsDefined(model)
+ // only discover subprojects if POM > 4.0.0
+ &&
!MODEL_VERSION_4_0_0.equals(model.getModelVersion())
+ // and if packaging is POM (we check type, but the
session is not yet available,
+ // we would require the project realm if we want
to support extensions
+ && Type.POM.equals(model.getPackaging())) {
+ List<String> subprojects = new ArrayList<>();
+ try (Stream<Path> files =
Files.list(model.getProjectDirectory())) {
+ for (Path f : files.toList()) {
+ if (Files.isDirectory(f)) {
+ Path subproject =
modelProcessor.locateExistingPom(f);
+ if (subproject != null) {
+
subprojects.add(f.getFileName().toString());
+ }
}
}
+ if (!subprojects.isEmpty()) {
+ model = model.withSubprojects(subprojects);
+ }
+ } catch (IOException e) {
+ add(Severity.FATAL, Version.V41, "Error
discovering subprojects", e);
}
- if (!subprojects.isEmpty()) {
- model = model.withSubprojects(subprojects);
- }
- } catch (IOException e) {
- add(Severity.FATAL, Version.V41, "Error discovering
subprojects", e);
}
- }
- // Enhanced property resolution with profile activation for
CI-friendly versions and repository URLs
- // This includes directory properties, profile properties, and
user properties
- Map<String, String> properties = getEnhancedProperties(model,
rootDirectory);
-
- // CI friendly version processing with profile-aware properties
- model = model.with()
- .version(replaceCiFriendlyVersion(properties,
model.getVersion()))
- .parent(
- model.getParent() != null
- ? model.getParent()
-
.withVersion(replaceCiFriendlyVersion(
- properties,
-
model.getParent().getVersion()))
- : null)
- .build();
+ // Enhanced property resolution with profile activation
for CI-friendly versions and repository URLs
+ // This includes directory properties, profile properties,
and user properties
+ Map<String, String> properties =
getEnhancedProperties(model, rootDirectory, activeModelReads);
+
+ // CI friendly version processing with profile-aware
properties
+ model = model.with()
+ .version(replaceCiFriendlyVersion(properties,
model.getVersion()))
+ .parent(
+ model.getParent() != null
+ ? model.getParent()
+
.withVersion(replaceCiFriendlyVersion(
+ properties,
+
model.getParent().getVersion()))
+ : null)
+ .build();
- // Repository URL interpolation with the same profile-aware
properties
- UnaryOperator<String> callback = properties::get;
- model = model.with()
-
.repositories(interpolateRepository(model.getRepositories(), callback))
-
.pluginRepositories(interpolateRepository(model.getPluginRepositories(),
callback))
- .profiles(map(model.getProfiles(),
this::interpolateRepository, callback))
-
.distributionManagement(interpolateRepository(model.getDistributionManagement(),
callback))
- .build();
- // Override model properties with user properties (use request
properties
- // to ensure consistency with model interpolation)
- Map<String, String> userProps = request.getUserProperties();
- Map<String, String> newProps = merge(model.getProperties(),
userProps);
- if (newProps != null) {
- model = model.withProperties(newProps);
+ // Repository URL interpolation with the same
profile-aware properties
+ UnaryOperator<String> callback = properties::get;
+ model = model.with()
+
.repositories(interpolateRepository(model.getRepositories(), callback))
+
.pluginRepositories(interpolateRepository(model.getPluginRepositories(),
callback))
+ .profiles(map(model.getProfiles(),
this::interpolateRepository, callback))
+
.distributionManagement(interpolateRepository(model.getDistributionManagement(),
callback))
+ .build();
+ // Override model properties with user properties (use
request properties
+ // to ensure consistency with model interpolation)
+ Map<String, String> userProps =
request.getUserProperties();
+ Map<String, String> newProps =
merge(model.getProperties(), userProps);
+ if (newProps != null) {
+ model = model.withProperties(newProps);
+ }
+ model = model.withProfiles(merge(model.getProfiles(),
userProps));
}
- model = model.withProfiles(merge(model.getProfiles(),
userProps));
- }
- for (var transformer : transformers) {
- model = transformer.transformFileModel(model);
- }
+ for (var transformer : transformers) {
+ model = transformer.transformFileModel(model);
+ }
- setSource(model);
- modelValidator.validateFileModel(
- session,
- model,
- isBuildRequest() ? ModelValidator.VALIDATION_LEVEL_STRICT
: ModelValidator.VALIDATION_LEVEL_MINIMAL,
- this);
- InternalSession internalSession = InternalSession.from(session);
- if
(Features.mavenMaven3Personality(internalSession.getSession().getConfigProperties())
- && Objects.equals(ModelBuilder.MODEL_VERSION_4_1_0,
model.getModelVersion())) {
- add(Severity.FATAL, Version.BASE, "Maven3 mode: no higher
model version than 4.0.0 allowed");
- }
- if (hasFatalErrors()) {
- throw newModelBuilderException();
- }
+ setSource(model);
+ modelValidator.validateFileModel(
+ session,
+ model,
+ isBuildRequest()
+ ? ModelValidator.VALIDATION_LEVEL_STRICT
+ : ModelValidator.VALIDATION_LEVEL_MINIMAL,
+ this);
+ InternalSession internalSession =
InternalSession.from(session);
+ if
(Features.mavenMaven3Personality(internalSession.getSession().getConfigProperties())
+ && Objects.equals(ModelBuilder.MODEL_VERSION_4_1_0,
model.getModelVersion())) {
+ add(Severity.FATAL, Version.BASE, "Maven3 mode: no higher
model version than 4.0.0 allowed");
+ }
+ if (hasFatalErrors()) {
+ throw newModelBuilderException();
+ }
- return model;
+ return model;
+ } finally {
+ if (trackRead) {
+ activeModelReads.remove(normalizedPath);
+ }
+ }
}
private DistributionManagement interpolateRepository(
diff --git
a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh12301StackOverflowInternalParentRevisionTest.java
b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh12301StackOverflowInternalParentRevisionTest.java
new file mode 100644
index 0000000000..dd24924ad1
--- /dev/null
+++
b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh12301StackOverflowInternalParentRevisionTest.java
@@ -0,0 +1,44 @@
+/*
+ * 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.File;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * This is a test set for <a
href="https://github.com/apache/maven/issues/12301">Issue #12301</a>.
+ * Verifies that a project with an internal parent in a subdirectory using
CI-friendly
+ * {@code ${revision}} and a {@code .mvn/} root marker does not cause a
StackOverflowError.
+ */
+public class MavenITgh12301StackOverflowInternalParentRevisionTest extends
AbstractMavenIntegrationTestCase {
+
+ @Test
+ public void testNoStackOverflowWithInternalParentAndRevision() throws
Exception {
+ File testDir =
extractResources("/gh-12301-stackoverflow-internal-parent");
+
+ Verifier verifier = newVerifier(testDir.getAbsolutePath());
+ verifier.setAutoclean(false);
+ verifier.deleteArtifacts("org.apache.maven.its.gh12301");
+ verifier.addCliArgument("-Drevision=1.0-SNAPSHOT");
+ verifier.addCliArgument("validate");
+ verifier.execute();
+ verifier.verifyErrorFreeLog();
+ }
+}
diff --git
a/its/core-it-suite/src/test/resources/gh-12301-stackoverflow-internal-parent/.mvn/.gitkeep
b/its/core-it-suite/src/test/resources/gh-12301-stackoverflow-internal-parent/.mvn/.gitkeep
new file mode 100644
index 0000000000..e69de29bb2
diff --git
a/its/core-it-suite/src/test/resources/gh-12301-stackoverflow-internal-parent/parent/pom.xml
b/its/core-it-suite/src/test/resources/gh-12301-stackoverflow-internal-parent/parent/pom.xml
new file mode 100644
index 0000000000..940ecc2231
--- /dev/null
+++
b/its/core-it-suite/src/test/resources/gh-12301-stackoverflow-internal-parent/parent/pom.xml
@@ -0,0 +1,29 @@
+<?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"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.1.0</modelVersion>
+
+ <groupId>org.apache.maven.its.gh12301</groupId>
+ <artifactId>parent</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ <packaging>pom</packaging>
+
+ <name>Maven Integration Test :: GH-12301 :: Parent</name>
+</project>
diff --git
a/its/core-it-suite/src/test/resources/gh-12301-stackoverflow-internal-parent/pom.xml
b/its/core-it-suite/src/test/resources/gh-12301-stackoverflow-internal-parent/pom.xml
new file mode 100644
index 0000000000..e3a1d65be1
--- /dev/null
+++
b/its/core-it-suite/src/test/resources/gh-12301-stackoverflow-internal-parent/pom.xml
@@ -0,0 +1,38 @@
+<?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"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.1.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.maven.its.gh12301</groupId>
+ <artifactId>parent</artifactId>
+ <version>${revision}</version>
+ <relativePath>parent</relativePath>
+ </parent>
+
+ <artifactId>root</artifactId>
+ <packaging>pom</packaging>
+
+ <name>Maven Integration Test :: GH-12301 :: Root</name>
+
+ <properties>
+ <revision>1.0-SNAPSHOT</revision>
+ </properties>
+</project>