diff --git a/initializr-version-resolver/pom.xml b/initializr-version-resolver/pom.xml
new file mode 100644
index 00000000..d8ed68b2
--- /dev/null
+++ b/initializr-version-resolver/pom.xml
@@ -0,0 +1,42 @@
+
+
+ 4.0.0
+
+ io.spring.initializr
+ initializr
+ ${revision}
+
+ initializr-version-resolver
+ Spring Initializr :: Version Resolver
+
+
+ ${basedir}/..
+
+
+
+
+ org.apache.maven
+ maven-resolver-provider
+
+
+ org.apache.maven.resolver
+ maven-resolver-connector-basic
+
+
+ org.apache.maven.resolver
+ maven-resolver-transport-http
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+
+
diff --git a/initializr-version-resolver/src/main/java/io/spring/initializr/versionresolver/DependencyManagementVersionResolver.java b/initializr-version-resolver/src/main/java/io/spring/initializr/versionresolver/DependencyManagementVersionResolver.java
new file mode 100644
index 00000000..f531a34e
--- /dev/null
+++ b/initializr-version-resolver/src/main/java/io/spring/initializr/versionresolver/DependencyManagementVersionResolver.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2012-2019 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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 io.spring.initializr.versionresolver;
+
+import java.nio.file.Path;
+import java.util.Map;
+
+/**
+ * A {@code DependencyManagementVersionResolver} is used to resolve the versions in the
+ * managed dependencies of a Maven bom. Implementations must be thread-safe.
+ *
+ * @author Andy Wilkinson
+ */
+public interface DependencyManagementVersionResolver {
+
+ /**
+ * Resolves the versions in the managed dependencies of the bom identified by the
+ * given {@code groupId}, {@code artifactId}, and {@code version}.
+ * @param groupId bom group ID
+ * @param artifactId bom artifact ID
+ * @param version bom version
+ * @return the managed dependencies as a map of {@code groupId:artifactId} to
+ * {@code version}
+ */
+ Map resolve(String groupId, String artifactId, String version);
+
+ /**
+ * Creates a new {@code DependencyManagementVersionResolver} that uses the given
+ * {@code location} for its local cache. To avoid multiple instances attempting to
+ * write to the same location cache, callers should ensure that a unique location is
+ * used. The returned resolver can then be used concurrently by multiple threads.
+ * @param location cache location
+ * @return the resolver
+ */
+ static DependencyManagementVersionResolver withCacheLocation(Path location) {
+ return new MavenResolverDependencyManagementVersionResolver(location);
+ }
+
+}
diff --git a/initializr-version-resolver/src/main/java/io/spring/initializr/versionresolver/MavenResolverDependencyManagementVersionResolver.java b/initializr-version-resolver/src/main/java/io/spring/initializr/versionresolver/MavenResolverDependencyManagementVersionResolver.java
new file mode 100644
index 00000000..9d45d4a9
--- /dev/null
+++ b/initializr-version-resolver/src/main/java/io/spring/initializr/versionresolver/MavenResolverDependencyManagementVersionResolver.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2012-2019 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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 io.spring.initializr.versionresolver;
+
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
+import org.eclipse.aether.DefaultRepositorySystemSession;
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory;
+import org.eclipse.aether.impl.DefaultServiceLocator;
+import org.eclipse.aether.internal.impl.DefaultRepositorySystem;
+import org.eclipse.aether.repository.LocalRepository;
+import org.eclipse.aether.repository.LocalRepositoryManager;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.resolution.ArtifactDescriptorException;
+import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
+import org.eclipse.aether.resolution.ArtifactDescriptorResult;
+import org.eclipse.aether.spi.connector.RepositoryConnectorFactory;
+import org.eclipse.aether.spi.connector.transport.TransporterFactory;
+import org.eclipse.aether.spi.locator.ServiceLocator;
+import org.eclipse.aether.transport.http.HttpTransporterFactory;
+import org.eclipse.aether.util.repository.SimpleArtifactDescriptorPolicy;
+
+/**
+ * A {@link DependencyManagementVersionResolver} that resolves versions using Maven
+ * Resolver. Maven's default {@link LocalRepositoryManager} implementation is not
+ * thread-safe. To avoid corruption of the local repository, interaction with the
+ * {@link RepositorySystem} is single-threaded.
+ *
+ * @author Andy Wilkinson
+ */
+class MavenResolverDependencyManagementVersionResolver implements DependencyManagementVersionResolver {
+
+ private static final RemoteRepository mavenCentral = new RemoteRepository.Builder("central", "default",
+ "https://repo1.maven.org/maven2").build();
+
+ private static final RemoteRepository springMilestones = new RemoteRepository.Builder("spring-milestones",
+ "default", "https://repo.spring.io/milestone").build();
+
+ private static final RemoteRepository springSnapshots = new RemoteRepository.Builder("spring-snapshots", "default",
+ "https://repo.spring.io/snapshot").build();
+
+ private static final List repositories = Arrays.asList(mavenCentral, springMilestones,
+ springSnapshots);
+
+ private final Object monitor = new Object();
+
+ private final RepositorySystemSession repositorySystemSession;
+
+ private final RepositorySystem repositorySystem;
+
+ MavenResolverDependencyManagementVersionResolver(Path cacheLocation) {
+ ServiceLocator serviceLocator = createServiceLocator();
+ DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession();
+ session.setArtifactDescriptorPolicy(new SimpleArtifactDescriptorPolicy(false, false));
+ LocalRepository localRepository = new LocalRepository(cacheLocation.toFile());
+ this.repositorySystem = serviceLocator.getService(RepositorySystem.class);
+ session.setLocalRepositoryManager(this.repositorySystem.newLocalRepositoryManager(session, localRepository));
+ session.setReadOnly();
+ this.repositorySystemSession = session;
+ }
+
+ @Override
+ public Map resolve(String groupId, String artifactId, String version) {
+ ArtifactDescriptorResult bom = resolveBom(groupId, artifactId, version);
+ Map managedVersions = new HashMap<>();
+ bom.getManagedDependencies().stream().map((dependency) -> dependency.getArtifact())
+ .forEach((artifact) -> managedVersions
+ .putIfAbsent(artifact.getGroupId() + ":" + artifact.getArtifactId(), artifact.getVersion()));
+ return managedVersions;
+ }
+
+ private ArtifactDescriptorResult resolveBom(String groupId, String artifactId, String version) {
+ synchronized (this.monitor) {
+ try {
+ return this.repositorySystem.readArtifactDescriptor(this.repositorySystemSession,
+ new ArtifactDescriptorRequest(new DefaultArtifact(groupId, artifactId, "pom", version),
+ repositories, null));
+ }
+ catch (ArtifactDescriptorException ex) {
+ throw new IllegalStateException(
+ "Bom '" + groupId + ":" + artifactId + ":" + version + "' could not be resolved", ex);
+ }
+ }
+ }
+
+ private static ServiceLocator createServiceLocator() {
+ DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator();
+ locator.addService(RepositorySystem.class, DefaultRepositorySystem.class);
+ locator.addService(RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class);
+ locator.addService(TransporterFactory.class, HttpTransporterFactory.class);
+ return locator;
+ }
+
+}
diff --git a/initializr-version-resolver/src/main/java/io/spring/initializr/versionresolver/package-info.java b/initializr-version-resolver/src/main/java/io/spring/initializr/versionresolver/package-info.java
new file mode 100644
index 00000000..4ecab6c7
--- /dev/null
+++ b/initializr-version-resolver/src/main/java/io/spring/initializr/versionresolver/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2012-2019 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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.
+ */
+
+/**
+ * Classes relating to version resolution.
+ */
+package io.spring.initializr.versionresolver;
diff --git a/initializr-version-resolver/src/test/java/io/spring/initializr/versionresolver/MavenResolverDependencyManagementVersionResolverTests.java b/initializr-version-resolver/src/test/java/io/spring/initializr/versionresolver/MavenResolverDependencyManagementVersionResolverTests.java
new file mode 100644
index 00000000..fa42c486
--- /dev/null
+++ b/initializr-version-resolver/src/test/java/io/spring/initializr/versionresolver/MavenResolverDependencyManagementVersionResolverTests.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2012-2019 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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 io.spring.initializr.versionresolver;
+
+import java.nio.file.Path;
+import java.util.Map;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
+
+/**
+ * Tests for {@link MavenResolverDependencyManagementVersionResolver}.
+ *
+ * @author Andy Wilkinson
+ */
+class MavenResolverDependencyManagementVersionResolverTests {
+
+ private DependencyManagementVersionResolver resolver;
+
+ @BeforeEach
+ void createResolver(@TempDir Path temp) {
+ this.resolver = new MavenResolverDependencyManagementVersionResolver(temp);
+ }
+
+ @Test
+ void springBootDependencies() {
+ Map versions = this.resolver.resolve("org.springframework.boot", "spring-boot-dependencies",
+ "2.1.5.RELEASE");
+ assertThat(versions).containsEntry("org.flywaydb:flyway-core", "5.2.4");
+ }
+
+ @Test
+ void springCloudDependencies() {
+ Map versions = this.resolver.resolve("org.springframework.cloud", "spring-cloud-dependencies",
+ "Greenwich.SR1");
+ assertThat(versions).containsEntry("com.netflix.ribbon:ribbon", "2.3.0");
+ }
+
+ @Test
+ void milestoneBomCanBeResolved() {
+ Map versions = this.resolver.resolve("org.springframework.boot", "spring-boot-dependencies",
+ "2.2.0.M3");
+ assertThat(versions).containsEntry("org.flywaydb:flyway-core", "5.2.4");
+ }
+
+ @Test
+ void snapshotBomCanBeResolved() {
+ Map versions = this.resolver.resolve("org.springframework.boot", "spring-boot-dependencies",
+ "2.2.0.BUILD-SNAPSHOT");
+ assertThat(versions).isNotEmpty();
+ }
+
+ @Test
+ void nonExistentDependency() {
+ assertThatIllegalStateException()
+ .isThrownBy(() -> this.resolver.resolve("org.springframework.boot", "spring-boot-bom", "1.0"))
+ .withMessage("Bom 'org.springframework.boot:spring-boot-bom:1.0' could not be resolved");
+ }
+
+}
diff --git a/pom.xml b/pom.xml
index 50819f49..854ea174 100644
--- a/pom.xml
+++ b/pom.xml
@@ -42,6 +42,8 @@
${basedir}
UTF-8
5.4.2
+ 3.6.1
+ 1.3.3
2.1.6.RELEASE
2.1.1.RELEASE
0.0.12
@@ -54,6 +56,7 @@
initializr-generator-spring
initializr-metadata
initializr-service-sample
+ initializr-version-resolver
initializr-web
@@ -102,6 +105,21 @@
${revision}
test-jar
+
+ org.apache.maven
+ maven-resolver-provider
+ ${maven.version}
+
+
+ org.apache.maven.resolver
+ maven-resolver-connector-basic
+ ${maven-resolver.version}
+
+
+ org.apache.maven.resolver
+ maven-resolver-transport-http
+ ${maven-resolver.version}
+
org.junit
junit-bom