diff --git a/build-parent/pom.xml b/build-parent/pom.xml
index 36d719186bfee1..e30becd151ecce 100644
--- a/build-parent/pom.xml
+++ b/build-parent/pom.xml
@@ -154,30 +154,29 @@
             </dependency>
 
             <!-- Dev tools -->
-
             <dependency>
                 <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-devmode-test-utils</artifactId>
+                <artifactId>quarkus-devtools-registry-client</artifactId>
                 <version>${project.version}</version>
             </dependency>
             <dependency>
                 <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-devtools-utilities</artifactId>
+                <artifactId>quarkus-test-devtools</artifactId>
                 <version>${project.version}</version>
             </dependency>
             <dependency>
                 <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-devtools-common</artifactId>
+                <artifactId>quarkus-devmode-test-utils</artifactId>
                 <version>${project.version}</version>
             </dependency>
             <dependency>
                 <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-platform-descriptor-api</artifactId>
+                <artifactId>quarkus-devtools-utilities</artifactId>
                 <version>${project.version}</version>
             </dependency>
             <dependency>
                 <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-platform-descriptor-resolver-json</artifactId>
+                <artifactId>quarkus-devtools-common</artifactId>
                 <version>${project.version}</version>
             </dependency>
             <dependency>
@@ -318,6 +317,7 @@
                             <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
                             <maven.home>${maven.home}</maven.home>
                             <maven.repo>${settings.localRepository}</maven.repo>
+                            <project.version>${project.version}</project.version> <!-- some dev tools tests need this -->
                         </systemPropertyVariables>
                         <argLine>${jacoco.agent.argLine} -Xmx1500m</argLine> <!-- limit the amount of memory surefire can use, 1500m should be plenty-->
                         <!-- https://lists.apache.org/thread.html/r9030808273c82ac6d7b9602d34d446c7d8c4e8aa02c41bca164df1c5%40%3Cdev.maven.apache.org%3E -->
@@ -332,6 +332,7 @@
                             <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
                             <maven.home>${maven.home}</maven.home>
                             <maven.repo>${settings.localRepository}</maven.repo>
+                            <project.version>${project.version}</project.version> <!-- some dev tools tests need this -->
                         </systemPropertyVariables>
                         <!-- https://lists.apache.org/thread.html/r9030808273c82ac6d7b9602d34d446c7d8c4e8aa02c41bca164df1c5%40%3Cdev.maven.apache.org%3E -->
                         <trimStackTrace>false</trimStackTrace>
diff --git a/devtools/bom-descriptor-json/pom.xml b/devtools/bom-descriptor-json/pom.xml
index ae87367dc470df..2979d4654ae85e 100644
--- a/devtools/bom-descriptor-json/pom.xml
+++ b/devtools/bom-descriptor-json/pom.xml
@@ -19,6 +19,27 @@
 
     <build>
         <plugins>
+            <plugin>
+                <artifactId>maven-resources-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>copy-resources</id>
+                        <phase>validate</phase>
+                        <goals>
+                            <goal>copy-resources</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${basedir}/target/resources</outputDirectory>
+                            <resources>
+                                <resource>
+                                    <directory>${basedir}/src/main/resources</directory>
+                                    <filtering>true</filtering>
+                                </resource>
+                            </resources>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
             <plugin>
                 <groupId>io.quarkus</groupId>
                 <artifactId>quarkus-platform-descriptor-json-plugin</artifactId>
@@ -27,7 +48,8 @@
                     <execution>
                         <phase>${pluginPhase}</phase>
                         <goals>
-                            <goal>generate-extensions-json</goal>
+                            <!-- goal>generate-extensions-json</goal -->
+                            <goal>generate-platform-descriptor-json</goal>
                         </goals>
                         <configuration>
                             <ignoredGroupIds>
@@ -35,6 +57,7 @@
                             </ignoredGroupIds>
                             <bomArtifactId>quarkus-bom</bomArtifactId>
                             <resolveDependencyManagement>true</resolveDependencyManagement>
+                            <overridesFile>${basedir}/target/resources/catalog-overrides.json</overridesFile>
                         </configuration>
                     </execution>
                 </executions>
@@ -2460,4 +2483,4 @@
         </profile>
     </profiles>
 
-</project>
+</project>
\ No newline at end of file
diff --git a/devtools/bom-descriptor-json/src/main/resources/catalog-overrides.json b/devtools/bom-descriptor-json/src/main/resources/catalog-overrides.json
new file mode 100644
index 00000000000000..3ac9f49cc7f65b
--- /dev/null
+++ b/devtools/bom-descriptor-json/src/main/resources/catalog-overrides.json
@@ -0,0 +1,28 @@
+{
+  "metadata":{
+    "project": {
+      "properties": {
+        "doc-root": "https://quarkus.io",
+        "rest-assured-version": "${rest-assured.version}",
+        "compiler-plugin-version": "${compiler-plugin.version}",
+        "surefire-plugin-version": "${version.surefire.plugin}",
+        "kotlin-version": "${kotlin.version}",
+        "scala-version": "${scala.version}",
+        "scala-plugin-version": "${scala-plugin.version}",
+        "quarkus-core-version": "${project.version}",
+        "maven-plugin-groupId": "${project.groupId}",
+        "maven-plugin-artifactId": "quarkus-maven-plugin",
+        "maven-plugin-version": "${project.version}",
+        "gradle-plugin-id": "io.quarkus",
+        "gradle-plugin-version": "${project.version}",
+        "supported-maven-versions": "${supported-maven-versions}",
+        "proposed-maven-version": "${proposed-maven-version}",
+        "maven-wrapper-version": "${maven-wrapper.version}",
+        "gradle-wrapper-version": "${gradle-wrapper.version}"
+      }
+    },
+    "codestarts-artifacts": [
+      "${project.groupId}:quarkus-platform-descriptor-json::jar:${project.version}"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/devtools/bom-descriptor-json/src/main/resources/extensions-overrides.json b/devtools/bom-descriptor-json/src/main/resources/extensions-overrides.json
deleted file mode 100644
index 53c32b86548aca..00000000000000
--- a/devtools/bom-descriptor-json/src/main/resources/extensions-overrides.json
+++ /dev/null
@@ -1,411 +0,0 @@
-{
-  "categories": [
-    {
-      "name": "Core",
-      "id": "core",
-      "description": "Essential Quarkus components. Provided automatically"
-    },
-    {
-      "name": "Web",
-      "id": "web",
-      "description": "Everything you need for REST endpoints, HTTP and web formats like JSON",
-      "metadata": {
-        "pinned": [
-          "io.quarkus:quarkus-resteasy",
-          "io.quarkus:quarkus-resteasy-jsonb",
-          "io.quarkus:quarkus-resteasy-jackson"
-        ]
-      }
-    },
-    {
-      "name": "Data",
-      "id": "data",
-      "description": "Accessing and managing your data (RDBMS, NoSQL, caching, transaction management, etc)",
-      "metadata": {
-        "pinned": [
-          "io.quarkus:quarkus-hibernate-orm",
-          "io.quarkus:quarkus-hibernate-orm-panache",
-          "io.quarkus:quarkus-jdbc-postgresql",
-          "io.quarkus:quarkus-jdbc-mariadb",
-          "io.quarkus:quarkus-jdbc-mysql",
-          "io.quarkus:quarkus-jdbc-mssql",
-          "io.quarkus:quarkus-jdbc-h2",
-          "io.quarkus:quarkus-jdbc-derby"
-        ]
-      }
-    },
-    {
-      "name": "Messaging",
-      "id": "messaging",
-      "description": "Send and receives message to various messaging ssytems (AMQP, KAfka etc)",
-      "metadata": {
-        "pinned": [
-          "io.quarkus:quarkus-smallrye-reactive-messaging",
-          "io.quarkus:quarkus-smallrye-reactive-messaging-amqp",
-          "io.quarkus:quarkus-smallrye-reactive-messaging-kafka",
-          "io.quarkus:quarkus-smallrye-reactive-messaging-mqtt"
-        ]
-      }
-    },
-    {
-      "name": "Reactive",
-      "id": "reactive",
-      "description": "Non blocking stack and connectors",
-      "metadata": {
-        "pinned": [
-          "io.quarkus:quarkus-vertx"
-        ]
-      }
-    },
-    {
-      "name": "Cloud",
-      "id": "cloud",
-      "description": "Useful for Cloud Native deployments platforms like Kubernetes and cloud providers",
-      "metadata": {
-        "pinned": [
-          "io.quarkus:quarkus-kubernetes",
-          "io.quarkus:quarkus-smallrye-health",
-          "io.quarkus:quarkus-smallrye-fault-tolerance"
-        ]
-      }
-    },
-    {
-      "name": "Observability",
-      "id": "observability",
-      "description": "Metrics, tracing, etc"
-    },
-    {
-      "name": "Security",
-      "id": "security",
-      "description": "Everything you need to secure your application",
-      "metadata": {
-        "pinned": [
-          "io.quarkus:quarkus-oidc",
-          "io.quarkus:quarkus-smallrye-jwt"
-        ]
-      }
-    },
-    {
-      "name": "Integration",
-      "id": "integration",
-      "description": "Connectors to read to write from a skew of systems (file, S#, Twitter, etc)",
-      "metadata": {
-        "pinned": [
-          "org.apache.camel.quarkus:camel-quarkus-core",
-          "org.apache.camel.quarkus:camel-quarkus-core-xml"
-        ]
-      }
-    },
-    {
-      "name": "gRPC",
-      "id": "grpc",
-      "description": "gRPC integration",
-      "metadata": {
-        "pinned": [
-          "io.quarkus:quarkus-grpc"
-        ]
-      }
-    },
-    {
-      "name": "Business Automation",
-      "id": "business-automation",
-      "description": "Rules engine, BPM, etc"
-    },
-    {
-      "name": "Serialization",
-      "id": "serialization",
-      "description": "Serializing and deserializing various formats"
-    },
-    {
-      "name": "Miscellaneous",
-      "id": "miscellaneous",
-      "description": "Mixed bag of good stuff"
-    },
-    {
-      "name": "Compatibility",
-      "id": "compatibility",
-      "description": "Support for alternative programming models on Quarkus"
-    },
-    {
-      "name": "Alternative languages",
-      "id": "alt-languages",
-      "description": "Support for other JVM based languages"
-    }
-  ],
-  "extensions": [
-    {
-      "group-id": "org.apache.camel.quarkus",
-      "artifact-id": "camel-quarkus-support-common",
-      "version": "0.3.1",
-      "description": "Camel Quarkus Support Common",
-      "metadata": {
-        "unlisted": true
-      }
-    },
-    {
-      "metadata": {
-        "unlisted": true
-      },
-      "group-id": "org.apache.camel.quarkus",
-      "artifact-id": "camel-quarkus-support-jetty",
-      "version": "0.3.1",
-      "description": "Camel Quarkus Support Jetty"
-    },
-    {
-      "metadata": {
-        "unlisted": true
-      },
-      "group-id": "org.apache.camel.quarkus",
-      "artifact-id": "camel-quarkus-support-xstream",
-      "version": "0.3.1",
-      "description": "Camel Quarkus Support XStream"
-    },
-    {
-      "metadata": {
-        "unlisted": true
-      },
-      "group-id": "org.apache.camel.quarkus",
-      "artifact-id": "camel-quarkus-support-xml",
-      "version": "0.3.1",
-      "description": "Camel Quarkus Support XML"
-    },
-    {
-      "group-id": "org.apache.camel.quarkus",
-      "artifact-id": "camel-quarkus-core",
-      "version": "0.3.1",
-      "description": "The Camel Quarkus core module"
-    },
-    {
-      "group-id": "org.apache.camel.quarkus",
-      "artifact-id": "camel-quarkus-core-cloud",
-      "version": "0.3.1",
-      "description": "The Camel Quarkus core cloud module"
-    },
-    {
-      "group-id": "org.apache.camel.quarkus",
-      "artifact-id": "camel-quarkus-core-xml",
-      "version": "0.3.1",
-      "description": "Camel Quarkus core XML module"
-    },
-    {
-      "metadata": {
-        "unlisted": true
-      },
-      "group-id": "org.apache.camel.quarkus",
-      "artifact-id": "camel-quarkus-http-common",
-      "version": "0.3.1",
-      "description": "Camel Quarkus HTTP Common"
-    },
-    {
-      "group-id": "org.apache.camel.quarkus",
-      "artifact-id": "camel-quarkus-aws-eks",
-      "version": "0.3.1",
-      "description": "A Camel Amazon EKS Web Service Component"
-    },
-    {
-      "group-id": "org.apache.camel.quarkus",
-      "artifact-id": "camel-quarkus-aws-s3",
-      "version": "0.3.1",
-      "description": "A Camel Amazon S3 Web Service Component"
-    },
-    {
-      "group-id": "org.apache.camel.quarkus",
-      "artifact-id": "camel-quarkus-aws-sqs",
-      "version": "0.3.1",
-      "description": "A Camel Amazon SQS Web Service Component"
-    },
-    {
-      "group-id": "org.apache.camel.quarkus",
-      "artifact-id": "camel-quarkus-aws-sns",
-      "version": "0.3.1",
-      "description": "A Camel Amazon SNS Web Service Component"
-    },
-    {
-      "group-id": "org.apache.camel.quarkus",
-      "artifact-id": "camel-quarkus-bean",
-      "version": "0.3.1",
-      "description": "Camel Bean component"
-    },
-    {
-      "group-id": "org.apache.camel.quarkus",
-      "artifact-id": "camel-quarkus-csv",
-      "version": "0.3.1",
-      "description": "Camel CSV data format support"
-    },
-    {
-      "group-id": "org.apache.camel.quarkus",
-      "artifact-id": "camel-quarkus-direct",
-      "version": "0.3.1",
-      "description": "Camel Direct component"
-    },
-    {
-      "group-id": "org.apache.camel.quarkus",
-      "artifact-id": "camel-quarkus-fhir",
-      "version": "0.3.1",
-      "description": "Camel FHIR HL7 support"
-    },
-    {
-      "group-id": "org.apache.camel.quarkus",
-      "artifact-id": "camel-quarkus-infinispan",
-      "version": "0.3.1",
-      "description": "Camel Infinispan support"
-    },
-    {
-      "group-id": "org.apache.camel.quarkus",
-      "artifact-id": "camel-quarkus-jackson",
-      "version": "0.3.1",
-      "description": "Camel Jackson support"
-    },
-    {
-      "group-id": "org.apache.camel.quarkus",
-      "artifact-id": "camel-quarkus-jdbc",
-      "version": "0.3.1",
-      "description": "Camel JDBC suppors"
-    },
-    {
-      "group-id": "org.apache.camel.quarkus",
-      "artifact-id": "camel-quarkus-log",
-      "version": "0.3.1",
-      "description": "Camel Log component"
-    },
-    {
-      "group-id": "org.apache.camel.quarkus",
-      "artifact-id": "camel-quarkus-microprofile-health",
-      "version": "0.3.1",
-      "description": "Integration with the Quarkus MicroProfile Health extension"
-    },
-    {
-      "group-id": "org.apache.camel.quarkus",
-      "artifact-id": "camel-quarkus-microprofile-metrics",
-      "version": "0.3.1",
-      "description": "Integration with the Quarkus MicroProfile Metrics extension"
-    },
-    {
-      "group-id": "org.apache.camel.quarkus",
-      "artifact-id": "camel-quarkus-mail",
-      "version": "0.3.1",
-      "description": "Camel Mail support"
-    },
-    {
-      "name": "Camel Quarkus Netty HTTP",
-      "group-id": "org.apache.camel.quarkus",
-      "artifact-id": "camel-quarkus-netty-http",
-      "version": "0.3.1",
-      "description": "Camel Netty HTTP support"
-    },
-    {
-      "group-id": "org.apache.camel.quarkus",
-      "artifact-id": "camel-quarkus-opentracing",
-      "version": "0.3.1",
-      "description": "Distributed tracing using OpenTracing"
-    },
-    {
-      "group-id": "org.apache.camel.quarkus",
-      "artifact-id": "camel-quarkus-paho",
-      "version": "0.3.1",
-      "description": "Camel Eclipse Paho support"
-    },
-    {
-      "group-id": "org.apache.camel.quarkus",
-      "artifact-id": "camel-quarkus-platform-http",
-      "version": "0.3.1",
-      "description": "HTTP platform component is used for integrating Camel HTTP with Quarkus HTTP layer"
-    },
-    {
-      "group-id": "org.apache.camel.quarkus",
-      "artifact-id": "camel-quarkus-reactive-executor",
-      "version": "0.3.1",
-      "description": "Integrates Quarkus reactive executor with Camel"
-    },
-    {
-      "group-id": "org.apache.camel.quarkus",
-      "artifact-id": "camel-quarkus-rest",
-      "version": "0.3.1",
-      "description": "Camel REST component"
-    },
-    {
-      "group-id": "org.apache.camel.quarkus",
-      "artifact-id": "camel-quarkus-salesforce",
-      "version": "0.3.1",
-      "description": "Camel Salesforce support"
-    },
-    {
-      "group-id": "org.apache.camel.quarkus",
-      "artifact-id": "camel-quarkus-servlet",
-      "version": "0.3.1",
-      "description": "Camel servlet transport support"
-    },
-    {
-      "group-id": "org.apache.camel.quarkus",
-      "artifact-id": "camel-quarkus-slack",
-      "version": "0.3.1",
-      "description": "Camel Slack Support"
-    },
-    {
-      "group-id": "org.apache.camel.quarkus",
-      "artifact-id": "camel-quarkus-tarfile",
-      "version": "0.3.1",
-      "description": "Camel Tar file support"
-    },
-    {
-      "group-id": "org.apache.camel.quarkus",
-      "artifact-id": "camel-quarkus-timer",
-      "version": "0.3.1",
-      "description": "Camel Timer component"
-    },
-    {
-      "group-id": "org.apache.camel.quarkus",
-      "artifact-id": "camel-quarkus-twitter",
-      "version": "0.3.1",
-      "description": "Camel Twitter support"
-    },
-    {
-      "group-id": "org.apache.camel.quarkus",
-      "artifact-id": "camel-quarkus-vm",
-      "version": "0.3.1",
-      "description": "Camel VM component"
-    },
-    {
-      "group-id": "org.apache.camel.quarkus",
-      "artifact-id": "camel-quarkus-zipfile",
-      "version": "0.3.1",
-      "description": "Camel Zip file support"
-    },
-    {
-      "group-id": "org.apache.camel.quarkus",
-      "artifact-id": "camel-quarkus-attachments",
-      "version": "0.3.1",
-      "description": "Java Attachments support for Camel Message"
-    },
-    {
-      "group-id": "org.apache.camel.quarkus",
-      "artifact-id": "camel-quarkus-pdf",
-      "version": "0.3.1",
-      "description": "Camel PDF support"
-    },
-    {
-      "group-id": "org.kie.kogito",
-      "artifact-id": "kogito-quarkus",
-      "version": "0.8.0",
-      "description": "Kogito extension"
-    },
-    {
-      "group-id": "org.optaplanner",
-      "artifact-id": "optaplanner-quarkus",
-      "version": "7.39.0.CR1",
-      "description": "OptaPlanner extension"
-    },
-    {
-      "group-id": "org.optaplanner",
-      "artifact-id": "optaplanner-quarkus-jackson",
-      "version": "7.39.0.CR1",
-      "description": "OptaPlanner Jackson extension"
-    },
-    {
-      "group-id": "org.optaplanner",
-      "artifact-id": "optaplanner-quarkus-jsonb",
-      "version": "7.39.0.CR1",
-      "description": "OptaPlanner JSON-B extension"
-    }
-  ]
-}
diff --git a/devtools/cli/pom.xml b/devtools/cli/pom.xml
index 2ceeff252befcc..582a33a4e773b6 100644
--- a/devtools/cli/pom.xml
+++ b/devtools/cli/pom.xml
@@ -28,7 +28,8 @@
         </dependency>
         <dependency>
             <groupId>io.quarkus</groupId>
-            <artifactId>quarkus-platform-descriptor-resolver-json</artifactId>
+            <artifactId>quarkus-test-devtools</artifactId>
+            <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.junit.jupiter</groupId>
@@ -57,6 +58,12 @@
     </dependencies>
 
     <build>
+        <testResources>
+            <testResource>
+                <directory>src/test/resources</directory>
+                <filtering>true</filtering>
+            </testResource>
+        </testResources>
         <plugins>
             <plugin>
                 <groupId>io.quarkus</groupId>
diff --git a/devtools/cli/src/main/java/io/quarkus/cli/Add.java b/devtools/cli/src/main/java/io/quarkus/cli/Add.java
index cfdf010dd7bbfb..8d811879d22569 100644
--- a/devtools/cli/src/main/java/io/quarkus/cli/Add.java
+++ b/devtools/cli/src/main/java/io/quarkus/cli/Add.java
@@ -7,11 +7,12 @@
 
 import io.quarkus.cli.core.BaseSubCommand;
 import io.quarkus.cli.core.BuildsystemCommand;
+import io.quarkus.cli.core.QuarkusCliVersion;
 import io.quarkus.devtools.commands.AddExtensions;
 import io.quarkus.devtools.commands.data.QuarkusCommandOutcome;
 import io.quarkus.devtools.project.BuildTool;
 import io.quarkus.devtools.project.QuarkusProject;
-import io.quarkus.platform.tools.config.QuarkusPlatformConfig;
+import io.quarkus.devtools.project.QuarkusProjectHelper;
 import picocli.CommandLine;
 
 @CommandLine.Command(name = "add", usageHelpAutoWidth = true, mixinStandardHelpOptions = false, description = "Add extension(s) to current project.")
@@ -47,8 +48,7 @@ public List<String> getArguments(Path projectDir, BuildTool buildtool) {
 
     private Integer addMaven(Path projectDirectory) {
         try {
-            QuarkusProject quarkusProject = QuarkusProject.resolveExistingProject(projectDirectory,
-                    QuarkusPlatformConfig.getGlobalDefault().getPlatformDescriptor());
+            QuarkusProject quarkusProject = QuarkusProjectHelper.getProject(projectDirectory, QuarkusCliVersion.version());
 
             AddExtensions project = new AddExtensions(quarkusProject);
             project.extensions(extensions);
diff --git a/devtools/cli/src/main/java/io/quarkus/cli/Create.java b/devtools/cli/src/main/java/io/quarkus/cli/Create.java
index 2f243290421bc8..e14daa927b2ddb 100644
--- a/devtools/cli/src/main/java/io/quarkus/cli/Create.java
+++ b/devtools/cli/src/main/java/io/quarkus/cli/Create.java
@@ -6,10 +6,12 @@
 import java.util.concurrent.Callable;
 
 import io.quarkus.cli.core.BaseSubCommand;
+import io.quarkus.cli.core.QuarkusCliVersion;
 import io.quarkus.devtools.commands.CreateProject;
 import io.quarkus.devtools.project.BuildTool;
+import io.quarkus.devtools.project.QuarkusProject;
+import io.quarkus.devtools.project.QuarkusProjectHelper;
 import io.quarkus.devtools.project.codegen.SourceType;
-import io.quarkus.platform.tools.config.QuarkusPlatformConfig;
 import picocli.CommandLine;
 
 @CommandLine.Command(name = "create", sortOptions = false, usageHelpAutoWidth = true, mixinStandardHelpOptions = false, description = "Create a new quarkus project.")
@@ -111,17 +113,17 @@ public Integer call() throws Exception {
             else if (targetBuildTool.gradleKotlinDsl)
                 buildTool = BuildTool.GRADLE_KOTLIN_DSL;
 
-            boolean status = new CreateProject(projectRoot.getAbsoluteFile().toPath(),
-                    QuarkusPlatformConfig.getGlobalDefault().getPlatformDescriptor())
-                            .buildTool(buildTool)
-                            .groupId(groupId)
-                            .artifactId(artifactId)
-                            .version(version)
-                            .sourceType(sourceType)
-                            .overrideExamples(examples)
-                            .extensions(extensions)
-                            .noExamples(noExamples)
-                            .execute().isSuccess();
+            final QuarkusProject project = QuarkusProjectHelper.getProject(projectRoot.getAbsoluteFile().toPath(), buildTool,
+                    QuarkusCliVersion.version());
+            boolean status = new CreateProject(project)
+                    .groupId(groupId)
+                    .artifactId(artifactId)
+                    .version(version)
+                    .sourceType(sourceType)
+                    .overrideExamples(examples)
+                    .extensions(extensions)
+                    .noExamples(noExamples)
+                    .execute().isSuccess();
 
             if (status) {
                 out().println("Project " + artifactId +
diff --git a/devtools/cli/src/main/java/io/quarkus/cli/CreateJBang.java b/devtools/cli/src/main/java/io/quarkus/cli/CreateJBang.java
index 41f3ddbf2cb79a..a922427e0a0201 100644
--- a/devtools/cli/src/main/java/io/quarkus/cli/CreateJBang.java
+++ b/devtools/cli/src/main/java/io/quarkus/cli/CreateJBang.java
@@ -5,8 +5,9 @@
 import java.util.concurrent.Callable;
 
 import io.quarkus.cli.core.BaseSubCommand;
+import io.quarkus.cli.core.QuarkusCliVersion;
 import io.quarkus.devtools.commands.CreateJBangProject;
-import io.quarkus.platform.tools.config.QuarkusPlatformConfig;
+import io.quarkus.devtools.project.QuarkusProjectHelper;
 import picocli.CommandLine;
 
 @CommandLine.Command(name = "create-jbang", sortOptions = false, usageHelpAutoWidth = true, mixinStandardHelpOptions = false, description = "Create a new quarkus jbang project.")
@@ -35,13 +36,12 @@ public Integer call() throws Exception {
                 return CommandLine.ExitCode.SOFTWARE;
             }
 
-            boolean status = new CreateJBangProject(projectRoot.getAbsoluteFile().toPath(),
-                    QuarkusPlatformConfig.getGlobalDefault().getPlatformDescriptor())
+            boolean status = new CreateJBangProject(
+                    QuarkusProjectHelper.getProject(projectRoot.getAbsoluteFile().toPath(), QuarkusCliVersion.version()))
                             .extensions(extensions)
                             .setValue("noJBangWrapper", noJBangWrapper)
                             .execute()
                             .isSuccess();
-
             if (status) {
                 out().println("JBang project created.");
                 parent.setProjectDirectory(projectRoot.toPath().toAbsolutePath());
diff --git a/devtools/cli/src/main/java/io/quarkus/cli/List.java b/devtools/cli/src/main/java/io/quarkus/cli/List.java
index b7665cd44294ed..40e7f09187c6d5 100644
--- a/devtools/cli/src/main/java/io/quarkus/cli/List.java
+++ b/devtools/cli/src/main/java/io/quarkus/cli/List.java
@@ -5,11 +5,11 @@
 
 import io.quarkus.cli.core.BaseSubCommand;
 import io.quarkus.cli.core.BuildsystemCommand;
+import io.quarkus.cli.core.QuarkusCliVersion;
 import io.quarkus.devtools.commands.ListExtensions;
 import io.quarkus.devtools.commands.data.QuarkusCommandException;
 import io.quarkus.devtools.project.BuildTool;
-import io.quarkus.devtools.project.QuarkusProject;
-import io.quarkus.platform.tools.config.QuarkusPlatformConfig;
+import io.quarkus.devtools.project.QuarkusProjectHelper;
 import picocli.CommandLine;
 
 @CommandLine.Command(name = "list", usageHelpAutoWidth = true, sortOptions = false, mixinStandardHelpOptions = false, description = "List installed (default) or installable extensions.")
@@ -37,6 +37,10 @@ static class ExtensionFormat {
                 "--full" }, order = 6, description = "Display concise format and version related columns.")
         boolean full = false;
 
+        @CommandLine.Option(names = {
+                "--origins" }, order = 7, description = "Display extensions including their platform origins.")
+        boolean origins = false;
+
     }
 
     @Override
@@ -59,6 +63,8 @@ private String getFormatString() {
             formatString = "concise";
         else if (format.full)
             formatString = "full";
+        else if (format.origins)
+            formatString = "origins";
         return formatString;
     }
 
@@ -82,14 +88,13 @@ private Integer listExtensionsMaven(Path projectDirectory) {
         // we do not have to spawn process for maven
         try {
 
-            new ListExtensions(QuarkusProject.resolveExistingProject(projectDirectory,
-                    QuarkusPlatformConfig.getGlobalDefault().getPlatformDescriptor()))
-                            .fromCli(true)
-                            .all(false)
-                            .installed(!installable)
-                            .format(getFormatString())
-                            .search(searchPattern)
-                            .execute();
+            new ListExtensions(QuarkusProjectHelper.getProject(projectDirectory, QuarkusCliVersion.version()))
+                    .fromCli(true)
+                    .all(false)
+                    .installed(!installable)
+                    .format(getFormatString())
+                    .search(searchPattern)
+                    .execute();
         } catch (QuarkusCommandException e) {
             if (parent.showErrors)
                 e.printStackTrace(err());
diff --git a/devtools/cli/src/main/java/io/quarkus/cli/Platforms.java b/devtools/cli/src/main/java/io/quarkus/cli/Platforms.java
new file mode 100644
index 00000000000000..26d056ab48bfb2
--- /dev/null
+++ b/devtools/cli/src/main/java/io/quarkus/cli/Platforms.java
@@ -0,0 +1,32 @@
+package io.quarkus.cli;
+
+import java.nio.file.Path;
+
+import io.quarkus.cli.core.BaseSubCommand;
+import io.quarkus.cli.core.BuildsystemCommand;
+import io.quarkus.cli.core.QuarkusCliVersion;
+import io.quarkus.devtools.commands.ListPlatforms;
+import io.quarkus.devtools.project.BuildTool;
+import io.quarkus.devtools.project.QuarkusProjectHelper;
+import picocli.CommandLine;
+
+@CommandLine.Command(name = "platforms", usageHelpAutoWidth = true, sortOptions = false, mixinStandardHelpOptions = false, description = "List imported (default) or all available Quarkus platforms.")
+public class Platforms extends BaseSubCommand implements BuildsystemCommand {
+
+    @Override
+    public int execute(Path projectDirectory, BuildTool buildTool) {
+        try {
+            new ListPlatforms(
+                    QuarkusProjectHelper.getProject(projectDirectory, buildTool == null ? BuildTool.MAVEN : buildTool,
+                            QuarkusCliVersion.version()))
+                                    .execute();
+        } catch (Exception e) {
+            if (parent.showErrors) {
+                e.printStackTrace(err());
+            }
+            return CommandLine.ExitCode.SOFTWARE;
+        }
+        return CommandLine.ExitCode.OK;
+    }
+
+}
diff --git a/devtools/cli/src/main/java/io/quarkus/cli/QuarkusCli.java b/devtools/cli/src/main/java/io/quarkus/cli/QuarkusCli.java
index 00b91bd33ce19f..ab7a18f04dd77d 100644
--- a/devtools/cli/src/main/java/io/quarkus/cli/QuarkusCli.java
+++ b/devtools/cli/src/main/java/io/quarkus/cli/QuarkusCli.java
@@ -10,7 +10,7 @@
 
 import io.quarkus.cli.core.BuildsystemCommand;
 import io.quarkus.cli.core.ExecuteUtil;
-import io.quarkus.cli.core.QuarkusVersion;
+import io.quarkus.cli.core.QuarkusCliVersion;
 import io.quarkus.devtools.project.BuildTool;
 import io.quarkus.devtools.project.QuarkusProject;
 import io.quarkus.runtime.QuarkusApplication;
@@ -19,9 +19,9 @@
 
 @QuarkusMain
 @CommandLine.Command(name = "quarkus", aliases = {
-        "qs" }, versionProvider = QuarkusVersion.class, usageHelpAutoWidth = true, subcommandsRepeatable = true, mixinStandardHelpOptions = true, subcommands = {
+        "qs" }, versionProvider = QuarkusCliVersion.class, usageHelpAutoWidth = true, subcommandsRepeatable = true, mixinStandardHelpOptions = true, subcommands = {
                 Build.class,
-                Clean.class, Create.class, CreateJBang.class, List.class, Add.class, Remove.class, Dev.class })
+                Clean.class, Create.class, CreateJBang.class, List.class, Platforms.class, Add.class, Remove.class, Dev.class })
 public class QuarkusCli implements QuarkusApplication {
 
     public void usage() {
diff --git a/devtools/cli/src/main/java/io/quarkus/cli/Remove.java b/devtools/cli/src/main/java/io/quarkus/cli/Remove.java
index 1eae235d6d981b..b8f1527fddef04 100644
--- a/devtools/cli/src/main/java/io/quarkus/cli/Remove.java
+++ b/devtools/cli/src/main/java/io/quarkus/cli/Remove.java
@@ -7,12 +7,11 @@
 
 import io.quarkus.cli.core.BaseSubCommand;
 import io.quarkus.cli.core.BuildsystemCommand;
+import io.quarkus.cli.core.QuarkusCliVersion;
 import io.quarkus.devtools.commands.RemoveExtensions;
 import io.quarkus.devtools.commands.data.QuarkusCommandOutcome;
 import io.quarkus.devtools.project.BuildTool;
-import io.quarkus.devtools.project.QuarkusProject;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
-import io.quarkus.platform.tools.config.QuarkusPlatformConfig;
+import io.quarkus.devtools.project.QuarkusProjectHelper;
 import picocli.CommandLine;
 
 @CommandLine.Command(name = "remove", aliases = "rm", usageHelpAutoWidth = true, mixinStandardHelpOptions = false, description = "Remove an extension from this project.")
@@ -48,10 +47,10 @@ public int execute(Path projectDir, BuildTool buildtool) throws Exception {
     }
 
     private Integer removeMaven(Path projectDirectory) {
-        final QuarkusPlatformDescriptor platformDescr = QuarkusPlatformConfig.getGlobalDefault().getPlatformDescriptor();
         try {
-            RemoveExtensions project = new RemoveExtensions(QuarkusProject.resolveExistingProject(projectDirectory,
-                    platformDescr)).extensions(extensions);
+            RemoveExtensions project = new RemoveExtensions(
+                    QuarkusProjectHelper.getProject(projectDirectory, QuarkusCliVersion.version()))
+                            .extensions(extensions);
             QuarkusCommandOutcome result = project.execute();
             return result.isSuccess() ? CommandLine.ExitCode.OK : CommandLine.ExitCode.SOFTWARE;
         } catch (Exception e) {
diff --git a/devtools/cli/src/main/java/io/quarkus/cli/core/ExecuteUtil.java b/devtools/cli/src/main/java/io/quarkus/cli/core/ExecuteUtil.java
index 7ad2d14dcc7259..eb1f618b9b4136 100644
--- a/devtools/cli/src/main/java/io/quarkus/cli/core/ExecuteUtil.java
+++ b/devtools/cli/src/main/java/io/quarkus/cli/core/ExecuteUtil.java
@@ -18,6 +18,7 @@
 
 import io.quarkus.cli.QuarkusCli;
 import io.quarkus.devtools.project.BuildTool;
+import io.quarkus.registry.config.RegistriesConfigLocator;
 import io.quarkus.utilities.OS;
 import picocli.CommandLine;
 
@@ -173,9 +174,9 @@ public static int executeGradleTarget(File projectPath, QuarkusCli cli, String..
             return SOFTWARE;
         }
         String[] newArgs = args;
-        if (System.getProperties().containsKey("maven.repo.local")) {
-            newArgs = prependArray("-Dmaven.repo.local=" + System.getProperty("maven.repo.local"), newArgs);
-        }
+        newArgs = propagatePropertyIfSet("maven.repo.local", newArgs);
+        newArgs = propagatePropertyIfSet(RegistriesConfigLocator.CONFIG_FILE_PATH_PROPERTY, newArgs);
+        newArgs = propagatePropertyIfSet("io.quarkus.maven.secondary-local-repo", newArgs);
         if (cli.isShowErrors()) {
             newArgs = prependArray("--full-stacktrace", newArgs);
         }
@@ -189,6 +190,11 @@ public static int executeGradleTarget(File projectPath, QuarkusCli cli, String..
         }
     }
 
+    private static String[] propagatePropertyIfSet(String name, String[] newArgs) {
+        final String value = System.getProperty(name);
+        return value == null ? newArgs : prependArray("-D" + name + "=" + value, newArgs);
+    }
+
     public static int executeMavenTarget(File projectPath, QuarkusCli cli, String... args) throws Exception {
         File buildFile = projectPath.toPath().resolve("pom.xml").toFile();
         if (!buildFile.isFile()) {
diff --git a/devtools/cli/src/main/java/io/quarkus/cli/core/QuarkusCliVersion.java b/devtools/cli/src/main/java/io/quarkus/cli/core/QuarkusCliVersion.java
new file mode 100644
index 00000000000000..60ad015fa716c4
--- /dev/null
+++ b/devtools/cli/src/main/java/io/quarkus/cli/core/QuarkusCliVersion.java
@@ -0,0 +1,44 @@
+package io.quarkus.cli.core;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.util.Properties;
+
+import io.smallrye.common.classloader.ClassPathUtils;
+import picocli.CommandLine;
+
+public class QuarkusCliVersion implements CommandLine.IVersionProvider {
+
+    private static String version;
+
+    public static String version() {
+        if (version != null) {
+            return version;
+        }
+        final URL quarkusPropsUrl = Thread.currentThread().getContextClassLoader().getResource("quarkus.properties");
+        if (quarkusPropsUrl == null) {
+            throw new RuntimeException("Failed to locate quarkus.properties on the classpath");
+        }
+        final Properties props = new Properties();
+        ClassPathUtils.consumeAsPath(quarkusPropsUrl, p -> {
+            try (BufferedReader reader = Files.newBufferedReader(p)) {
+                props.load(reader);
+            } catch (IOException e) {
+                throw new RuntimeException("Failed to load quarkus.properties", e);
+            }
+        });
+        version = props.getProperty("quarkus-core-version");
+        if (version == null) {
+            throw new RuntimeException("Failed to locate quarkus-core-version property in the bundled quarkus.properties");
+        }
+        return version;
+    }
+
+    @Override
+    public String[] getVersion() throws Exception {
+        return new String[] { version() };
+    }
+
+}
diff --git a/devtools/cli/src/main/java/io/quarkus/cli/core/QuarkusVersion.java b/devtools/cli/src/main/java/io/quarkus/cli/core/QuarkusVersion.java
index 8e710d9c1ae1d5..e3ebfbc3c6dfb9 100644
--- a/devtools/cli/src/main/java/io/quarkus/cli/core/QuarkusVersion.java
+++ b/devtools/cli/src/main/java/io/quarkus/cli/core/QuarkusVersion.java
@@ -1,12 +1,15 @@
 package io.quarkus.cli.core;
 
-import io.quarkus.platform.tools.config.QuarkusPlatformConfig;
+import java.nio.file.Paths;
+
+import io.quarkus.devtools.project.QuarkusProjectHelper;
 import picocli.CommandLine;
 
 public class QuarkusVersion implements CommandLine.IVersionProvider {
 
     @Override
     public String[] getVersion() throws Exception {
-        return new String[] { QuarkusPlatformConfig.getGlobalDefault().getPlatformDescriptor().getQuarkusVersion() };
+        return new String[] { QuarkusProjectHelper.getProject(Paths.get("").normalize().toAbsolutePath()).getExtensionsCatalog()
+                .getQuarkusCoreVersion() };
     }
 }
diff --git a/devtools/cli/src/test/java/io/quarkus/cli/CliTest.java b/devtools/cli/src/test/java/io/quarkus/cli/CliTest.java
index 89d34244c5c841..bd0f7cfaa823f7 100644
--- a/devtools/cli/src/test/java/io/quarkus/cli/CliTest.java
+++ b/devtools/cli/src/test/java/io/quarkus/cli/CliTest.java
@@ -8,12 +8,15 @@
 import java.nio.file.Paths;
 import java.util.Comparator;
 
+import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 import io.quarkus.cli.core.ExecuteUtil;
+import io.quarkus.devtools.test.DevToolsRegistryTestHelper;
 import picocli.CommandLine;
 
 public class CliTest {
@@ -24,6 +27,16 @@ public class CliTest {
     private String cwd;
     private Path workspace;
 
+    @BeforeAll
+    public static void globalSetup() {
+        DevToolsRegistryTestHelper.enableDevToolsTestConfig();
+    }
+
+    @AfterAll
+    public static void globalReset() {
+        DevToolsRegistryTestHelper.disableDevToolsTestConfig();
+    }
+
     @BeforeEach
     public void setupTestDirectories() throws Exception {
         cwd = System.getProperty("user.dir");
@@ -32,7 +45,6 @@ public void setupTestDirectories() throws Exception {
         Assertions.assertFalse(workspace.toFile().exists());
         Files.createDirectories(workspace);
         System.setProperty("user.dir", workspace.toFile().getAbsolutePath());
-
     }
 
     @AfterEach
diff --git a/devtools/gradle/build.gradle b/devtools/gradle/build.gradle
index 30e74d42792018..e55bfd1ad8d18f 100644
--- a/devtools/gradle/build.gradle
+++ b/devtools/gradle/build.gradle
@@ -27,17 +27,12 @@ repositories {
     mavenCentral()
 }
 
-configurations.all {
-    exclude group: 'io.quarkus', module: 'quarkus-bootstrap-maven-resolver'
-}
-
 dependencies {
     api gradleApi()
 
     implementation "io.quarkus:quarkus-bootstrap-core:${version}"
     implementation "io.quarkus:quarkus-devtools-common:${version}"
     implementation "io.quarkus:quarkus-platform-descriptor-json:${version}"
-    implementation "io.quarkus:quarkus-platform-descriptor-resolver-json:${version}"
     implementation "io.quarkus:quarkus-core-deployment:${version}"
 
     testImplementation 'org.mockito:mockito-core:3.8.0'
diff --git a/devtools/gradle/pom.xml b/devtools/gradle/pom.xml
index 870b117142b9c2..cfc5f3bf44de11 100644
--- a/devtools/gradle/pom.xml
+++ b/devtools/gradle/pom.xml
@@ -45,10 +45,6 @@
             <groupId>io.quarkus</groupId>
             <artifactId>quarkus-platform-descriptor-json</artifactId>
         </dependency>
-        <dependency>
-            <groupId>io.quarkus</groupId>
-            <artifactId>quarkus-platform-descriptor-resolver-json</artifactId>
-        </dependency>
         <dependency>
             <groupId>org.awaitility</groupId>
             <artifactId>awaitility</artifactId>
diff --git a/devtools/gradle/src/main/java/io/quarkus/devtools/project/buildfile/GradleGroovyProjectBuildFile.java b/devtools/gradle/src/main/java/io/quarkus/devtools/project/buildfile/GradleGroovyProjectBuildFile.java
index 8ad02f5e780d5e..66a9f7449076df 100644
--- a/devtools/gradle/src/main/java/io/quarkus/devtools/project/buildfile/GradleGroovyProjectBuildFile.java
+++ b/devtools/gradle/src/main/java/io/quarkus/devtools/project/buildfile/GradleGroovyProjectBuildFile.java
@@ -4,15 +4,15 @@
 
 import io.quarkus.bootstrap.model.AppArtifactCoords;
 import io.quarkus.devtools.project.BuildTool;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
+import io.quarkus.registry.catalog.ExtensionCatalog;
 
 public class GradleGroovyProjectBuildFile extends GradleProjectBuildFile {
 
     static final String BUILD_GRADLE_PATH = "build.gradle";
     static final String SETTINGS_GRADLE_PATH = "settings.gradle";
 
-    public GradleGroovyProjectBuildFile(Project project, QuarkusPlatformDescriptor platformDescriptor) {
-        super(project, platformDescriptor);
+    public GradleGroovyProjectBuildFile(Project project, ExtensionCatalog catalog) {
+        super(project, catalog);
     }
 
     @Override
diff --git a/devtools/gradle/src/main/java/io/quarkus/devtools/project/buildfile/GradleKotlinProjectBuildFile.java b/devtools/gradle/src/main/java/io/quarkus/devtools/project/buildfile/GradleKotlinProjectBuildFile.java
index cafc67675528fa..4ca9db743ea10d 100644
--- a/devtools/gradle/src/main/java/io/quarkus/devtools/project/buildfile/GradleKotlinProjectBuildFile.java
+++ b/devtools/gradle/src/main/java/io/quarkus/devtools/project/buildfile/GradleKotlinProjectBuildFile.java
@@ -4,15 +4,15 @@
 
 import io.quarkus.bootstrap.model.AppArtifactCoords;
 import io.quarkus.devtools.project.BuildTool;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
+import io.quarkus.registry.catalog.ExtensionCatalog;
 
 public class GradleKotlinProjectBuildFile extends GradleProjectBuildFile {
 
     static final String BUILD_GRADLE_PATH = "build.gradle.kts";
     static final String SETTINGS_GRADLE_PATH = "settings.gradle.kts";
 
-    public GradleKotlinProjectBuildFile(Project project, QuarkusPlatformDescriptor platformDescriptor) {
-        super(project, platformDescriptor);
+    public GradleKotlinProjectBuildFile(Project project, ExtensionCatalog catalog) {
+        super(project, catalog);
     }
 
     @Override
diff --git a/devtools/gradle/src/main/java/io/quarkus/devtools/project/buildfile/GradleProjectBuildFile.java b/devtools/gradle/src/main/java/io/quarkus/devtools/project/buildfile/GradleProjectBuildFile.java
index bc9dbccb3e4f20..2ae31c87f2d614 100644
--- a/devtools/gradle/src/main/java/io/quarkus/devtools/project/buildfile/GradleProjectBuildFile.java
+++ b/devtools/gradle/src/main/java/io/quarkus/devtools/project/buildfile/GradleProjectBuildFile.java
@@ -15,14 +15,14 @@
 import org.gradle.api.plugins.JavaPlugin;
 
 import io.quarkus.bootstrap.model.AppArtifactCoords;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
+import io.quarkus.registry.catalog.ExtensionCatalog;
 
 public abstract class GradleProjectBuildFile extends AbstractGradleBuildFile {
 
     private final Project project;
 
-    public GradleProjectBuildFile(Project project, QuarkusPlatformDescriptor platformDescriptor) {
-        super(project.getProjectDir().toPath(), platformDescriptor,
+    public GradleProjectBuildFile(Project project, ExtensionCatalog catalog) {
+        super(project.getProjectDir().toPath(), catalog,
                 project.getParent() != null ? project.getRootProject().getProjectDir().toPath()
                         : project.getProjectDir().toPath());
         this.project = project;
diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/AppModelGradleResolver.java b/devtools/gradle/src/main/java/io/quarkus/gradle/AppModelGradleResolver.java
index f9fa9d7fb7ab50..4760563d618ae3 100644
--- a/devtools/gradle/src/main/java/io/quarkus/gradle/AppModelGradleResolver.java
+++ b/devtools/gradle/src/main/java/io/quarkus/gradle/AppModelGradleResolver.java
@@ -7,6 +7,7 @@
 
 import org.gradle.api.Project;
 import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.ResolveException;
 import org.gradle.api.artifacts.ResolvedArtifact;
 import org.gradle.api.artifacts.ResolvedConfiguration;
 import org.gradle.api.internal.artifacts.dependencies.DefaultDependencyArtifact;
@@ -35,12 +36,23 @@ public AppModelGradleResolver(Project project, QuarkusModel model) {
     @Override
     public String getLatestVersion(AppArtifact appArtifact, String upToVersion, boolean inclusive)
             throws AppModelResolverException {
-        throw new UnsupportedOperationException();
+        try {
+            return resolveArtifact(new AppArtifact(appArtifact.getGroupId(), appArtifact.getArtifactId(),
+                    appArtifact.getClassifier(), appArtifact.getType(),
+                    "[" + appArtifact.getVersion() + "," + upToVersion + (inclusive ? "]" : ")"))).getVersion();
+        } catch (AppModelResolverException e) {
+            return null;
+        }
     }
 
     @Override
     public String getLatestVersionFromRange(AppArtifact appArtifact, String range) throws AppModelResolverException {
-        throw new UnsupportedOperationException();
+        try {
+            return resolveArtifact(new AppArtifact(appArtifact.getGroupId(), appArtifact.getArtifactId(),
+                    appArtifact.getClassifier(), appArtifact.getType(), range)).getVersion();
+        } catch (AppModelResolverException e) {
+            return null;
+        }
     }
 
     @Override
@@ -63,40 +75,52 @@ public void relink(AppArtifact appArtifact, Path localPath) throws AppModelResol
 
     @Override
     public Path resolve(AppArtifact appArtifact) throws AppModelResolverException {
-        if (!appArtifact.isResolved()) {
-            final DefaultDependencyArtifact dep = new DefaultDependencyArtifact();
-            dep.setExtension(appArtifact.getType());
-            dep.setType(appArtifact.getType());
-            dep.setName(appArtifact.getArtifactId());
-            if (appArtifact.getClassifier() != null) {
-                dep.setClassifier(appArtifact.getClassifier());
-            }
+        return resolveArtifact(appArtifact).getPaths().getSinglePath();
+    }
 
-            final DefaultExternalModuleDependency gradleDep = new DefaultExternalModuleDependency(appArtifact.getGroupId(),
-                    appArtifact.getArtifactId(), appArtifact.getVersion(), null);
-            gradleDep.addArtifact(dep);
-
-            final Configuration detachedConfig = project.getConfigurations().detachedConfiguration(gradleDep);
-
-            final ResolvedConfiguration rc = detachedConfig.getResolvedConfiguration();
-            Set<ResolvedArtifact> resolvedArtifacts = rc.getResolvedArtifacts();
-            for (ResolvedArtifact a : resolvedArtifacts) {
-                if (appArtifact.getArtifactId().equals(a.getName())
-                        && appArtifact.getType().equals(a.getType())
-                        && (a.getClassifier() == null ? appArtifact.getClassifier() == null
-                                : a.getClassifier().equals(appArtifact.getClassifier()))
-                        && appArtifact.getGroupId().equals(a.getModuleVersion().getId().getGroup())) {
-                    appArtifact.setPath(a.getFile().toPath());
-                    break;
-                }
-            }
+    private AppArtifact resolveArtifact(AppArtifact appArtifact) throws AppModelResolverException {
+        if (appArtifact.isResolved()) {
+            return appArtifact;
+        }
+        final DefaultDependencyArtifact dep = new DefaultDependencyArtifact();
+        dep.setExtension(appArtifact.getType());
+        dep.setType(appArtifact.getType());
+        dep.setName(appArtifact.getArtifactId());
+        if (appArtifact.getClassifier() != null) {
+            dep.setClassifier(appArtifact.getClassifier());
+        }
+
+        final DefaultExternalModuleDependency gradleDep = new DefaultExternalModuleDependency(appArtifact.getGroupId(),
+                appArtifact.getArtifactId(), appArtifact.getVersion(), null);
+        gradleDep.addArtifact(dep);
 
-            if (!appArtifact.isResolved()) {
-                throw new AppModelResolverException("Failed to resolve " + appArtifact);
+        final Configuration detachedConfig = project.getConfigurations().detachedConfiguration(gradleDep);
+
+        final ResolvedConfiguration rc = detachedConfig.getResolvedConfiguration();
+        Set<ResolvedArtifact> resolvedArtifacts;
+        try {
+            resolvedArtifacts = rc.getResolvedArtifacts();
+        } catch (ResolveException e) {
+            throw new AppModelResolverException("Failed to resolve " + appArtifact, e);
+        }
+        for (ResolvedArtifact a : resolvedArtifacts) {
+            if (appArtifact.getArtifactId().equals(a.getName()) && appArtifact.getType().equals(a.getType())
+                    && (a.getClassifier() == null ? appArtifact.getClassifier() == null
+                            : a.getClassifier().equals(appArtifact.getClassifier()))
+                    && appArtifact.getGroupId().equals(a.getModuleVersion().getId().getGroup())) {
+                if (!appArtifact.getVersion().equals(a.getModuleVersion().getId().getVersion())) {
+                    appArtifact = new AppArtifact(appArtifact.getGroupId(), appArtifact.getArtifactId(),
+                            appArtifact.getClassifier(), appArtifact.getType(), a.getModuleVersion().getId().getVersion());
+                }
+                appArtifact.setPath(a.getFile().toPath());
+                break;
             }
+        }
 
+        if (!appArtifact.isResolved()) {
+            throw new AppModelResolverException("Failed to resolve " + appArtifact);
         }
-        return appArtifact.getPaths().getSinglePath();
+        return appArtifact;
     }
 
     @Override
@@ -129,5 +153,4 @@ public AppModel resolveManagedModel(AppArtifact appArtifact, List<AppDependency>
             throws AppModelResolverException {
         return resolveModel(appArtifact);
     }
-
 }
diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java
index 3d3b31f50b1b16..331a0027870e5d 100644
--- a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java
+++ b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java
@@ -39,6 +39,7 @@
 import io.quarkus.gradle.tasks.QuarkusGenerateCode;
 import io.quarkus.gradle.tasks.QuarkusGenerateConfig;
 import io.quarkus.gradle.tasks.QuarkusListExtensions;
+import io.quarkus.gradle.tasks.QuarkusListPlatforms;
 import io.quarkus.gradle.tasks.QuarkusRemoteDev;
 import io.quarkus.gradle.tasks.QuarkusRemoveExtension;
 import io.quarkus.gradle.tasks.QuarkusTestConfig;
@@ -51,6 +52,7 @@ public class QuarkusPlugin implements Plugin<Project> {
 
     public static final String EXTENSION_NAME = "quarkus";
     public static final String LIST_EXTENSIONS_TASK_NAME = "listExtensions";
+    public static final String LIST_PLATFORMS_TASK_NAME = "listPlatforms";
     public static final String ADD_EXTENSION_TASK_NAME = "addExtension";
     public static final String REMOVE_EXTENSION_TASK_NAME = "removeExtension";
     public static final String QUARKUS_GENERATE_CODE_TASK_NAME = "quarkusGenerateCode";
@@ -90,10 +92,10 @@ public void apply(Project project) {
         registerTasks(project, quarkusExt);
     }
 
-    @SuppressWarnings("Convert2Lambda")
     private void registerTasks(Project project, QuarkusPluginExtension quarkusExt) {
         TaskContainer tasks = project.getTasks();
         tasks.create(LIST_EXTENSIONS_TASK_NAME, QuarkusListExtensions.class);
+        tasks.create(LIST_PLATFORMS_TASK_NAME, QuarkusListPlatforms.class);
         tasks.create(ADD_EXTENSION_TASK_NAME, QuarkusAddExtension.class);
         tasks.create(REMOVE_EXTENSION_TASK_NAME, QuarkusRemoveExtension.class);
         tasks.create(GENERATE_CONFIG_TASK_NAME, QuarkusGenerateConfig.class);
diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusAddExtension.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusAddExtension.java
index cab182f13f01e2..2639c3cdfbbc2d 100644
--- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusAddExtension.java
+++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusAddExtension.java
@@ -1,21 +1,17 @@
 package io.quarkus.gradle.tasks;
 
 import static java.util.Arrays.stream;
-import static java.util.stream.Collectors.toList;
 import static java.util.stream.Collectors.toSet;
 
-import java.net.URL;
 import java.util.List;
 import java.util.Set;
 
 import org.gradle.api.GradleException;
 import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.Optional;
 import org.gradle.api.tasks.TaskAction;
 import org.gradle.api.tasks.options.Option;
 
 import io.quarkus.devtools.commands.AddExtensions;
-import io.quarkus.registry.DefaultExtensionRegistry;
 
 public class QuarkusAddExtension extends QuarkusPlatformTask {
 
@@ -24,7 +20,6 @@ public QuarkusAddExtension() {
     }
 
     private List<String> extensionsToAdd;
-    private List<String> registries;
 
     @Option(option = "extensions", description = "Configures the extensions to be added.")
     public void setExtensionsToAdd(List<String> extensionsToAdd) {
@@ -36,17 +31,6 @@ public List<String> getExtensionsToAdd() {
         return extensionsToAdd;
     }
 
-    @Optional
-    @Input
-    public List<String> getRegistries() {
-        return registries;
-    }
-
-    @Option(description = "The extension registry URLs to be used", option = "registry")
-    public void setRegistries(List<String> registry) {
-        this.registries = registry;
-    }
-
     @TaskAction
     public void addExtension() {
         Set<String> extensionsSet = getExtensionsToAdd()
@@ -56,14 +40,8 @@ public void addExtension() {
                 .collect(toSet());
 
         try {
-            AddExtensions addExtensions = new AddExtensions(getQuarkusProject())
+            AddExtensions addExtensions = new AddExtensions(getQuarkusProject(false))
                     .extensions(extensionsSet);
-            if (registries != null && !registries.isEmpty()) {
-                List<URL> urls = registries.stream()
-                        .map(QuarkusAddExtension::toURL)
-                        .collect(toList());
-                addExtensions.extensionRegistry(DefaultExtensionRegistry.fromURLs(urls));
-            }
             addExtensions.execute();
         } catch (Exception e) {
             throw new GradleException("Failed to add extensions " + getExtensionsToAdd(), e);
diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusListExtensions.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusListExtensions.java
index 97c29f1261475b..428f0f4d932ae6 100644
--- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusListExtensions.java
+++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusListExtensions.java
@@ -1,10 +1,5 @@
 package io.quarkus.gradle.tasks;
 
-import static java.util.stream.Collectors.toList;
-
-import java.net.URL;
-import java.util.List;
-
 import org.gradle.api.GradleException;
 import org.gradle.api.tasks.Input;
 import org.gradle.api.tasks.Optional;
@@ -13,7 +8,6 @@
 
 import io.quarkus.devtools.commands.ListExtensions;
 import io.quarkus.devtools.project.QuarkusProject;
-import io.quarkus.registry.DefaultExtensionRegistry;
 
 public class QuarkusListExtensions extends QuarkusPlatformTask {
 
@@ -25,8 +19,6 @@ public class QuarkusListExtensions extends QuarkusPlatformTask {
 
     private String searchPattern;
 
-    private List<String> registries;
-
     @Input
     public boolean isAll() {
         return all;
@@ -79,17 +71,6 @@ public void setSearchPattern(String searchPattern) {
         this.searchPattern = searchPattern;
     }
 
-    @Optional
-    @Input
-    public List<String> getRegistries() {
-        return registries;
-    }
-
-    @Option(description = "The extension registry URLs to be used", option = "registry")
-    public void setRegistries(List<String> registry) {
-        this.registries = registry;
-    }
-
     public QuarkusListExtensions() {
         super("Lists the available quarkus extensions");
     }
@@ -97,22 +78,13 @@ public QuarkusListExtensions() {
     @TaskAction
     public void listExtensions() {
         try {
-            final QuarkusProject quarkusProject = getQuarkusProject();
-            getLogger().info("Quarkus platform " + quarkusProject.getPlatformDescriptor().getBomGroupId() + ":"
-                    + quarkusProject.getPlatformDescriptor().getBomArtifactId() + ":"
-                    + quarkusProject.getPlatformDescriptor().getBomVersion());
+            final QuarkusProject quarkusProject = getQuarkusProject(installed);
             ListExtensions listExtensions = new ListExtensions(quarkusProject)
                     .all(isFromCli() ? false : isAll())
                     .fromCli(isFromCli())
                     .format(getFormat())
                     .installed(isInstalled())
                     .search(getSearchPattern());
-            if (registries != null && !registries.isEmpty()) {
-                List<URL> urls = registries.stream()
-                        .map(QuarkusListExtensions::toURL)
-                        .collect(toList());
-                listExtensions.extensionRegistry(DefaultExtensionRegistry.fromURLs(urls));
-            }
             listExtensions.execute();
         } catch (Exception e) {
             throw new GradleException("Unable to list extensions", e);
diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusListPlatforms.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusListPlatforms.java
new file mode 100644
index 00000000000000..ae671f78c16c94
--- /dev/null
+++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusListPlatforms.java
@@ -0,0 +1,52 @@
+package io.quarkus.gradle.tasks;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.api.tasks.options.Option;
+
+import io.quarkus.devtools.commands.ListPlatforms;
+import io.quarkus.registry.Constants;
+
+public class QuarkusListPlatforms extends QuarkusPlatformTask {
+
+    private boolean installed = false;
+
+    public QuarkusListPlatforms() {
+        super("Lists the available quarkus platforms");
+    }
+
+    @Input
+    public boolean isInstalled() {
+        return installed;
+    }
+
+    @Option(description = "List only installed platforms.", option = "installed")
+    public void setInstalled(boolean installed) {
+        this.installed = installed;
+    }
+
+    @TaskAction
+    public void listExtensions() {
+        getProject().getLogger().info("");
+        if (installed) {
+            getProject().getLogger().info("Imported Quarkus platforms:");
+            importedPlatforms().forEach(coords -> {
+                final StringBuilder buf = new StringBuilder();
+                buf.append(coords.getGroupId()).append(":")
+                        .append(coords.getArtifactId().substring(0,
+                                coords.getArtifactId().length() - Constants.PLATFORM_DESCRIPTOR_ARTIFACT_ID_SUFFIX.length()))
+                        .append("::pom:").append(coords.getVersion());
+                messageWriter().info(buf.toString());
+            });
+        } else {
+            getProject().getLogger().info("Available Quarkus platforms:");
+            try {
+                new ListPlatforms(getQuarkusProject(installed)).execute();
+            } catch (Exception e) {
+                throw new GradleException("Unable to list platforms", e);
+            }
+        }
+        getProject().getLogger().info("");
+    }
+}
diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusPlatformTask.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusPlatformTask.java
index 6631109480881d..c3905b83dadb6b 100644
--- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusPlatformTask.java
+++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusPlatformTask.java
@@ -8,28 +8,27 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
 
 import org.gradle.api.GradleException;
-import org.gradle.api.Project;
 import org.gradle.api.artifacts.Configuration;
 import org.gradle.api.artifacts.Dependency;
 import org.gradle.api.artifacts.ModuleDependency;
-import org.gradle.api.artifacts.ResolvedArtifact;
 import org.gradle.api.attributes.Category;
-import org.gradle.api.internal.artifacts.dependencies.DefaultDependencyArtifact;
-import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency;
 import org.gradle.api.plugins.JavaPlugin;
-import org.gradle.api.tasks.Internal;
 
 import io.quarkus.bootstrap.BootstrapConstants;
 import io.quarkus.bootstrap.model.AppArtifactKey;
+import io.quarkus.devtools.messagewriter.MessageWriter;
 import io.quarkus.devtools.project.QuarkusProject;
+import io.quarkus.devtools.project.QuarkusProjectHelper;
 import io.quarkus.devtools.project.buildfile.BuildFile;
 import io.quarkus.devtools.project.buildfile.GradleGroovyProjectBuildFile;
 import io.quarkus.devtools.project.buildfile.GradleKotlinProjectBuildFile;
-import io.quarkus.platform.descriptor.CombinedQuarkusPlatformDescriptor;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
-import io.quarkus.platform.descriptor.resolver.json.QuarkusJsonPlatformDescriptorResolver;
+import io.quarkus.maven.ArtifactCoords;
+import io.quarkus.platform.tools.ToolsUtils;
+import io.quarkus.registry.ExtensionCatalogResolver;
+import io.quarkus.registry.catalog.ExtensionCatalog;
 
 public abstract class QuarkusPlatformTask extends QuarkusTask {
 
@@ -37,35 +36,70 @@ public abstract class QuarkusPlatformTask extends QuarkusTask {
         super(description);
     }
 
-    protected QuarkusPlatformDescriptor platformDescriptor() {
+    private ExtensionCatalog extensionsCatalog(boolean limitExtensionsToImportedPlatforms, MessageWriter log) {
+        final List<ArtifactCoords> platforms = importedPlatforms();
+        final ExtensionCatalogResolver catalogResolver = QuarkusProjectHelper.getCatalogResolver(log);
+        if (catalogResolver.hasRegistries()) {
+            try {
+                return limitExtensionsToImportedPlatforms ? catalogResolver.resolveExtensionCatalog(platforms)
+                        : catalogResolver.resolveExtensionCatalog(quarkusCoreVersion());
+            } catch (Exception e) {
+                throw new RuntimeException("Failed to resolve extensions catalog", e);
+            }
+        }
+        return ToolsUtils.mergePlatforms(platforms, extension().getAppModelResolver());
+    }
 
-        final List<Dependency> boms = boms();
-        if (boms.isEmpty()) {
+    protected List<ArtifactCoords> importedPlatforms() {
+        final List<Dependency> bomDeps = boms();
+        if (bomDeps.isEmpty()) {
             throw new GradleException("No platforms detected in the project");
         }
 
-        final QuarkusJsonPlatformDescriptorResolver platformResolver = QuarkusJsonPlatformDescriptorResolver.newInstance()
-                .setArtifactResolver(extension().getAppModelResolver())
-                .setMessageWriter(new GradleMessageWriter(getProject().getLogger()));
+        final Configuration boms = getProject().getConfigurations()
+                .detachedConfiguration(bomDeps.toArray(new org.gradle.api.artifacts.Dependency[0]));
+        final Set<AppArtifactKey> processedKeys = new HashSet<>(1);
 
-        final QuarkusPlatformDescriptor platform = resolvePlatformDescriptor(platformResolver, getProject(), boms);
-        if (platform != null) {
-            return platform;
-        }
+        List<ArtifactCoords> platforms = new ArrayList<>();
+        boms.getResolutionStrategy().eachDependency(d -> {
+            if (!d.getTarget().getName().endsWith(BootstrapConstants.PLATFORM_DESCRIPTOR_ARTIFACT_ID_SUFFIX)
+                    || !processedKeys.add(new AppArtifactKey(d.getTarget().getGroup(), d.getTarget().getName()))) {
+                return;
+            }
+            final ArtifactCoords platform = new ArtifactCoords(d.getTarget().getGroup(), d.getTarget().getName(),
+                    d.getTarget().getVersion(), "json", d.getTarget().getVersion());
+            platforms.add(platform);
+        });
+        boms.getResolvedConfiguration();
 
-        final List<QuarkusPlatformDescriptor> platforms = resolveLegacyPlatforms(platformResolver, boms);
         if (platforms.isEmpty()) {
-            throw new GradleException("Failed to determine the Quarkus platform for the project");
+            throw new RuntimeException("No Quarkus platforms found in the project");
         }
-        if (platforms.size() == 1) {
-            return platforms.get(0);
+        return platforms;
+    }
+
+    protected String quarkusCoreVersion() {
+        final List<Dependency> bomDeps = boms();
+        if (bomDeps.isEmpty()) {
+            throw new GradleException("No platforms detected in the project");
         }
 
-        final CombinedQuarkusPlatformDescriptor.Builder builder = CombinedQuarkusPlatformDescriptor.builder();
-        for (QuarkusPlatformDescriptor descriptor : platforms) {
-            builder.addPlatform(descriptor);
+        final Configuration boms = getProject().getConfigurations()
+                .detachedConfiguration(bomDeps.toArray(new org.gradle.api.artifacts.Dependency[0]));
+
+        final AtomicReference<String> quarkusVersionRef = new AtomicReference<>();
+        boms.getResolutionStrategy().eachDependency(d -> {
+            if (quarkusVersionRef.get() == null && d.getTarget().getName().equals("quarkus-core")
+                    && d.getTarget().getGroup().equals("io.quarkus")) {
+                quarkusVersionRef.set(d.getTarget().getVersion());
+            }
+        });
+        boms.getResolvedConfiguration();
+        final String quarkusCoreVersion = quarkusVersionRef.get();
+        if (quarkusCoreVersion == null) {
+            throw new IllegalStateException("Failed to determine the Quarkus core version for the project");
         }
-        return builder.build();
+        return quarkusCoreVersion;
     }
 
     private List<Dependency> boms() {
@@ -87,99 +121,10 @@ private List<Dependency> boms() {
         return boms;
     }
 
-    private List<QuarkusPlatformDescriptor> resolveLegacyPlatforms(
-            QuarkusJsonPlatformDescriptorResolver platformResolver,
-            List<Dependency> boms) {
-        List<QuarkusPlatformDescriptor> platforms = new ArrayList<>(2);
-        boms.forEach(bom -> {
-            try {
-                platforms
-                        .add(platformResolver.resolveFromBom(bom.getGroup(), bom.getName(), bom.getVersion()));
-            } catch (Exception e) {
-                // not a platform
-            }
-        });
-        return platforms;
-    }
+    protected QuarkusProject getQuarkusProject(boolean limitExtensionsToImportedPlatforms) {
 
-    private QuarkusPlatformDescriptor resolvePlatformDescriptor(QuarkusJsonPlatformDescriptorResolver descriptorResolver,
-            Project project, List<Dependency> bomDeps) {
-        final Configuration boms = project.getConfigurations()
-                .detachedConfiguration(bomDeps.toArray(new org.gradle.api.artifacts.Dependency[0]));
-        final Set<AppArtifactKey> processedKeys = new HashSet<>(1);
-        final List<ResolvedArtifact> descriptorDeps = new ArrayList<>(2);
-        boms.getResolutionStrategy().eachDependency(d -> {
-            if (!d.getTarget().getName().endsWith(BootstrapConstants.PLATFORM_DESCRIPTOR_ARTIFACT_ID_SUFFIX)
-                    || !processedKeys.add(new AppArtifactKey(d.getTarget().getGroup(), d.getTarget().getName()))) {
-                return;
-            }
-
-            final DefaultDependencyArtifact dep = new DefaultDependencyArtifact();
-            dep.setExtension("json");
-            dep.setType("json");
-            dep.setClassifier(d.getTarget().getVersion());
-            dep.setName(d.getTarget().getName());
-
-            final DefaultExternalModuleDependency gradleDep = new DefaultExternalModuleDependency(
-                    d.getTarget().getGroup(), d.getTarget().getName(), d.getTarget().getVersion(), null);
-            gradleDep.addArtifact(dep);
-
-            try {
-                for (ResolvedArtifact a : project.getConfigurations().detachedConfiguration(gradleDep)
-                        .getResolvedConfiguration().getResolvedArtifacts()) {
-                    if (a.getName().equals(d.getTarget().getName())) {
-                        descriptorDeps.add(a);
-                        break;
-                    }
-                }
-            } catch (Exception e) {
-                // ignore for now
-            }
-        });
-        boms.getResolvedConfiguration();
-
-        if (descriptorDeps.isEmpty()) {
-            return null;
-        }
-        if (descriptorDeps.size() == 1) {
-            return descriptorResolver.resolveFromJson(descriptorDeps.get(0).getFile().toPath());
-        }
-
-        // Typically, quarkus-bom platform will appear first.
-        // The descriptors that are generated today are not fragmented and include everything
-        // a platform offers. Which means if the quarkus-bom platform appears first and its version
-        // matches the Quarkus core version of the platform built on top of the quarkus-bom
-        // (e.g. quarkus-universe-bom) the quarkus-bom platform can be skipped,
-        // since it will already be included in the platform that's built on top of it
-        int i = 0;
-        ResolvedArtifact platformArtifact = descriptorDeps.get(0);
-        final String quarkusBomPlatformArtifactId = "quarkus-bom-"
-                + BootstrapConstants.PLATFORM_DESCRIPTOR_ARTIFACT_ID_SUFFIX;
-        ResolvedArtifact quarkusBomPlatformArtifact = null;
-        if (quarkusBomPlatformArtifactId.equals(platformArtifact.getName())) {
-            quarkusBomPlatformArtifact = platformArtifact;
-        }
-        final CombinedQuarkusPlatformDescriptor.Builder builder = CombinedQuarkusPlatformDescriptor.builder();
-        while (++i < descriptorDeps.size()) {
-            platformArtifact = descriptorDeps.get(i);
-            final QuarkusPlatformDescriptor descriptor = descriptorResolver
-                    .resolveFromJson(platformArtifact.getFile().toPath());
-            if (quarkusBomPlatformArtifact != null) {
-                if (!quarkusBomPlatformArtifact.getModuleVersion().getId().getVersion()
-                        .equals(descriptor.getQuarkusVersion())) {
-                    builder.addPlatform(descriptorResolver.resolveFromJson(quarkusBomPlatformArtifact.getFile().toPath()));
-                }
-                quarkusBomPlatformArtifact = null;
-            }
-            builder.addPlatform(descriptorResolver.resolveFromJson(platformArtifact.getFile().toPath()));
-        }
-        return builder.build();
-    }
-
-    @Internal
-    protected QuarkusProject getQuarkusProject() {
-
-        final QuarkusPlatformDescriptor platformDescriptor = platformDescriptor();
+        final GradleMessageWriter log = messageWriter();
+        final ExtensionCatalog catalog = extensionsCatalog(limitExtensionsToImportedPlatforms, log);
 
         final Path projectDirPath = getProject().getProjectDir().toPath();
         final Path rootProjectPath = getProject().getParent() != null ? getProject().getRootProject().getProjectDir().toPath()
@@ -187,15 +132,19 @@ protected QuarkusProject getQuarkusProject() {
         final BuildFile buildFile;
         if (Files.exists(rootProjectPath.resolve("settings.gradle.kts"))
                 && Files.exists(projectDirPath.resolve("build.gradle.kts"))) {
-            buildFile = new GradleKotlinProjectBuildFile(getProject(), platformDescriptor);
+            buildFile = new GradleKotlinProjectBuildFile(getProject(), catalog);
         } else if (Files.exists(rootProjectPath.resolve("settings.gradle"))
                 && Files.exists(projectDirPath.resolve("build.gradle"))) {
-            buildFile = new GradleGroovyProjectBuildFile(getProject(), platformDescriptor);
+            buildFile = new GradleGroovyProjectBuildFile(getProject(), catalog);
         } else {
             throw new GradleException(
                     "Mixed DSL is not supported. Both build and settings file need to use either Kotlin or Groovy DSL");
         }
-        return QuarkusProject.of(getProject().getProjectDir().toPath(), platformDescriptor, buildFile);
+        return QuarkusProjectHelper.getProject(getProject().getProjectDir().toPath(), catalog, buildFile, log);
+    }
+
+    protected GradleMessageWriter messageWriter() {
+        return new GradleMessageWriter(getProject().getLogger());
     }
 
     protected static URL toURL(String url) {
diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusRemoveExtension.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusRemoveExtension.java
index 1adf369f5c4154..0d11fa2f81c670 100644
--- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusRemoveExtension.java
+++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusRemoveExtension.java
@@ -39,7 +39,7 @@ public void removeExtension() {
                 .map(String::trim)
                 .collect(toSet());
         try {
-            new RemoveExtensions(getQuarkusProject())
+            new RemoveExtensions(getQuarkusProject(true))
                     .extensions(extensionsSet)
                     .execute();
         } catch (Exception e) {
diff --git a/devtools/maven/pom.xml b/devtools/maven/pom.xml
index 619bb19724c139..2f5aa084e1ed00 100644
--- a/devtools/maven/pom.xml
+++ b/devtools/maven/pom.xml
@@ -36,10 +36,6 @@
             <groupId>io.quarkus</groupId>
             <artifactId>quarkus-platform-descriptor-json</artifactId>
         </dependency>
-        <dependency>
-            <groupId>io.quarkus</groupId>
-            <artifactId>quarkus-platform-descriptor-resolver-json</artifactId>
-        </dependency>
         <dependency>
             <groupId>org.apache.maven</groupId>
             <artifactId>maven-plugin-api</artifactId>
diff --git a/devtools/maven/src/main/java/io/quarkus/maven/AddExtensionMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/AddExtensionMojo.java
index 3a0a32b6c120a5..1983653ef206d6 100644
--- a/devtools/maven/src/main/java/io/quarkus/maven/AddExtensionMojo.java
+++ b/devtools/maven/src/main/java/io/quarkus/maven/AddExtensionMojo.java
@@ -2,10 +2,8 @@
 
 import static java.util.stream.Collectors.toSet;
 
-import java.net.URL;
 import java.util.Arrays;
 import java.util.HashSet;
-import java.util.List;
 import java.util.Set;
 
 import org.apache.commons.lang3.StringUtils;
@@ -17,7 +15,6 @@
 import io.quarkus.devtools.commands.data.QuarkusCommandOutcome;
 import io.quarkus.devtools.messagewriter.MessageWriter;
 import io.quarkus.devtools.project.QuarkusProject;
-import io.quarkus.registry.DefaultExtensionRegistry;
 
 /**
  * Allow adding an extension to an existing pom.xml file.
@@ -40,12 +37,6 @@ public class AddExtensionMojo extends QuarkusProjectMojoBase {
     @Parameter(property = "extension")
     String extension;
 
-    /**
-     * The URL where the registry is.
-     */
-    @Parameter(property = "registry", alias = "quarkus.extension.registry")
-    List<URL> registries;
-
     @Override
     protected void validateParameters() throws MojoExecutionException {
         if ((StringUtils.isBlank(extension) && (extensions == null || extensions.isEmpty())) // None are set
@@ -69,9 +60,6 @@ public void doExecute(final QuarkusProject quarkusProject, final MessageWriter l
         try {
             AddExtensions addExtensions = new AddExtensions(quarkusProject)
                     .extensions(ext.stream().map(String::trim).collect(toSet()));
-            if (registries != null && !registries.isEmpty()) {
-                addExtensions.extensionRegistry(DefaultExtensionRegistry.fromURLs(registries));
-            }
             final QuarkusCommandOutcome outcome = addExtensions.execute();
             if (!outcome.isSuccess()) {
                 throw new MojoExecutionException("Unable to add extensions");
diff --git a/devtools/maven/src/main/java/io/quarkus/maven/CreateJBangMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/CreateJBangMojo.java
index 05ecc15b89efe2..347346062a4551 100644
--- a/devtools/maven/src/main/java/io/quarkus/maven/CreateJBangMojo.java
+++ b/devtools/maven/src/main/java/io/quarkus/maven/CreateJBangMojo.java
@@ -9,6 +9,7 @@
 import java.util.List;
 import java.util.Set;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.maven.plugin.AbstractMojo;
 import org.apache.maven.plugin.MojoExecutionException;
 import org.apache.maven.plugins.annotations.Component;
@@ -22,7 +23,12 @@
 import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver;
 import io.quarkus.devtools.commands.CreateJBangProject;
 import io.quarkus.devtools.commands.data.QuarkusCommandException;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
+import io.quarkus.devtools.messagewriter.MessageWriter;
+import io.quarkus.devtools.project.BuildTool;
+import io.quarkus.devtools.project.QuarkusProject;
+import io.quarkus.devtools.project.QuarkusProjectHelper;
+import io.quarkus.platform.tools.maven.MojoMessageWriter;
+import io.quarkus.registry.catalog.ExtensionCatalog;
 
 @Mojo(name = "create-jbang", requiresProject = false)
 public class CreateJBangMojo extends AbstractMojo {
@@ -89,12 +95,17 @@ public void execute() throws MojoExecutionException {
             throw new MojoExecutionException("Failed to initialize Maven artifact resolver", e);
         }
 
-        final QuarkusPlatformDescriptor platform = CreateUtils.resolvePlatformDescriptor(bomGroupId, bomArtifactId, bomVersion,
-                mvn, getLog());
-
-        final CreateJBangProject createJBangProject = new CreateJBangProject(projectDirPath, platform)
-                .extensions(extensions)
-                .setValue("noJBangWrapper", noJBangWrapper);
+        final MessageWriter log = new MojoMessageWriter(getLog());
+        final ExtensionCatalog catalog = CreateProjectMojo.resolveExtensionsCatalog(
+                StringUtils.defaultIfBlank(bomGroupId, null),
+                StringUtils.defaultIfBlank(bomArtifactId, null),
+                StringUtils.defaultIfBlank(bomVersion, null),
+                QuarkusProjectHelper.getCatalogResolver(mvn, log), mvn, log);
+
+        final CreateJBangProject createJBangProject = new CreateJBangProject(QuarkusProject.of(projectDirPath, catalog,
+                QuarkusProjectHelper.getResourceLoader(catalog, mvn), log, BuildTool.MAVEN))
+                        .extensions(extensions)
+                        .setValue("noJBangWrapper", noJBangWrapper);
 
         boolean success;
 
diff --git a/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java
index 25d8ea61527eb3..048a886fba53d5 100644
--- a/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java
+++ b/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java
@@ -1,31 +1,21 @@
 package io.quarkus.maven;
 
 import static org.fusesource.jansi.Ansi.ansi;
-import static org.twdata.maven.mojoexecutor.MojoExecutor.artifactId;
-import static org.twdata.maven.mojoexecutor.MojoExecutor.configuration;
-import static org.twdata.maven.mojoexecutor.MojoExecutor.element;
-import static org.twdata.maven.mojoexecutor.MojoExecutor.executeMojo;
-import static org.twdata.maven.mojoexecutor.MojoExecutor.executionEnvironment;
-import static org.twdata.maven.mojoexecutor.MojoExecutor.goal;
-import static org.twdata.maven.mojoexecutor.MojoExecutor.groupId;
-import static org.twdata.maven.mojoexecutor.MojoExecutor.name;
-import static org.twdata.maven.mojoexecutor.MojoExecutor.plugin;
-import static org.twdata.maven.mojoexecutor.MojoExecutor.version;
 
+import java.io.BufferedWriter;
 import java.io.File;
 import java.io.IOException;
+import java.io.StringWriter;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
-import java.util.Properties;
 import java.util.Set;
 import java.util.stream.Collectors;
 
 import org.apache.commons.lang3.StringUtils;
-import org.apache.maven.execution.DefaultMavenExecutionRequest;
-import org.apache.maven.execution.MavenExecutionRequest;
 import org.apache.maven.execution.MavenSession;
 import org.apache.maven.model.Model;
 import org.apache.maven.model.Parent;
@@ -35,10 +25,8 @@
 import org.apache.maven.plugins.annotations.Component;
 import org.apache.maven.plugins.annotations.Mojo;
 import org.apache.maven.plugins.annotations.Parameter;
-import org.apache.maven.project.DefaultProjectBuildingRequest;
 import org.apache.maven.project.MavenProject;
 import org.apache.maven.project.ProjectBuilder;
-import org.apache.maven.settings.Proxy;
 import org.eclipse.aether.RepositorySystem;
 import org.eclipse.aether.RepositorySystemSession;
 import org.eclipse.aether.impl.RemoteRepositoryManager;
@@ -47,13 +35,21 @@
 
 import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver;
 import io.quarkus.devtools.commands.CreateProject;
+import io.quarkus.devtools.messagewriter.MessageWriter;
 import io.quarkus.devtools.project.BuildTool;
+import io.quarkus.devtools.project.QuarkusProject;
+import io.quarkus.devtools.project.QuarkusProjectHelper;
 import io.quarkus.devtools.project.codegen.SourceType;
 import io.quarkus.maven.components.MavenVersionEnforcer;
 import io.quarkus.maven.components.Prompter;
 import io.quarkus.maven.utilities.MojoUtils;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
 import io.quarkus.platform.tools.ToolsUtils;
+import io.quarkus.platform.tools.maven.MojoMessageWriter;
+import io.quarkus.registry.ExtensionCatalogResolver;
+import io.quarkus.registry.RegistryResolutionException;
+import io.quarkus.registry.catalog.ExtensionCatalog;
+import io.quarkus.registry.catalog.Platform;
+import io.quarkus.registry.catalog.PlatformCatalog;
 
 /**
  * This goal helps in setting up Quarkus Maven project with quarkus-maven-plugin, with sensible defaults
@@ -78,9 +74,6 @@ public class CreateProjectMojo extends AbstractMojo {
     @Parameter(property = "projectVersion")
     private String projectVersion;
 
-    @Parameter(property = "legacyCodegen", defaultValue = "false")
-    private boolean legacyCodegen;
-
     /**
      * When true, do not include any example code in the generated Quarkus project.
      */
@@ -195,6 +188,15 @@ public class CreateProjectMojo extends AbstractMojo {
     @Override
     public void execute() throws MojoExecutionException {
 
+        // We detect the Maven version during the project generation to indicate the user immediately that the installed
+        // version may not be supported.
+        mavenVersionEnforcer.ensureMavenVersion(getLog(), session);
+        try {
+            Files.createDirectories(outputDirectory.toPath());
+        } catch (IOException e) {
+            throw new MojoExecutionException("Could not create directory " + outputDirectory, e);
+        }
+
         final MavenArtifactResolver mvn;
         try {
             mvn = MavenArtifactResolver.builder()
@@ -206,17 +208,14 @@ public void execute() throws MojoExecutionException {
         } catch (Exception e) {
             throw new MojoExecutionException("Failed to initialize Maven artifact resolver", e);
         }
-        final QuarkusPlatformDescriptor platform = CreateUtils.resolvePlatformDescriptor(bomGroupId, bomArtifactId, bomVersion,
-                mvn, getLog());
+        final MojoMessageWriter log = new MojoMessageWriter(getLog());
+        final ExtensionCatalogResolver catalogResolver = QuarkusProjectHelper.getCatalogResolver(mvn, log);
 
-        // We detect the Maven version during the project generation to indicate the user immediately that the installed
-        // version may not be supported.
-        mavenVersionEnforcer.ensureMavenVersion(getLog(), session);
-        try {
-            Files.createDirectories(outputDirectory.toPath());
-        } catch (IOException e) {
-            throw new MojoExecutionException("Could not create directory " + outputDirectory, e);
-        }
+        final ExtensionCatalog catalog = resolveExtensionsCatalog(
+                StringUtils.defaultIfBlank(bomGroupId, null),
+                StringUtils.defaultIfBlank(bomArtifactId, null),
+                StringUtils.defaultIfBlank(bomVersion, null),
+                catalogResolver, mvn, log);
 
         File projectRoot = outputDirectory;
         File pom = project != null ? project.getFile() : null;
@@ -272,8 +271,9 @@ public void execute() throws MojoExecutionException {
             final SourceType sourceType = CreateProject.determineSourceType(extensions);
             sanitizeOptions(sourceType);
 
-            final CreateProject createProject = new CreateProject(projectDirPath, platform)
-                    .buildTool(buildToolEnum)
+            QuarkusProject newProject = QuarkusProject.of(projectDirPath, catalog,
+                    QuarkusProjectHelper.getResourceLoader(catalog, mvn), log, buildToolEnum);
+            final CreateProject createProject = new CreateProject(newProject)
                     .groupId(projectGroupId)
                     .artifactId(projectArtifactId)
                     .version(projectVersion)
@@ -282,7 +282,6 @@ public void execute() throws MojoExecutionException {
                     .packageName(packageName)
                     .extensions(extensions)
                     .overrideExamples(examples)
-                    .legacyCodegen(legacyCodegen)
                     .noExamples(noExamples);
             if (path != null) {
                 createProject.setValue("path", path);
@@ -304,14 +303,6 @@ public void execute() throws MojoExecutionException {
                 MojoUtils.write(parentPomModel, pom);
                 MojoUtils.write(subModulePomModel, subModulePomFile);
             }
-            if (legacyCodegen) {
-                File createdDependenciesBuildFile = new File(projectRoot, buildToolEnum.getDependenciesFile());
-                if (BuildTool.MAVEN.equals(buildToolEnum)) {
-                    createMavenWrapper(createdDependenciesBuildFile, ToolsUtils.readQuarkusProperties(platform));
-                } else if (BuildTool.GRADLE.equals(buildToolEnum) || BuildTool.GRADLE_KOTLIN_DSL.equals(buildToolEnum)) {
-                    createGradleWrapper(platform, projectDirPath);
-                }
-            }
         } catch (Exception e) {
             throw new MojoExecutionException("Failed to generate Quarkus project", e);
         }
@@ -323,80 +314,70 @@ public void execute() throws MojoExecutionException {
         }
     }
 
-    private void createGradleWrapper(QuarkusPlatformDescriptor platform, Path projectDirPath) {
-        try {
-            Files.createDirectories(projectDirPath.resolve("gradle/wrapper"));
-
-            for (String filename : CreateUtils.GRADLE_WRAPPER_FILES) {
-                byte[] fileContent = platform.loadResource(CreateUtils.GRADLE_WRAPPER_PATH + '/' + filename,
-                        is -> {
-                            byte[] buffer = new byte[is.available()];
-                            is.read(buffer);
-                            return buffer;
-                        });
-                final Path destination = projectDirPath.resolve(filename);
-                Files.write(destination, fileContent);
-            }
-
-            projectDirPath.resolve("gradlew").toFile().setExecutable(true);
-            projectDirPath.resolve("gradlew.bat").toFile().setExecutable(true);
-        } catch (IOException e) {
-            getLog().error("Unable to copy Gradle wrapper from platform descriptor", e);
+    static ExtensionCatalog resolveExtensionsCatalog(String groupId, String artifactId, String version,
+            ExtensionCatalogResolver catalogResolver, MavenArtifactResolver artifactResolver, MessageWriter log)
+            throws MojoExecutionException {
+
+        if (!catalogResolver.hasRegistries()) {
+            // TODO: this should normally result in an error, however for the time being
+            // until we get the registry service up and running this will allow
+            // a fall back to the legacy way of resolving the default platform catalog directly
+            return ToolsUtils.resolvePlatformDescriptorDirectly(groupId, artifactId,
+                    version == null ? CreateUtils.resolvePluginInfo(CreateUtils.class).getVersion() : version, artifactResolver,
+                    log);
         }
-    }
 
-    private void createMavenWrapper(File createdPomFile, Properties props) {
         try {
-            // we need to modify the maven environment used by the wrapper plugin since the project could have been
-            // created in a directory other than the current
-            MavenProject newProject = projectBuilder.build(
-                    createdPomFile, new DefaultProjectBuildingRequest(session.getProjectBuildingRequest())).getProject();
-
-            MavenExecutionRequest newExecutionRequest = DefaultMavenExecutionRequest.copy(session.getRequest());
-            newExecutionRequest.setBaseDirectory(createdPomFile.getParentFile());
-
-            MavenSession newSession = new MavenSession(session.getContainer(), session.getRepositorySession(),
-                    newExecutionRequest, session.getResult());
-            newSession.setCurrentProject(newProject);
-
-            setProxySystemPropertiesFromSession();
-
-            executeMojo(
-                    plugin(
-                            groupId("io.takari"),
-                            artifactId("maven"),
-                            version(ToolsUtils.getMavenWrapperVersion(props))),
-                    goal("wrapper"),
-                    configuration(
-                            element(name("maven"), ToolsUtils.getProposedMavenVersion(props))),
-                    executionEnvironment(
-                            newProject,
-                            newSession,
-                            pluginManager));
-        } catch (Exception e) {
-            // no reason to fail if the wrapper could not be created
-            getLog().error("Unable to install the Maven wrapper (./mvnw) in the project", e);
-        }
-    }
-
-    private void setProxySystemPropertiesFromSession() {
-        List<Proxy> proxiesFromSession = session.getRequest().getProxies();
-        // - takari maven uses https to download the maven wrapper
-        // - don't do anything if proxy system property is already set
-        if (!proxiesFromSession.isEmpty() && System.getProperty("https.proxyHost") == null) {
-
-            // use the first active proxy for setting the system properties
-            proxiesFromSession.stream()
-                    .filter(Proxy::isActive)
-                    .findFirst()
-                    .ifPresent(proxy -> {
-                        // note: a http proxy _is_ usable as https.proxyHost
-                        System.setProperty("https.proxyHost", proxy.getHost());
-                        System.setProperty("https.proxyPort", String.valueOf(proxy.getPort()));
-                        if (proxy.getNonProxyHosts() != null) {
-                            System.setProperty("http.nonProxyHosts", proxy.getNonProxyHosts());
-                        }
-                    });
+            if (groupId == null && artifactId == null && version == null) {
+                return catalogResolver.resolveExtensionCatalog();
+            }
+            final PlatformCatalog platformsCatalog = catalogResolver.resolvePlatformCatalog();
+            if (platformsCatalog == null) {
+                throw new MojoExecutionException(
+                        "No platforms are available. Please make sure your .quarkus/config.yaml configuration includes proper extensions registry configuration");
+            }
+            ArtifactCoords matchedBom = null;
+            List<ArtifactCoords> matchedBoms = null;
+            for (Platform p : platformsCatalog.getPlatforms()) {
+                final ArtifactCoords bom = p.getBom();
+                if (version != null && !bom.getVersion().equals(version)) {
+                    continue;
+                }
+                if (artifactId != null && !bom.getArtifactId().equals(artifactId)) {
+                    continue;
+                }
+                if (groupId != null && !bom.getGroupId().equals(groupId)) {
+                    continue;
+                }
+                if (matchedBom != null) {
+                    if (matchedBoms == null) {
+                        matchedBoms = new ArrayList<>();
+                        matchedBoms.add(matchedBom);
+                    }
+                    matchedBoms.add(bom);
+                } else {
+                    matchedBom = bom;
+                }
+            }
+            if (matchedBoms != null) {
+                StringWriter buf = new StringWriter();
+                buf.append("Multiple platforms were matching the requested platform BOM coordinates ");
+                buf.append(groupId == null ? "*" : groupId).append(':');
+                buf.append(artifactId == null ? "*" : artifactId).append(':');
+                buf.append(version == null ? "*" : version).append(": ");
+                try (BufferedWriter writer = new BufferedWriter(buf)) {
+                    for (ArtifactCoords bom : matchedBoms) {
+                        writer.newLine();
+                        writer.append("- ").append(bom.toString());
+                    }
+                } catch (IOException e) {
+                    //
+                }
+                throw new MojoExecutionException(buf.toString());
+            }
+            return catalogResolver.resolveExtensionCatalog(Arrays.asList(matchedBom));
+        } catch (RegistryResolutionException e) {
+            throw new MojoExecutionException("Failed to resolve the extensions catalog", e);
         }
     }
 
@@ -434,38 +415,18 @@ private void askTheUserForMissingValues() throws MojoExecutionException {
                         DEFAULT_VERSION);
             }
 
-            if (legacyCodegen) {
-                if (StringUtils.isBlank(className)) {
-                    // Ask the user if he want to create a resource
-                    String answer = prompter.promptWithDefaultValue("Do you want to create a REST resource? (y/n)", "no");
-                    if (isTrueOrYes(answer)) {
-                        String defaultResourceName = projectGroupId.replace("-", ".")
-                                .replace("_", ".") + ".HelloResource";
-                        className = prompter.promptWithDefaultValue("Set the resource classname", defaultResourceName);
-                        if (StringUtils.isBlank(path)) {
-                            path = prompter.promptWithDefaultValue("Set the resource path ",
-                                    CreateUtils.getDerivedPath(className));
-                        }
-                    } else {
-                        className = null;
-                        path = null;
-                    }
-                }
-            } else {
-                if (examples.isEmpty()) {
-                    if (extensions.isEmpty()) {
-                        extensions = Arrays
-                                .stream(prompter
-                                        .promptWithDefaultValue("What extensions do you wish to add (comma separated list)",
-                                                DEFAULT_EXTENSIONS)
-                                        .split(","))
-                                .map(String::trim).filter(StringUtils::isNotEmpty)
-                                .collect(Collectors.toSet());
-                    }
-                    String answer = prompter.promptWithDefaultValue(
-                            "Do you want example code to get started (yes), or just an empty project (no)", "yes");
-                    noExamples = answer.startsWith("n");
+            if (examples.isEmpty()) {
+                if (extensions.isEmpty()) {
+                    extensions = Arrays
+                            .stream(prompter
+                                    .promptWithDefaultValue("What extensions do you wish to add (comma separated list)",
+                                            DEFAULT_EXTENSIONS)
+                                    .split(","))
+                            .map(String::trim).filter(StringUtils::isNotEmpty).collect(Collectors.toSet());
                 }
+                String answer = prompter.promptWithDefaultValue(
+                        "Do you want example code to get started (yes), or just an empty project (no)", "yes");
+                noExamples = answer.startsWith("n");
             }
         } catch (IOException e) {
             throw new MojoExecutionException("Unable to get user input", e);
@@ -478,14 +439,6 @@ private boolean shouldUseDefaults() {
 
     }
 
-    private boolean isTrueOrYes(String answer) {
-        if (answer == null) {
-            return false;
-        }
-        String content = answer.trim().toLowerCase();
-        return "true".equalsIgnoreCase(content) || "yes".equalsIgnoreCase(content) || "y".equalsIgnoreCase(content);
-    }
-
     private void sanitizeOptions(SourceType sourceType) {
         if (className != null) {
             className = sourceType.stripExtensionFrom(className);
@@ -529,5 +482,4 @@ private void printUserInstructions(File root) {
         getLog().info("========================================================================================");
         getLog().info("");
     }
-
 }
diff --git a/devtools/maven/src/main/java/io/quarkus/maven/CreateUtils.java b/devtools/maven/src/main/java/io/quarkus/maven/CreateUtils.java
index e1d207ef041236..1b5b8896cc3d22 100644
--- a/devtools/maven/src/main/java/io/quarkus/maven/CreateUtils.java
+++ b/devtools/maven/src/main/java/io/quarkus/maven/CreateUtils.java
@@ -9,28 +9,20 @@
 import javax.xml.parsers.DocumentBuilderFactory;
 
 import org.apache.commons.lang3.StringUtils;
-import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
 import org.apache.maven.model.Plugin;
 import org.apache.maven.plugin.MojoExecutionException;
-import org.apache.maven.plugin.logging.Log;
 import org.w3c.dom.Document;
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 
-import io.quarkus.bootstrap.resolver.BootstrapAppModelResolver;
-import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver;
 import io.quarkus.bootstrap.util.ZipUtils;
 import io.quarkus.maven.utilities.MojoUtils;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
-import io.quarkus.platform.descriptor.resolver.json.QuarkusJsonPlatformDescriptorResolver;
-import io.quarkus.platform.tools.config.QuarkusPlatformConfig;
-import io.quarkus.platform.tools.maven.MojoMessageWriter;
 
 public final class CreateUtils {
 
     public static final String DEFAULT_PLATFORM_BOM_GROUP_ID = "io.quarkus";
     public static final String QUARKUS_CORE_BOM_ARTIFACT_ID = "quarkus-bom";
-    public static final String DEFAULT_PLATFORM_BOM_ARTIFACT_ID = QUARKUS_CORE_BOM_ARTIFACT_ID;
+    public static final String DEFAULT_PLATFORM_BOM_ARTIFACT_ID = "quarkus-universe-bom";
 
     public static final String GRADLE_WRAPPER_PATH = "gradle-wrapper";
     public static final String[] GRADLE_WRAPPER_FILES = new String[] {
@@ -44,66 +36,6 @@ private CreateUtils() {
         //Not to be constructed
     }
 
-    private static boolean isVersionRange(String versionStr) {
-        if (versionStr == null || versionStr.isEmpty()) {
-            return false;
-        }
-        char c = versionStr.charAt(0);
-        if (c == '[' || c == '(') {
-            return true;
-        }
-        c = versionStr.charAt(versionStr.length() - 1);
-        if (c == ']' || c == ')') {
-            return true;
-        }
-        return versionStr.indexOf(',') >= 0;
-    }
-
-    static QuarkusPlatformDescriptor setGlobalPlatformDescriptor(final String bomGroupId, final String bomArtifactId,
-            final String bomVersion,
-            MavenArtifactResolver mvn, Log log) throws MojoExecutionException {
-        final QuarkusPlatformDescriptor platform = resolvePlatformDescriptor(bomGroupId, bomArtifactId, bomVersion, mvn, log);
-        QuarkusPlatformConfig.defaultConfigBuilder().setPlatformDescriptor(platform).build();
-        return platform;
-    }
-
-    static QuarkusPlatformDescriptor resolvePlatformDescriptor(final String bomGroupId, final String bomArtifactId,
-            final String bomVersion, MavenArtifactResolver mvn, Log log) throws MojoExecutionException {
-        final QuarkusJsonPlatformDescriptorResolver platformResolver = QuarkusJsonPlatformDescriptorResolver.newInstance()
-                .setMessageWriter(new MojoMessageWriter(log))
-                .setArtifactResolver(new BootstrapAppModelResolver(mvn));
-
-        String groupId = StringUtils.defaultIfBlank(bomGroupId, null);
-        String artifactId = StringUtils.defaultIfBlank(bomArtifactId, null);
-        String version = StringUtils.defaultIfBlank(bomVersion, null);
-
-        if (version == null) {
-            if (CreateUtils.QUARKUS_CORE_BOM_ARTIFACT_ID.equals(artifactId)) {
-                version = resolvePluginInfo(CreateUtils.class).getVersion();
-            } else if ((groupId == null && artifactId == null) || ("quarkus-universe-bom".equals(artifactId))) {
-                String baseVersion = resolvePluginInfo(CreateUtils.class).getVersion();
-                DefaultArtifactVersion pluginVersion = new DefaultArtifactVersion(baseVersion);
-                int majorVer = pluginVersion.getMajorVersion();
-                int minorVer = pluginVersion.getMinorVersion();
-                version = "[" + majorVer + "." + minorVer + "-alpha, " + majorVer + "." + (minorVer + 1) + "-alpha)";
-            }
-        }
-
-        final QuarkusPlatformDescriptor platform;
-        if (version == null) {
-            if (artifactId == null && groupId == null) {
-                platform = platformResolver.resolve();
-            } else {
-                platform = platformResolver.resolveLatestFromBom(groupId, artifactId, null);
-            }
-        } else if (isVersionRange(version)) {
-            platform = platformResolver.resolveLatestFromBom(groupId, artifactId, version);
-        } else {
-            platform = platformResolver.resolveFromBom(groupId, artifactId, version);
-        }
-        return platform;
-    }
-
     public static String getDerivedPath(String className) {
         String[] resourceClassName = StringUtils.splitByCharacterTypeCamelCase(
                 className.substring(className.lastIndexOf(".") + 1));
diff --git a/devtools/maven/src/main/java/io/quarkus/maven/ListExtensionsMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/ListExtensionsMojo.java
index 7d3bc1a7726642..8ab5df047dc3c7 100644
--- a/devtools/maven/src/main/java/io/quarkus/maven/ListExtensionsMojo.java
+++ b/devtools/maven/src/main/java/io/quarkus/maven/ListExtensionsMojo.java
@@ -1,8 +1,5 @@
 package io.quarkus.maven;
 
-import java.net.URL;
-import java.util.List;
-
 import org.apache.maven.plugin.MojoExecutionException;
 import org.apache.maven.plugins.annotations.Mojo;
 import org.apache.maven.plugins.annotations.Parameter;
@@ -10,7 +7,6 @@
 import io.quarkus.devtools.commands.ListExtensions;
 import io.quarkus.devtools.messagewriter.MessageWriter;
 import io.quarkus.devtools.project.QuarkusProject;
-import io.quarkus.registry.DefaultExtensionRegistry;
 
 /**
  * List the available extensions.
@@ -46,12 +42,6 @@ public class ListExtensionsMojo extends QuarkusProjectMojoBase {
     @Parameter(property = "installed", defaultValue = "false")
     protected boolean installed;
 
-    /**
-     * The extension registry URLs
-     */
-    @Parameter(property = "registry", alias = "quarkus.extension.registry")
-    List<URL> registries;
-
     @Override
     public void doExecute(final QuarkusProject quarkusProject, final MessageWriter log) throws MojoExecutionException {
         try {
@@ -60,9 +50,6 @@ public void doExecute(final QuarkusProject quarkusProject, final MessageWriter l
                     .format(format)
                     .search(searchPattern)
                     .installed(installed);
-            if (registries != null && !registries.isEmpty()) {
-                listExtensions.extensionRegistry(DefaultExtensionRegistry.fromURLs(registries));
-            }
             listExtensions.execute();
         } catch (Exception e) {
             throw new MojoExecutionException("Failed to list extensions", e);
diff --git a/devtools/maven/src/main/java/io/quarkus/maven/ListPlatformsMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/ListPlatformsMojo.java
new file mode 100644
index 00000000000000..8a75f1bcc1a75a
--- /dev/null
+++ b/devtools/maven/src/main/java/io/quarkus/maven/ListPlatformsMojo.java
@@ -0,0 +1,43 @@
+package io.quarkus.maven;
+
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+
+import io.quarkus.devtools.commands.ListPlatforms;
+import io.quarkus.devtools.messagewriter.MessageWriter;
+import io.quarkus.devtools.project.QuarkusProject;
+import io.quarkus.registry.Constants;
+
+/**
+ * List imported and optionally other platforms available for the project.
+ */
+@Mojo(name = "list-platforms", requiresProject = false)
+public class ListPlatformsMojo extends QuarkusProjectMojoBase {
+
+    /**
+     * List the already installed extensions
+     */
+    @Parameter(property = "installed", defaultValue = "false")
+    protected boolean installed;
+
+    @Override
+    public void doExecute(final QuarkusProject quarkusProject, final MessageWriter log) throws MojoExecutionException {
+        if (installed) {
+            getImportedPlatforms().forEach(coords -> {
+                final StringBuilder buf = new StringBuilder();
+                buf.append(coords.getGroupId()).append(":")
+                        .append(coords.getArtifactId().substring(0,
+                                coords.getArtifactId().length() - Constants.PLATFORM_DESCRIPTOR_ARTIFACT_ID_SUFFIX.length()))
+                        .append("::pom:").append(coords.getVersion());
+                log.info(buf.toString());
+            });
+            return;
+        }
+        try {
+            new ListPlatforms(quarkusProject).execute();
+        } catch (Exception e) {
+            throw new MojoExecutionException("Failed to list platforms", e);
+        }
+    }
+}
diff --git a/devtools/maven/src/main/java/io/quarkus/maven/ListUpdatesMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/ListUpdatesMojo.java
new file mode 100644
index 00000000000000..eee1e9f479c496
--- /dev/null
+++ b/devtools/maven/src/main/java/io/quarkus/maven/ListUpdatesMojo.java
@@ -0,0 +1,93 @@
+package io.quarkus.maven;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+
+import io.quarkus.devtools.messagewriter.MessageWriter;
+import io.quarkus.devtools.project.QuarkusProject;
+import io.quarkus.registry.Constants;
+import io.quarkus.registry.RegistryResolutionException;
+import io.quarkus.registry.catalog.Platform;
+import io.quarkus.registry.catalog.PlatformCatalog;
+
+/**
+ * List updates available for the project
+ */
+@Mojo(name = "list-updates", requiresProject = false)
+public class ListUpdatesMojo extends QuarkusProjectMojoBase {
+
+    /**
+     * List the already installed extensions
+     */
+    @Parameter(property = "installed", defaultValue = "false")
+    protected boolean installed;
+
+    @Override
+    public void doExecute(final QuarkusProject quarkusProject, final MessageWriter log) throws MojoExecutionException {
+
+        final PlatformCatalog catalog;
+        try {
+            catalog = getExtensionCatalogResolver().resolvePlatformCatalog();
+        } catch (RegistryResolutionException e) {
+            throw new MojoExecutionException("Failed to resolve the Quarkus platform catalog", e);
+        }
+        final List<Platform> platforms = catalog == null ? Collections.emptyList() : catalog.getPlatforms();
+        if (platforms.isEmpty()) {
+            return;
+        }
+        List<ArtifactCoords> latestList = new ArrayList<>(platforms.size());
+        for (Platform p : platforms) {
+            final ArtifactCoords bom = p.getBom();
+            latestList.add(bom);
+        }
+
+        final List<ArtifactCoords> platformJsons = getImportedPlatforms();
+        final List<ArtifactCoords> importedPlatformBoms = new ArrayList<>(platformJsons.size());
+        for (ArtifactCoords descriptor : platformJsons) {
+            final ArtifactCoords importedBom = new ArtifactCoords(descriptor.getGroupId(),
+                    descriptor.getArtifactId().substring(0,
+                            descriptor.getArtifactId().length() - Constants.PLATFORM_DESCRIPTOR_ARTIFACT_ID_SUFFIX.length()),
+                    null, "pom", descriptor.getVersion());
+            if (!latestList.remove(importedBom)) {
+                importedPlatformBoms.add(importedBom);
+            }
+        }
+
+        final Map<ArtifactKey, String> latest = new HashMap<>(platforms.size());
+        for (ArtifactCoords latestBom : latestList) {
+            latest.put(latestBom.getKey(), latestBom.getVersion());
+        }
+
+        log.info("Available Quarkus platform updates:");
+        final StringBuilder buf = new StringBuilder(0);
+        for (ArtifactCoords importedBom : importedPlatformBoms) {
+            final ArtifactKey key = importedBom.getKey();
+            final String update = latest.get(key);
+            if (update == null || importedBom.getVersion().equals(update)) {
+                continue;
+            }
+            buf.setLength(0);
+            buf.append(key.getGroupId()).append(':').append(key.getArtifactId());
+            for (int i = buf.length(); i < 45; ++i) {
+                buf.append(' ');
+            }
+            buf.append(importedBom.getVersion());
+            for (int i = buf.length(); i < 60; ++i) {
+                buf.append(' ');
+            }
+            buf.append(" -> ").append(update);
+            log.info(buf.toString());
+        }
+
+        if (buf.length() == 0) {
+            log.info("No updates yet, ping @gsmet");
+        }
+    }
+}
diff --git a/devtools/maven/src/main/java/io/quarkus/maven/MavenProjectBuildFile.java b/devtools/maven/src/main/java/io/quarkus/maven/MavenProjectBuildFile.java
index 8fbf8b6aaa18e0..baa4d52148c5d0 100644
--- a/devtools/maven/src/main/java/io/quarkus/maven/MavenProjectBuildFile.java
+++ b/devtools/maven/src/main/java/io/quarkus/maven/MavenProjectBuildFile.java
@@ -23,7 +23,7 @@
 import io.quarkus.devtools.project.BuildTool;
 import io.quarkus.devtools.project.buildfile.BuildFile;
 import io.quarkus.maven.utilities.MojoUtils;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
+import io.quarkus.registry.catalog.ExtensionCatalog;
 
 public class MavenProjectBuildFile extends BuildFile {
 
@@ -37,11 +37,11 @@ public class MavenProjectBuildFile extends BuildFile {
     protected List<AppArtifactCoords> managedDependencies;
     protected Model model;
 
-    public MavenProjectBuildFile(Path projectDirPath, QuarkusPlatformDescriptor platformDescriptor, Supplier<Model> model,
+    public MavenProjectBuildFile(Path projectDirPath, ExtensionCatalog extensionsCatalog, Supplier<Model> model,
             Supplier<List<org.eclipse.aether.graph.Dependency>> projectDeps,
             Supplier<List<org.eclipse.aether.graph.Dependency>> projectManagedDeps,
             Properties projectProps) {
-        super(projectDirPath, platformDescriptor);
+        super(projectDirPath, extensionsCatalog);
         this.modelSupplier = model;
         this.projectDepsSupplier = projectDeps;
         this.projectManagedDepsSupplier = projectManagedDeps;
diff --git a/devtools/maven/src/main/java/io/quarkus/maven/QuarkusProjectMojoBase.java b/devtools/maven/src/main/java/io/quarkus/maven/QuarkusProjectMojoBase.java
index 9b451fc6c573bf..e0dcbcd077aff0 100644
--- a/devtools/maven/src/main/java/io/quarkus/maven/QuarkusProjectMojoBase.java
+++ b/devtools/maven/src/main/java/io/quarkus/maven/QuarkusProjectMojoBase.java
@@ -1,6 +1,8 @@
 package io.quarkus.maven;
 
+import java.io.BufferedWriter;
 import java.io.IOException;
+import java.io.StringWriter;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
@@ -8,9 +10,6 @@
 import java.util.List;
 
 import org.apache.maven.model.Dependency;
-import org.apache.maven.model.DependencyManagement;
-import org.apache.maven.model.Model;
-import org.apache.maven.model.Parent;
 import org.apache.maven.plugin.AbstractMojo;
 import org.apache.maven.plugin.MojoExecutionException;
 import org.apache.maven.plugins.annotations.Component;
@@ -25,21 +24,22 @@
 import org.eclipse.aether.impl.RemoteRepositoryManager;
 import org.eclipse.aether.repository.RemoteRepository;
 import org.eclipse.aether.resolution.ArtifactDescriptorResult;
-import org.eclipse.aether.resolution.ArtifactResult;
 
 import io.quarkus.bootstrap.BootstrapConstants;
-import io.quarkus.bootstrap.resolver.BootstrapAppModelResolver;
 import io.quarkus.bootstrap.resolver.maven.BootstrapMavenException;
 import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver;
-import io.quarkus.bootstrap.resolver.maven.workspace.ModelUtils;
 import io.quarkus.devtools.messagewriter.MessageWriter;
 import io.quarkus.devtools.project.BuildTool;
 import io.quarkus.devtools.project.QuarkusProject;
-import io.quarkus.platform.descriptor.CombinedQuarkusPlatformDescriptor;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
-import io.quarkus.platform.descriptor.resolver.json.QuarkusJsonPlatformDescriptorResolver;
+import io.quarkus.devtools.project.QuarkusProjectHelper;
+import io.quarkus.platform.descriptor.loader.json.ClassPathResourceLoader;
 import io.quarkus.platform.tools.ToolsConstants;
+import io.quarkus.platform.tools.ToolsUtils;
 import io.quarkus.platform.tools.maven.MojoMessageWriter;
+import io.quarkus.registry.ExtensionCatalogResolver;
+import io.quarkus.registry.RegistryResolutionException;
+import io.quarkus.registry.catalog.ExtensionCatalog;
+import io.quarkus.registry.catalog.Platform;
 
 public abstract class QuarkusProjectMojoBase extends AbstractMojo {
 
@@ -55,7 +55,7 @@ public abstract class QuarkusProjectMojoBase extends AbstractMojo {
     @Parameter(defaultValue = "${project.remoteProjectRepositories}", readonly = true, required = true)
     protected List<RemoteRepository> repos;
 
-    @Parameter(property = "bomGroupId", defaultValue = ToolsConstants.DEFAULT_PLATFORM_BOM_GROUP_ID)
+    @Parameter(property = "bomGroupId", required = false)
     private String bomGroupId;
 
     @Parameter(property = "bomArtifactId", required = false)
@@ -67,9 +67,13 @@ public abstract class QuarkusProjectMojoBase extends AbstractMojo {
     @Component
     RemoteRepositoryManager remoteRepositoryManager;
 
+    private List<ArtifactCoords> importedPlatforms;
+
     private Artifact projectArtifact;
     private ArtifactDescriptorResult projectDescr;
     private MavenArtifactResolver artifactResolver;
+    private ExtensionCatalogResolver catalogResolver;
+    private MessageWriter log;
 
     @Override
     public void execute() throws MojoExecutionException {
@@ -77,7 +81,6 @@ public void execute() throws MojoExecutionException {
         // Validate Mojo parameters
         validateParameters();
 
-        final MessageWriter log = new MojoMessageWriter(getLog());
         final Path projectDirPath = baseDir();
         BuildTool buildTool = QuarkusProject.resolveExistingProjectBuildTool(projectDirPath);
         if (buildTool == null) {
@@ -85,11 +88,13 @@ public void execute() throws MojoExecutionException {
             buildTool = BuildTool.MAVEN;
         }
 
-        final QuarkusPlatformDescriptor platformDescriptor = resolvePlatformDescriptor(log);
+        final ExtensionCatalog catalog = resolveExtensionsCatalog();
+        final ClassPathResourceLoader codestartsResourceLoader = QuarkusProjectHelper.getResourceLoader(catalog,
+                artifactResolver());
         final QuarkusProject quarkusProject;
         if (BuildTool.MAVEN.equals(buildTool) && project.getFile() != null) {
-            quarkusProject = QuarkusProject.of(baseDir(), platformDescriptor,
-                    new MavenProjectBuildFile(baseDir(), platformDescriptor, () -> project.getOriginalModel(),
+            quarkusProject = QuarkusProject.of(baseDir(), catalog, codestartsResourceLoader, getMessageWriter(),
+                    new MavenProjectBuildFile(baseDir(), catalog, () -> project.getOriginalModel(),
                             () -> {
                                 try {
                                     return projectDependencies();
@@ -106,10 +111,14 @@ public void execute() throws MojoExecutionException {
                             },
                             project.getModel().getProperties()));
         } else {
-            quarkusProject = QuarkusProject.of(baseDir(), platformDescriptor, buildTool);
+            quarkusProject = QuarkusProject.of(baseDir(), catalog, codestartsResourceLoader, log, buildTool);
         }
 
-        doExecute(quarkusProject, log);
+        doExecute(quarkusProject, getMessageWriter());
+    }
+
+    protected MessageWriter getMessageWriter() {
+        return log == null ? log = new MojoMessageWriter(getLog()) : log;
     }
 
     private ArtifactDescriptorResult projectDescriptor() throws MojoExecutionException {
@@ -128,146 +137,133 @@ protected Path baseDir() {
                 : project.getBasedir().toPath();
     }
 
-    private QuarkusPlatformDescriptor resolvePlatformDescriptor(final MessageWriter log) throws MojoExecutionException {
-        // Resolve and setup the platform descriptor
-        try {
-            final MavenArtifactResolver mvn = artifactResolver();
-            if (project.getFile() != null) {
-                List<Artifact> descrArtifactList = collectQuarkusPlatformDescriptors(log, mvn);
-                if (descrArtifactList.isEmpty()) {
-                    descrArtifactList = resolveLegacyQuarkusPlatformDescriptors(log, mvn);
-                }
-                if (!descrArtifactList.isEmpty()) {
-                    final QuarkusJsonPlatformDescriptorResolver descriptorResolver = QuarkusJsonPlatformDescriptorResolver
-                            .newInstance()
-                            .setArtifactResolver(new BootstrapAppModelResolver(mvn))
-                            .setMessageWriter(log);
-
-                    if (descrArtifactList.size() == 1) {
-                        return descriptorResolver.resolveFromJson(descrArtifactList.get(0).getFile().toPath());
-                    }
+    protected boolean isLimitExtensionsToImportedPlatforms() {
+        return false;
+    }
 
-                    // Typically, quarkus-bom platform will appear first.
-                    // The descriptors that are generated today are not fragmented and include everything
-                    // a platform offers. Which means if the quarkus-bom platform appears first and its version
-                    // matches the Quarkus core version of the platform built on top of the quarkus-bom
-                    // (e.g. quarkus-universe-bom) the quarkus-bom platform can be skipped,
-                    // since it will already be included in the platform that's built on top of it
-                    int i = 0;
-                    Artifact platformArtifact = descrArtifactList.get(0);
-                    final String quarkusBomPlatformArtifactId = "quarkus-bom"
-                            + BootstrapConstants.PLATFORM_DESCRIPTOR_ARTIFACT_ID_SUFFIX;
-                    Artifact quarkusBomPlatformArtifact = null;
-                    if (quarkusBomPlatformArtifactId.equals(platformArtifact.getArtifactId())) {
-                        quarkusBomPlatformArtifact = platformArtifact;
-                    }
-                    final CombinedQuarkusPlatformDescriptor.Builder builder = CombinedQuarkusPlatformDescriptor.builder();
-                    while (++i < descrArtifactList.size()) {
-                        platformArtifact = descrArtifactList.get(i);
-                        final QuarkusPlatformDescriptor descriptor = descriptorResolver
-                                .resolveFromJson(platformArtifact.getFile().toPath());
-                        if (quarkusBomPlatformArtifact != null) {
-                            if (!quarkusBomPlatformArtifact.getVersion().equals(descriptor.getQuarkusVersion())) {
-                                builder.addPlatform(
-                                        descriptorResolver.resolveFromJson(quarkusBomPlatformArtifact.getFile().toPath()));
+    private ExtensionCatalog resolveExtensionsCatalog() throws MojoExecutionException {
+        final ExtensionCatalogResolver catalogResolver = getExtensionCatalogResolver();
+        if (catalogResolver.hasRegistries()) {
+            try {
+                return isLimitExtensionsToImportedPlatforms()
+                        ? catalogResolver.resolveExtensionCatalog(getImportedPlatforms())
+                        : catalogResolver.resolveExtensionCatalog(getQuarkusCoreVersion());
+            } catch (Exception e) {
+                throw new MojoExecutionException("Failed to resolve the Quarkus extensions catalog", e);
+            }
+        }
+        return ToolsUtils.mergePlatforms(collectImportedPlatforms(), artifactResolver());
+    }
+
+    protected ExtensionCatalogResolver getExtensionCatalogResolver() throws MojoExecutionException {
+        return catalogResolver == null
+                ? catalogResolver = QuarkusProjectHelper.getCatalogResolver(artifactResolver(), getMessageWriter())
+                : catalogResolver;
+    }
+
+    protected List<ArtifactCoords> getImportedPlatforms() throws MojoExecutionException {
+        if (importedPlatforms == null) {
+            if (project.getFile() == null) {
+                if (bomGroupId == null && bomArtifactId == null && bomVersion == null) {
+                    return Collections.emptyList();
+                }
+                if (bomGroupId == null) {
+                    bomGroupId = ToolsConstants.DEFAULT_PLATFORM_BOM_GROUP_ID;
+                }
+                final ExtensionCatalogResolver catalogResolver = getExtensionCatalogResolver();
+                ArtifactCoords platformBom = null;
+                List<ArtifactCoords> matches = null;
+                try {
+                    for (Platform p : catalogResolver.resolvePlatformCatalog().getPlatforms()) {
+                        final ArtifactCoords bom = p.getBom();
+                        if (bomGroupId != null && !bom.getGroupId().equals(bomGroupId)) {
+                            continue;
+                        }
+                        if (bomArtifactId != null && !bom.getArtifactId().equals(bomArtifactId)) {
+                            continue;
+                        }
+                        if (bomVersion != null && !bom.getVersion().equals(bomVersion)) {
+                            continue;
+                        }
+                        if (platformBom == null) {
+                            platformBom = bom;
+                        } else {
+                            if (matches == null) {
+                                matches = new ArrayList<>();
+                                matches.add(platformBom);
                             }
-                            quarkusBomPlatformArtifact = null;
+                            matches.add(bom);
                         }
-                        builder.addPlatform(descriptorResolver.resolveFromJson(platformArtifact.getFile().toPath()));
                     }
-                    return builder.build();
+                } catch (RegistryResolutionException e) {
+                    throw new MojoExecutionException("Failed to resolve the catalog of Quarkus platforms", e);
                 }
+                if (matches != null) {
+                    final StringWriter buf = new StringWriter();
+                    buf.append("Found multiple platforms matching the provided arguments: ");
+                    try (BufferedWriter writer = new BufferedWriter(buf)) {
+                        for (ArtifactCoords coords : matches) {
+                            writer.newLine();
+                            writer.append("- ").append(coords.toString());
+                        }
+                    } catch (IOException e) {
+                        buf.append(matches.toString());
+                    }
+                    throw new MojoExecutionException(buf.toString());
+                }
+                return importedPlatforms = Collections.singletonList(platformBom);
             }
-            return CreateUtils.resolvePlatformDescriptor(bomGroupId, bomArtifactId, bomVersion, mvn, getLog());
-        } catch (Exception e) {
-            throw new MojoExecutionException("Failed to initialize maven artifact resolver", e);
+            importedPlatforms = collectImportedPlatforms();
         }
+        return importedPlatforms;
     }
 
-    private MavenArtifactResolver artifactResolver() throws BootstrapMavenException {
+    private MavenArtifactResolver artifactResolver() throws MojoExecutionException {
         if (artifactResolver == null) {
-            artifactResolver = MavenArtifactResolver.builder()
-                    .setRepositorySystem(repoSystem)
-                    .setRepositorySystemSession(repoSession)
-                    .setRemoteRepositories(repos)
-                    .setRemoteRepositoryManager(remoteRepositoryManager)
-                    .build();
-        }
-        return artifactResolver;
-    }
-
-    private List<Artifact> resolveLegacyQuarkusPlatformDescriptors(MessageWriter log, MavenArtifactResolver mvn)
-            throws IOException {
-        final List<Artifact> descrArtifactList = new ArrayList<>(2);
-        for (Dependency dep : getManagedDependencies(mvn)) {
-            if ((dep.getScope() == null || !dep.getScope().equals("import"))
-                    && (dep.getType() == null || !dep.getType().equals("pom"))) {
-                continue;
-            }
-            // We don't know which BOM is the platform one, so we are trying every BOM here
-            final String bomVersion = resolveValue(dep.getVersion());
-            final String bomGroupId = resolveValue(dep.getGroupId());
-            final String bomArtifactId = resolveValue(dep.getArtifactId());
-            if (bomVersion == null || bomGroupId == null || bomArtifactId == null) {
-                continue;
-            }
-
-            final Artifact jsonArtifact = resolveJsonOrNull(mvn, bomGroupId, bomArtifactId, bomVersion);
-            if (jsonArtifact != null) {
-                log.debug("Found legacy platform %s", jsonArtifact);
-                descrArtifactList.add(jsonArtifact);
+            try {
+                artifactResolver = MavenArtifactResolver.builder()
+                        .setRepositorySystem(repoSystem)
+                        .setRepositorySystemSession(repoSession)
+                        .setRemoteRepositories(repos)
+                        .setRemoteRepositoryManager(remoteRepositoryManager)
+                        .build();
+            } catch (BootstrapMavenException e) {
+                throw new MojoExecutionException("Failed to initialize Maven artifact resolver", e);
             }
         }
-        return descrArtifactList;
+        return artifactResolver;
     }
 
-    private List<Artifact> collectQuarkusPlatformDescriptors(MessageWriter log, MavenArtifactResolver mvn)
+    private List<ArtifactCoords> collectImportedPlatforms()
             throws MojoExecutionException {
-        final List<Artifact> descrArtifactList = new ArrayList<>(2);
+        final List<ArtifactCoords> descriptors = new ArrayList<>(4);
         final List<Dependency> constraints = project.getDependencyManagement() == null ? Collections.emptyList()
                 : project.getDependencyManagement().getDependencies();
         if (!constraints.isEmpty()) {
+            final MessageWriter log = getMessageWriter();
             for (Dependency d : constraints) {
                 if (!("json".equals(d.getType())
                         && d.getArtifactId().endsWith(BootstrapConstants.PLATFORM_DESCRIPTOR_ARTIFACT_ID_SUFFIX))) {
                     continue;
                 }
-                final Artifact a = new DefaultArtifact(d.getGroupId(), d.getArtifactId(), d.getClassifier(),
+                final ArtifactCoords a = new ArtifactCoords(d.getGroupId(), d.getArtifactId(), d.getClassifier(),
                         d.getType(), d.getVersion());
-                try {
-                    log.debug("Found platform descriptor %s", a);
-                    descrArtifactList.add(mvn.resolve(a).getArtifact());
-                } catch (Exception e) {
-                    throw new MojoExecutionException("Failed to resolve the platform descriptor " + a, e);
-                }
+                descriptors.add(a);
+                log.debug("Found platform descriptor %s", a);
             }
         }
-        return descrArtifactList;
+        return descriptors;
     }
 
-    private Artifact resolveJsonOrNull(MavenArtifactResolver mvn, String bomGroupId, String bomArtifactId, String bomVersion) {
-        Artifact jsonArtifact = new DefaultArtifact(bomGroupId, bomArtifactId, null, "json", bomVersion);
-        try {
-            jsonArtifact = mvn.resolve(jsonArtifact).getArtifact();
-        } catch (Exception e) {
-            if (getLog().isDebugEnabled()) {
-                getLog().debug("Failed to resolve JSON descriptor as " + jsonArtifact);
-            }
-            jsonArtifact = new DefaultArtifact(bomGroupId, bomArtifactId + "-descriptor-json", null, "json",
-                    bomVersion);
-            try {
-                jsonArtifact = mvn.resolve(jsonArtifact).getArtifact();
-            } catch (Exception e1) {
-                if (getLog().isDebugEnabled()) {
-                    getLog().debug("Failed to resolve JSON descriptor as " + jsonArtifact);
-                }
-                return null;
+    private String getQuarkusCoreVersion() {
+        final List<Dependency> constraints = project.getDependencyManagement() == null ? Collections.emptyList()
+                : project.getDependencyManagement().getDependencies();
+        for (Dependency d : constraints) {
+            if (d.getArtifactId().endsWith("quarkus-core") && d.getGroupId().equals("io.quarkus")) {
+                return d.getVersion();
             }
         }
-        if (getLog().isDebugEnabled()) {
-            getLog().debug("Resolve JSON descriptor " + jsonArtifact);
-        }
-        return jsonArtifact;
+        return null;
     }
 
     protected void validateParameters() throws MojoExecutionException {
@@ -276,53 +272,6 @@ protected void validateParameters() throws MojoExecutionException {
     protected abstract void doExecute(QuarkusProject quarkusProject, MessageWriter log)
             throws MojoExecutionException;
 
-    private String resolveValue(String expr) throws IOException {
-        if (expr.startsWith("${") && expr.endsWith("}")) {
-            final String name = expr.substring(2, expr.length() - 1);
-            final String v = project.getModel().getProperties().getProperty(name);
-            if (v == null) {
-                if (getLog().isDebugEnabled()) {
-                    getLog().debug("Failed to resolve property " + name);
-                }
-            }
-            return v;
-        }
-        return expr;
-    }
-
-    private List<Dependency> getManagedDependencies(MavenArtifactResolver resolver) throws IOException {
-        List<Dependency> managedDependencies = new ArrayList<>();
-        Model model = project.getOriginalModel();
-        DependencyManagement managed = model.getDependencyManagement();
-        if (managed != null) {
-            managedDependencies.addAll(managed.getDependencies());
-        }
-        Parent parent;
-        while ((parent = model.getParent()) != null) {
-            try {
-                ArtifactResult result = resolver.resolve(new DefaultArtifact(
-                        parent.getGroupId(),
-                        parent.getArtifactId(),
-                        "pom",
-                        ModelUtils.resolveVersion(parent.getVersion(), model)));
-                model = ModelUtils.readModel(result.getArtifact().getFile().toPath());
-                managed = model.getDependencyManagement();
-                if (managed != null) {
-                    // Alexey Loubyansky: In Maven whatever is imported first has a priority
-                    // So to match the maven way, we should be reading the root parent first
-                    managedDependencies.addAll(0, managed.getDependencies());
-                }
-            } catch (BootstrapMavenException e) {
-                // ignore
-                if (getLog().isDebugEnabled()) {
-                    getLog().debug("Error while resolving descriptor", e);
-                }
-                break;
-            }
-        }
-        return managedDependencies;
-    }
-
     private List<org.eclipse.aether.graph.Dependency> projectDependencies() throws MojoExecutionException {
         final List<org.eclipse.aether.graph.Dependency> deps = new ArrayList<>();
         try {
diff --git a/devtools/platform-descriptor-json-plugin/pom.xml b/devtools/platform-descriptor-json-plugin/pom.xml
index a72f3f6b66dc14..3959a47c3c77c6 100644
--- a/devtools/platform-descriptor-json-plugin/pom.xml
+++ b/devtools/platform-descriptor-json-plugin/pom.xml
@@ -36,11 +36,6 @@
             <groupId>org.wildfly.common</groupId>
             <artifactId>wildfly-common</artifactId>
         </dependency>
-        <dependency>
-            <groupId>io.quarkus</groupId>
-            <artifactId>quarkus-platform-descriptor-resolver-json</artifactId>
-            <version>${project.version}</version>
-        </dependency>
         <dependency>
             <groupId>org.apache.maven.plugin-tools</groupId>
             <artifactId>maven-plugin-annotations</artifactId>
diff --git a/devtools/platform-descriptor-json-plugin/src/main/java/io/quarkus/maven/GenerateExtensionsJsonMojo.java b/devtools/platform-descriptor-json-plugin/src/main/java/io/quarkus/maven/GenerateExtensionsJsonMojo.java
index af15641550f6da..dfdc15c16c4a7e 100644
--- a/devtools/platform-descriptor-json-plugin/src/main/java/io/quarkus/maven/GenerateExtensionsJsonMojo.java
+++ b/devtools/platform-descriptor-json-plugin/src/main/java/io/quarkus/maven/GenerateExtensionsJsonMojo.java
@@ -64,17 +64,22 @@
 import io.quarkus.bootstrap.resolver.maven.workspace.ModelUtils;
 import io.quarkus.bootstrap.util.IoUtils;
 import io.quarkus.bootstrap.util.ZipUtils;
-import io.quarkus.dependencies.Extension;
 import io.quarkus.platform.tools.ToolsConstants;
 
 /**
  * This goal generates a list of extensions for a given BOM
  * and stores it in a JSON format file that is later used by the tools
  * as the catalog of available extensions.
+ * @deprecated in favor of {@link GeneratePlatformDescriptorJsonMojo}
  */
+@Deprecated
 @Mojo(name = "generate-extensions-json")
 public class GenerateExtensionsJsonMojo extends AbstractMojo {
 
+    private static final String GROUP_ID = "group-id";
+    private static final String ARTIFACT_ID = "artifact-id";
+    private static final String VERSION = "version";
+
     @Parameter(property = "bomGroupId", defaultValue = "${project.groupId}")
     private String bomGroupId;
 
@@ -233,9 +238,9 @@ public void execute() throws MojoExecutionException, MojoFailureException {
         final JsonObjectBuilder platformJson = Json.createObjectBuilder();
         // Add information about the BOM to it
         final JsonObjectBuilder bomJson = Json.createObjectBuilder();
-        bomJson.add(Extension.GROUP_ID, bomGroupId);
-        bomJson.add(Extension.ARTIFACT_ID, bomArtifactId);
-        bomJson.add(Extension.VERSION, bomVersion);
+        bomJson.add(GROUP_ID, bomGroupId);
+        bomJson.add(ARTIFACT_ID, bomArtifactId);
+        bomJson.add(VERSION, bomVersion);
         platformJson.add("bom", bomJson.build());
         // Add Quarkus version
         platformJson.add("quarkus-core-version", quarkusCoreVersion);
@@ -409,8 +414,8 @@ private JsonNode processMetaInfDir(Artifact artifact, Path metaInfDir)
                 final Path props = metaInfDir.resolve(BootstrapConstants.DESCRIPTOR_FILE_NAME);
                 if (Files.exists(props)) {
                     return mapper.createObjectNode()
-                            .put(Extension.ARTIFACT_ID, artifact.getArtifactId())
-                            .put(Extension.GROUP_ID, artifact.getGroupId())
+                            .put(ARTIFACT_ID, artifact.getArtifactId())
+                            .put(GROUP_ID, artifact.getGroupId())
                             .put("version", artifact.getVersion())
                             .put("name", artifact.getArtifactId());
                 } else {
@@ -428,7 +433,7 @@ private JsonNode processPlatformArtifact(Artifact artifact, Path descriptor, Obj
         try (InputStream is = Files.newInputStream(descriptor)) {
             ObjectNode object = mapper.readValue(is, ObjectNode.class);
             transformLegacyToNew(object, mapper);
-            debug("Adding Quarkus extension %s:%s", object.get(Extension.GROUP_ID), object.get(Extension.ARTIFACT_ID));
+            debug("Adding Quarkus extension %s:%s", object.get(GROUP_ID), object.get(ARTIFACT_ID));
             return object;
         } catch (IOException io) {
             throw new IOException("Failed to parse " + descriptor, io);
@@ -452,15 +457,15 @@ private ObjectMapper getMapper(boolean yaml) {
     }
 
     private String extensionId(JsonObject extObject) {
-        String artId = extObject.getString(Extension.ARTIFACT_ID, "");
+        String artId = extObject.getString(ARTIFACT_ID, "");
         if (artId.isEmpty()) {
             getLog().warn("Missing artifactId in extension overrides in " + extObject.toString());
         }
-        String groupId = extObject.getString(Extension.GROUP_ID, "");
+        String groupId = extObject.getString(GROUP_ID, "");
         if (groupId.isEmpty()) {
             return artId;
         } else {
-            return extObject.getString(Extension.GROUP_ID, "") + ":" + artId;
+            return extObject.getString(GROUP_ID, "") + ":" + artId;
         }
     }
 
@@ -513,12 +518,12 @@ private void transformLegacyToNew(ObjectNode extObject, ObjectMapper mapper) {
         // just putting it
         // here for completenes
         if (extObject.get("groupId") != null) {
-            extObject.set(Extension.GROUP_ID, extObject.get("groupId"));
+            extObject.set(GROUP_ID, extObject.get("groupId"));
             extObject.remove("groupId");
         }
 
         if (extObject.get("artifactId") != null) {
-            extObject.set(Extension.ARTIFACT_ID, extObject.get("artifactId"));
+            extObject.set(ARTIFACT_ID, extObject.get("artifactId"));
             extObject.remove("artifactId");
         }
 
@@ -550,31 +555,31 @@ private void transformLegacyToNew(ObjectNode extObject, ObjectMapper mapper) {
 
     public OverrideInfo getOverrideInfo(File overridesFile) throws MojoExecutionException {
         // Read the overrides file for the extensions (if it exists)
-        HashMap extOverrides = new HashMap<>();
+        HashMap<String, JsonObject> extOverrides = new HashMap<>();
         JsonObject theRest = null;
         if (overridesFile.isFile()) {
             info("Found overrides file %s", overridesFile);
-            try (JsonReader jsonReader = Json.createReader(new FileInputStream(overridesFile))) {
-                JsonObject overridesObject = jsonReader.readObject();
-                JsonArray extOverrideObjects = overridesObject.getJsonArray("extensions");
-                if (extOverrideObjects != null) {
-                    // Put the extension overrides into a map keyed to their GAV
-                    for (JsonValue val : extOverrideObjects) {
-                        JsonObject extOverrideObject = val.asJsonObject();
-                        String key = extensionId(extOverrideObject);
-                        extOverrides.put(key, extOverrideObject);
+            try (FileInputStream fileInputStream = new FileInputStream(overridesFile)) {
+                try (JsonReader jsonReader = Json.createReader(fileInputStream)) {
+                    JsonObject overridesObject = jsonReader.readObject();
+                    JsonArray extOverrideObjects = overridesObject.getJsonArray("extensions");
+                    if (extOverrideObjects != null) {
+                        // Put the extension overrides into a map keyed to their GAV
+                        for (JsonValue val : extOverrideObjects) {
+                            JsonObject extOverrideObject = val.asJsonObject();
+                            String key = extensionId(extOverrideObject);
+                            extOverrides.put(key, extOverrideObject);
+                        }
                     }
-                }
 
-                theRest = overridesObject;
+                    theRest = overridesObject;
+                }
             } catch (IOException e) {
                 throw new MojoExecutionException("Failed to read " + overridesFile, e);
             }
             return new OverrideInfo(extOverrides, theRest);
-
-        } else {
-            throw new MojoExecutionException(overridesFile + " not found.");
         }
+        return null;
     }
 
     private static class OverrideInfo {
diff --git a/devtools/platform-descriptor-json-plugin/src/main/java/io/quarkus/maven/GeneratePlatformDescriptorJsonMojo.java b/devtools/platform-descriptor-json-plugin/src/main/java/io/quarkus/maven/GeneratePlatformDescriptorJsonMojo.java
new file mode 100644
index 00000000000000..499aec8476c66b
--- /dev/null
+++ b/devtools/platform-descriptor-json-plugin/src/main/java/io/quarkus/maven/GeneratePlatformDescriptorJsonMojo.java
@@ -0,0 +1,632 @@
+package io.quarkus.maven;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.maven.model.Model;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugins.annotations.Component;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.project.MavenProjectHelper;
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.eclipse.aether.graph.Dependency;
+import org.eclipse.aether.impl.RemoteRepositoryManager;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.resolution.ArtifactDescriptorException;
+import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
+import org.eclipse.aether.resolution.ArtifactRequest;
+import org.eclipse.aether.resolution.ArtifactResolutionException;
+import org.eclipse.aether.resolution.ArtifactResult;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+
+import io.quarkus.bootstrap.BootstrapConstants;
+import io.quarkus.bootstrap.model.AppArtifact;
+import io.quarkus.bootstrap.resolver.AppModelResolverException;
+import io.quarkus.bootstrap.resolver.BootstrapAppModelResolver;
+import io.quarkus.bootstrap.resolver.maven.BootstrapMavenException;
+import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver;
+import io.quarkus.bootstrap.resolver.maven.workspace.LocalWorkspace;
+import io.quarkus.bootstrap.resolver.maven.workspace.ModelUtils;
+import io.quarkus.bootstrap.util.IoUtils;
+import io.quarkus.bootstrap.util.ZipUtils;
+import io.quarkus.platform.descriptor.ProjectPlatformDescriptorJsonUtil;
+import io.quarkus.platform.tools.ToolsConstants;
+import io.quarkus.registry.catalog.Category;
+import io.quarkus.registry.catalog.Extension;
+import io.quarkus.registry.catalog.ExtensionOrigin;
+import io.quarkus.registry.catalog.json.JsonCatalogMapperHelper;
+import io.quarkus.registry.catalog.json.JsonCategory;
+import io.quarkus.registry.catalog.json.JsonExtension;
+import io.quarkus.registry.catalog.json.JsonExtensionCatalog;
+
+/**
+ * This goal generates a platform JSON descriptor for a given platform BOM.
+ */
+@Mojo(name = "generate-platform-descriptor-json")
+public class GeneratePlatformDescriptorJsonMojo extends AbstractMojo {
+
+    @Parameter(property = "quarkusCoreGroupId", defaultValue = ToolsConstants.QUARKUS_CORE_GROUP_ID)
+    private String quarkusCoreGroupId;
+
+    @Parameter(property = "quarkusCoreArtifactId", defaultValue = ToolsConstants.QUARKUS_CORE_ARTIFACT_ID)
+    private String quarkusCoreArtifactId;
+
+    @Parameter(property = "bomGroupId", defaultValue = "${project.groupId}")
+    private String bomGroupId;
+
+    @Parameter(property = "bomArtifactId", defaultValue = "${project.artifactId}")
+    private String bomArtifactId;
+
+    @Parameter(property = "bomVersion", defaultValue = "${project.version}")
+    private String bomVersion;
+
+    /** file used for overrides - overridesFiles takes precedence over this file. **/
+    @Parameter(property = "overridesFile", defaultValue = "${project.basedir}/src/main/resources/extensions-overrides.json")
+    private String overridesFile;
+
+    @Parameter(property = "outputFile", defaultValue = "${project.build.directory}/${project.artifactId}-${project.version}-${project.version}.json")
+    private File outputFile;
+
+    @Component
+    private RepositorySystem repoSystem;
+
+    @Component
+    RemoteRepositoryManager remoteRepoManager;
+
+    @Parameter(defaultValue = "${repositorySystemSession}", readonly = true)
+    private RepositorySystemSession repoSession;
+
+    @Parameter(defaultValue = "${project.remoteProjectRepositories}", readonly = true, required = true)
+    private List<RemoteRepository> repos;
+
+    @Component
+    private MavenProject project;
+    @Component
+    private MavenProjectHelper projectHelper;
+
+    /**
+     * Group ID's that we know don't contain extensions. This can speed up the process
+     * by preventing the download of artifacts that are not required.
+     */
+    @Parameter
+    private Set<String> ignoredGroupIds = new HashSet<>(0);
+
+    @Parameter
+    private boolean skipArtifactIdCheck;
+
+    @Parameter(property = "skipBomCheck")
+    private boolean skipBomCheck;
+
+    @Parameter(property = "resolveDependencyManagement")
+    boolean resolveDependencyManagement;
+
+    @Override
+    public void execute() throws MojoExecutionException, MojoFailureException {
+
+        final Artifact jsonArtifact = new DefaultArtifact(project.getGroupId(), project.getArtifactId(), project.getVersion(),
+                "json", project.getVersion());
+        if (!skipArtifactIdCheck) {
+            final String expectedArtifactId = bomArtifactId + BootstrapConstants.PLATFORM_DESCRIPTOR_ARTIFACT_ID_SUFFIX;
+            if (!jsonArtifact.getArtifactId().equals(expectedArtifactId)) {
+                throw new MojoExecutionException(
+                        "The project's artifactId " + project.getArtifactId() + " is expected to be " + expectedArtifactId);
+            }
+            if (!jsonArtifact.getGroupId().equals(bomGroupId)) {
+                throw new MojoExecutionException("The project's groupId " + project.getGroupId()
+                        + " is expected to match the groupId of the BOM which is " + bomGroupId);
+            }
+            if (!jsonArtifact.getVersion().equals(bomVersion)) {
+                throw new MojoExecutionException("The project's version " + project.getVersion()
+                        + " is expected to match the version of the BOM which is " + bomVersion);
+            }
+        }
+
+        // Get the BOM artifact
+        final DefaultArtifact bomArtifact = new DefaultArtifact(bomGroupId, bomArtifactId, "", "pom", bomVersion);
+        info("Generating catalog of extensions for %s", bomArtifact);
+
+        // if the BOM is generated and has replaced the original one, to pick up the generated content
+        // we should read the dependencyManagement from the generated pom.xml
+        List<Dependency> deps;
+        if (resolveDependencyManagement) {
+            getLog().debug("Resolving dependencyManagement from the artifact descriptor");
+            deps = dependencyManagementFromDescriptor(bomArtifact);
+        } else {
+            deps = dependencyManagementFromProject();
+            if (deps == null) {
+                deps = dependencyManagementFromResolvedPom(bomArtifact);
+            }
+        }
+        if (deps.isEmpty()) {
+            getLog().warn("BOM " + bomArtifact + " does not include any dependency");
+            return;
+        }
+
+        List<OverrideInfo> allOverrides = new ArrayList<>();
+        for (String path : overridesFile.split(",")) {
+            OverrideInfo overrideInfo = getOverrideInfo(new File(path.trim()));
+            if (overrideInfo != null) {
+                allOverrides.add(overrideInfo);
+            }
+        }
+
+        final JsonExtensionCatalog platformJson = new JsonExtensionCatalog();
+        final String platformId = jsonArtifact.getGroupId() + ":" + jsonArtifact.getArtifactId() + ":"
+                + jsonArtifact.getClassifier()
+                + ":" + jsonArtifact.getExtension() + ":" + jsonArtifact.getVersion();
+        platformJson.setId(platformId);
+        platformJson.setBom(ArtifactCoords.pom(bomGroupId, bomArtifactId, bomVersion));
+        platformJson.setPlatform(true);
+
+        final List<AppArtifact> importedDescriptors = deps.stream().filter(
+                d -> d.getArtifact().getArtifactId().endsWith(BootstrapConstants.PLATFORM_DESCRIPTOR_ARTIFACT_ID_SUFFIX)
+                        && d.getArtifact().getExtension().equals("json")
+                        && !(d.getArtifact().getArtifactId().equals(jsonArtifact.getArtifactId())
+                                && d.getArtifact().getGroupId().equals(jsonArtifact.getGroupId())))
+                .map(d -> new AppArtifact(d.getArtifact().getGroupId(), d.getArtifact().getArtifactId(),
+                        d.getArtifact().getClassifier(), d.getArtifact().getExtension(), d.getArtifact().getVersion()))
+                .collect(Collectors.toList());
+
+        Map<ArtifactKey, Extension> inheritedExtensions = Collections.emptyMap();
+        if (!importedDescriptors.isEmpty()) {
+            final MavenArtifactResolver mvnResolver;
+            try {
+                mvnResolver = MavenArtifactResolver.builder()
+                        .setRepositorySystem(repoSystem)
+                        .setRemoteRepositoryManager(remoteRepoManager)
+                        .setRepositorySystemSession(repoSession)
+                        .setRemoteRepositories(repos)
+                        .setWorkspaceDiscovery(false)
+                        .build();
+            } catch (BootstrapMavenException e) {
+                throw new MojoExecutionException("Failed to initialize Maven artifact resolver", e);
+            }
+            final JsonExtensionCatalog baseCatalog;
+            try {
+                baseCatalog = ProjectPlatformDescriptorJsonUtil
+                        .resolveCatalog(new BootstrapAppModelResolver(mvnResolver), importedDescriptors);
+            } catch (AppModelResolverException e) {
+                throw new MojoExecutionException("Failed to resolver inherited platform descriptor", e);
+            }
+            platformJson.setDerivedFrom(baseCatalog.getDerivedFrom());
+
+            final List<Extension> extensions = baseCatalog.getExtensions();
+            if (!extensions.isEmpty()) {
+                inheritedExtensions = new HashMap<>(extensions.size());
+                for (Extension e : extensions) {
+                    inheritedExtensions.put(e.getArtifact().getKey(), e);
+                }
+            }
+
+            platformJson.setMetadata(baseCatalog.getMetadata());
+        }
+
+        // Create a JSON array of extension descriptors
+        final List<io.quarkus.registry.catalog.Extension> extListJson = new ArrayList<>();
+        platformJson.setExtensions(extListJson);
+        String quarkusCoreVersion = null;
+        boolean jsonFoundInBom = false;
+        for (Dependency dep : deps) {
+            final Artifact artifact = dep.getArtifact();
+
+            if (!skipBomCheck && !jsonFoundInBom) {
+                jsonFoundInBom = artifact.getArtifactId().equals(jsonArtifact.getArtifactId())
+                        && artifact.getGroupId().equals(jsonArtifact.getGroupId())
+                        && artifact.getExtension().equals(jsonArtifact.getExtension())
+                        && artifact.getClassifier().equals(jsonArtifact.getClassifier())
+                        && artifact.getVersion().equals(jsonArtifact.getVersion());
+            }
+
+            if (ignoredGroupIds.contains(artifact.getGroupId())
+                    || !artifact.getExtension().equals("jar")
+                    || "javadoc".equals(artifact.getClassifier())
+                    || "tests".equals(artifact.getClassifier())
+                    || "sources".equals(artifact.getClassifier())) {
+                continue;
+            }
+
+            if (artifact.getArtifactId().equals(quarkusCoreArtifactId)
+                    && artifact.getGroupId().equals(quarkusCoreGroupId)) {
+                quarkusCoreVersion = artifact.getVersion();
+            }
+            ArtifactResult resolved = null;
+            try {
+                resolved = repoSystem.resolveArtifact(repoSession,
+                        new ArtifactRequest().setRepositories(repos).setArtifact(artifact));
+                JsonExtension extension = processDependency(resolved.getArtifact());
+                if (extension != null) {
+                    Extension inherited = inheritedExtensions.get(extension.getArtifact().getKey());
+                    final List<ExtensionOrigin> origins;
+                    if (inherited != null) {
+                        origins = new ArrayList<>(inherited.getOrigins().size() + 1);
+                        origins.addAll(inherited.getOrigins());
+                        origins.add(platformJson);
+                    } else {
+                        origins = Arrays.asList(platformJson);
+                    }
+                    extension.setOrigins(origins);
+                    String key = extensionId(extension);
+                    for (OverrideInfo info : allOverrides) {
+                        io.quarkus.registry.catalog.Extension extOverride = info.getExtOverrides().get(key);
+                        if (extOverride != null) {
+                            extension = mergeObject(extension, extOverride);
+                        }
+                    }
+                    extListJson.add(extension);
+                }
+            } catch (ArtifactResolutionException e) {
+                // there are some parent poms that appear as jars for some reason
+                debug("Failed to resolve dependency %s defined in %s", artifact, bomArtifact);
+            } catch (IOException e) {
+                throw new MojoExecutionException("Failed to process dependency " + artifact, e);
+            }
+        }
+
+        if (!skipBomCheck && !jsonFoundInBom) {
+            throw new MojoExecutionException(
+                    "Failed to locate " + jsonArtifact + " in the dependencyManagement section of " + bomArtifact);
+        }
+        if (quarkusCoreVersion == null) {
+            throw new MojoExecutionException("Failed to determine the Quarkus Core version for " + bomArtifact);
+        }
+        platformJson.setQuarkusCoreVersion(quarkusCoreVersion);
+
+        for (OverrideInfo info : allOverrides) {
+            if (info.getTheRest() != null) {
+                if (!info.getTheRest().getCategories().isEmpty()) {
+                    if (platformJson.getCategories().isEmpty()) {
+                        platformJson.setCategories(info.getTheRest().getCategories());
+                    } else {
+                        info.getTheRest().getCategories().stream().forEach(c -> {
+                            boolean found = false;
+                            for (Category platformC : platformJson.getCategories()) {
+                                if (platformC.getId().equals(c.getId())) {
+                                    found = true;
+                                    JsonCategory jsonC = (JsonCategory) platformC;
+                                    if (c.getDescription() != null) {
+                                        jsonC.setDescription(c.getDescription());
+                                    }
+                                    if (!c.getMetadata().isEmpty()) {
+                                        if (jsonC.getMetadata().isEmpty()) {
+                                            jsonC.setMetadata(c.getMetadata());
+                                        } else {
+                                            jsonC.getMetadata().putAll(c.getMetadata());
+                                        }
+                                    }
+                                    if (c.getName() != null) {
+                                        jsonC.setName(c.getName());
+                                    }
+                                }
+                                break;
+                            }
+                            if (!found) {
+                                platformJson.getCategories().add(c);
+                            }
+                        });
+                    }
+                }
+            }
+            if (!info.getTheRest().getMetadata().isEmpty()) {
+                if (platformJson.getMetadata().isEmpty()) {
+                    platformJson.setMetadata(info.getTheRest().getMetadata());
+                } else {
+                    platformJson.getMetadata().putAll(info.getTheRest().getMetadata());
+                }
+            }
+        }
+        // Write the JSON to the output file
+        final File outputDir = outputFile.getParentFile();
+        if (outputFile.exists()) {
+            outputFile.delete();
+        } else if (!outputDir.exists()) {
+            if (!outputDir.mkdirs()) {
+                throw new MojoExecutionException("Failed to create output directory " + outputDir);
+            }
+        }
+        try {
+            JsonCatalogMapperHelper.serialize(platformJson, outputFile.toPath().getParent().resolve(outputFile.getName()));
+        } catch (IOException e) {
+            throw new MojoExecutionException("Failed to persist the platform descriptor", e);
+        }
+        info("Extensions file written to %s", outputFile);
+
+        // this is necessary to sometimes be able to resolve the artifacts from the workspace
+        final File published = new File(project.getBuild().getDirectory(), LocalWorkspace.getFileName(jsonArtifact));
+        if (!outputDir.equals(published)) {
+            try {
+                IoUtils.copy(outputFile.toPath(), published.toPath());
+            } catch (IOException e) {
+                throw new MojoExecutionException("Failed to copy " + outputFile + " to " + published);
+            }
+        }
+        projectHelper.attachArtifact(project, jsonArtifact.getExtension(), jsonArtifact.getClassifier(), published);
+    }
+
+    private List<Dependency> dependencyManagementFromDescriptor(Artifact bomArtifact) throws MojoExecutionException {
+        try {
+            return repoSystem
+                    .readArtifactDescriptor(repoSession,
+                            new ArtifactDescriptorRequest().setRepositories(repos).setArtifact(bomArtifact))
+                    .getManagedDependencies();
+        } catch (ArtifactDescriptorException e) {
+            throw new MojoExecutionException("Failed to read descriptor of " + bomArtifact, e);
+        }
+    }
+
+    private List<Dependency> dependencyManagementFromResolvedPom(Artifact bomArtifact) throws MojoExecutionException {
+        final Path pomXml;
+        try {
+            pomXml = repoSystem
+                    .resolveArtifact(repoSession, new ArtifactRequest().setArtifact(bomArtifact).setRepositories(repos))
+                    .getArtifact().getFile().toPath();
+        } catch (ArtifactResolutionException e) {
+            throw new MojoExecutionException("Failed to resolve " + bomArtifact, e);
+        }
+        return readDependencyManagement(pomXml);
+    }
+
+    private List<Dependency> dependencyManagementFromProject() throws MojoExecutionException {
+        // if the configured BOM coordinates are not matching the current project
+        // the current project's POM isn't the right source
+        if (!project.getArtifact().getArtifactId().equals(bomArtifactId)
+                || !project.getArtifact().getVersion().equals(bomVersion)
+                || !project.getArtifact().getGroupId().equals(bomGroupId)
+                || !project.getFile().exists()) {
+            return null;
+        }
+        return readDependencyManagement(project.getFile().toPath());
+    }
+
+    private List<Dependency> readDependencyManagement(Path pomXml) throws MojoExecutionException {
+        if (getLog().isDebugEnabled()) {
+            getLog().debug("Reading dependencyManagement from " + pomXml);
+        }
+        final Model bomModel;
+        try {
+            bomModel = ModelUtils.readModel(pomXml);
+        } catch (IOException e) {
+            throw new MojoExecutionException("Failed to parse " + project.getFile(), e);
+        }
+
+        // if the POM has a parent then we better resolve the descriptor
+        if (bomModel.getParent() != null) {
+            throw new MojoExecutionException(pomXml
+                    + " has a parent, in which case it is recommended to set 'resolveDependencyManagement' parameter to true");
+        }
+
+        if (bomModel.getDependencyManagement() == null) {
+            return Collections.emptyList();
+        }
+        final List<org.apache.maven.model.Dependency> modelDeps = bomModel.getDependencyManagement().getDependencies();
+        if (modelDeps.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        final List<Dependency> deps = new ArrayList<>(modelDeps.size());
+        for (org.apache.maven.model.Dependency modelDep : modelDeps) {
+            final Artifact artifact = new DefaultArtifact(modelDep.getGroupId(), modelDep.getArtifactId(),
+                    modelDep.getClassifier(), modelDep.getType(), modelDep.getVersion());
+            // exclusions aren't relevant in this context
+            deps.add(new Dependency(artifact, modelDep.getScope(), modelDep.isOptional(), Collections.emptyList()));
+        }
+        return deps;
+    }
+
+    private JsonExtension processDependency(Artifact artifact) throws IOException {
+        return processDependencyToObjectNode(artifact);
+    }
+
+    private JsonExtension processDependencyToObjectNode(Artifact artifact) throws IOException {
+        final Path path = artifact.getFile().toPath();
+        if (Files.isDirectory(path)) {
+            return processMetaInfDir(artifact, path.resolve(BootstrapConstants.META_INF));
+        } else {
+            try (FileSystem artifactFs = ZipUtils.newFileSystem(path)) {
+                return processMetaInfDir(artifact, artifactFs.getPath(BootstrapConstants.META_INF));
+            }
+        }
+    }
+
+    /**
+     * Load and return javax.jsonObject based on yaml, json or properties file.
+     *
+     * @param artifact
+     * @param metaInfDir
+     * @return
+     * @throws IOException
+     */
+    private JsonExtension processMetaInfDir(Artifact artifact, Path metaInfDir)
+            throws IOException {
+
+        ObjectMapper mapper = null;
+
+        if (!Files.exists(metaInfDir)) {
+            return null;
+        }
+        Path jsonOrYaml = null;
+
+        Path yaml = metaInfDir.resolve(BootstrapConstants.QUARKUS_EXTENSION_FILE_NAME);
+        if (Files.exists(yaml)) {
+            mapper = getMapper(true);
+            jsonOrYaml = yaml;
+        } else {
+            mapper = getMapper(false);
+            Path json = metaInfDir.resolve(BootstrapConstants.EXTENSION_PROPS_JSON_FILE_NAME);
+            if (!Files.exists(json)) {
+                final Path props = metaInfDir.resolve(BootstrapConstants.DESCRIPTOR_FILE_NAME);
+                if (Files.exists(props)) {
+                    final JsonExtension e = new JsonExtension();
+                    e.setArtifact(new ArtifactCoords(artifact.getGroupId(), artifact.getArtifactId(), artifact.getClassifier(),
+                            artifact.getExtension(), artifact.getVersion()));
+                    e.setName(artifact.getArtifactId());
+                    return e;
+                }
+                return null;
+            } else {
+                jsonOrYaml = json;
+            }
+        }
+        return processPlatformArtifact(artifact, jsonOrYaml, mapper);
+    }
+
+    private JsonExtension processPlatformArtifact(Artifact artifact, Path descriptor, ObjectMapper mapper)
+            throws IOException {
+        try (InputStream is = Files.newInputStream(descriptor)) {
+            JsonExtension legacy = mapper.readValue(is, JsonExtension.class);
+            JsonExtension object = transformLegacyToNew(legacy);
+            debug("Adding Quarkus extension %s", object.getArtifact());
+            return object;
+        } catch (IOException io) {
+            throw new IOException("Failed to parse " + descriptor, io);
+        }
+    }
+
+    private ObjectMapper getMapper(boolean yaml) {
+
+        if (yaml) {
+            YAMLFactory yf = new YAMLFactory();
+            return JsonCatalogMapperHelper.initMapper(new ObjectMapper(yf));
+        } else {
+            return JsonCatalogMapperHelper.mapper();
+        }
+    }
+
+    private String extensionId(io.quarkus.registry.catalog.Extension extObject) {
+        return extObject.getArtifact().getGroupId() + ":" + extObject.getArtifact().getArtifactId();
+    }
+
+    private JsonExtension mergeObject(JsonExtension extObject, io.quarkus.registry.catalog.Extension extOverride) {
+        if (extOverride.getArtifact() != null) {
+            extObject.setArtifact(extOverride.getArtifact());
+        }
+        if (extOverride.getCodestart() != null) {
+            extObject.setCodestart(extOverride.getCodestart());
+        }
+        if (extOverride.getDescription() != null) {
+            extObject.setDescription(extOverride.getDescription());
+        }
+        if (extOverride.getGuide() != null) {
+            extObject.setGuide(extOverride.getGuide());
+        }
+        if (!extOverride.getKeywords().isEmpty()) {
+            extObject.setKeywords(extOverride.getKeywords());
+        }
+        if (!extOverride.getMetadata().isEmpty()) {
+            if (extObject.getMetadata().isEmpty()) {
+                extObject.setMetadata(extOverride.getMetadata());
+            } else {
+                extObject.getMetadata().putAll(extOverride.getMetadata());
+            }
+        }
+        if (extOverride.getName() != null) {
+            extObject.setName(extOverride.getName());
+        }
+        if (!extOverride.getOrigins().isEmpty()) {
+            extObject.setOrigins(extOverride.getOrigins());
+        }
+        if (extOverride.getShortName() != null) {
+            extObject.setShortName(extOverride.getShortName());
+        }
+        return extObject;
+    }
+
+    private void info(String msg, Object... args) {
+        if (!getLog().isInfoEnabled()) {
+            return;
+        }
+        if (args.length == 0) {
+            getLog().info(msg);
+            return;
+        }
+        getLog().info(String.format(msg, args));
+    }
+
+    private void debug(String msg, Object... args) {
+        if (!getLog().isDebugEnabled()) {
+            return;
+        }
+        if (args.length == 0) {
+            getLog().debug(msg);
+            return;
+        }
+        getLog().debug(String.format(msg, args));
+    }
+
+    private JsonExtension transformLegacyToNew(JsonExtension extObject) {
+        final Map<String, Object> metadata = extObject.getMetadata();
+        final Object labels = metadata.get("labels");
+        if (labels != null) {
+            metadata.put("keywords", labels);
+            metadata.remove("labels");
+        }
+        return extObject;
+    }
+
+    public OverrideInfo getOverrideInfo(File overridesFile) throws MojoExecutionException {
+        // Read the overrides file for the extensions (if it exists)
+        HashMap<String, io.quarkus.registry.catalog.Extension> extOverrides = new HashMap<>();
+        JsonExtensionCatalog theRest = null;
+        if (overridesFile.isFile()) {
+            info("Found overrides file %s", overridesFile);
+            try {
+                JsonExtensionCatalog overridesObject = JsonCatalogMapperHelper.deserialize(overridesFile.toPath(),
+                        JsonExtensionCatalog.class);
+                List<io.quarkus.registry.catalog.Extension> extensionsOverrides = overridesObject.getExtensions();
+                if (!extensionsOverrides.isEmpty()) {
+                    // Put the extension overrides into a map keyed to their GAV
+                    for (io.quarkus.registry.catalog.Extension extOverride : extensionsOverrides) {
+                        String key = extensionId(extOverride);
+                        extOverrides.put(key, extOverride);
+                    }
+                }
+
+                theRest = overridesObject;
+            } catch (IOException e) {
+                throw new MojoExecutionException("Failed to read " + overridesFile, e);
+            }
+            return new OverrideInfo(extOverrides, theRest);
+        }
+        return null;
+    }
+
+    private static class OverrideInfo {
+        private Map<String, io.quarkus.registry.catalog.Extension> extOverrides;
+        private JsonExtensionCatalog theRest;
+
+        public OverrideInfo(Map<String, io.quarkus.registry.catalog.Extension> extOverrides,
+                JsonExtensionCatalog theRest) {
+            this.extOverrides = extOverrides;
+            this.theRest = theRest;
+        }
+
+        public Map<String, io.quarkus.registry.catalog.Extension> getExtOverrides() {
+            return extOverrides;
+        }
+
+        public JsonExtensionCatalog getTheRest() {
+            return theRest;
+        }
+    }
+}
diff --git a/devtools/platform-descriptor-json-plugin/src/main/java/io/quarkus/maven/ValidateExtensionsJsonMojo.java b/devtools/platform-descriptor-json-plugin/src/main/java/io/quarkus/maven/ValidateExtensionsJsonMojo.java
index b33fb8ec0f7861..88cb63cc48abf2 100644
--- a/devtools/platform-descriptor-json-plugin/src/main/java/io/quarkus/maven/ValidateExtensionsJsonMojo.java
+++ b/devtools/platform-descriptor-json-plugin/src/main/java/io/quarkus/maven/ValidateExtensionsJsonMojo.java
@@ -29,11 +29,11 @@
 import org.eclipse.aether.repository.RemoteRepository;
 
 import io.quarkus.bootstrap.BootstrapConstants;
-import io.quarkus.bootstrap.resolver.BootstrapAppModelResolver;
+import io.quarkus.bootstrap.resolver.maven.BootstrapMavenException;
 import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver;
-import io.quarkus.dependencies.Extension;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
-import io.quarkus.platform.descriptor.resolver.json.QuarkusJsonPlatformDescriptorResolver;
+import io.quarkus.registry.catalog.Extension;
+import io.quarkus.registry.catalog.json.JsonCatalogMapperHelper;
+import io.quarkus.registry.catalog.json.JsonExtensionCatalog;
 
 /**
  * This goal validates a given JSON descriptor.
@@ -101,17 +101,29 @@ public void execute() throws MojoExecutionException, MojoFailureException {
             throw new MojoExecutionException("Failed to initialize maven artifact resolver", e);
         }
 
-        final QuarkusPlatformDescriptor descriptor = QuarkusJsonPlatformDescriptorResolver.newInstance()
-                .setArtifactResolver(new BootstrapAppModelResolver(mvn))
-                .resolveFromJson(jsonGroupId, jsonArtifactId, jsonVersion, jsonVersion);
+        final Artifact artifact = new DefaultArtifact(jsonGroupId, jsonArtifactId, jsonVersion, "json", jsonVersion);
+        final Path jsonPath;
+        try {
+            jsonPath = mvn.resolve(artifact).getArtifact().getFile().toPath();
+        } catch (BootstrapMavenException e) {
+            throw new MojoExecutionException("Failed to resolve platform descriptor " + artifact, e);
+        }
+
+        JsonExtensionCatalog catalog;
+        try {
+            catalog = JsonCatalogMapperHelper.deserialize(jsonPath, JsonExtensionCatalog.class);
+        } catch (IOException e) {
+            throw new MojoExecutionException("Failed to deserialize extension catalog " + jsonPath, e);
+        }
+        final ArtifactCoords bomCoords = catalog.getBom();
 
-        final DefaultArtifact bomArtifact = new DefaultArtifact(descriptor.getBomGroupId(),
-                descriptor.getBomArtifactId(), null, "pom", descriptor.getBomVersion());
+        final DefaultArtifact bomArtifact = new DefaultArtifact(bomCoords.getGroupId(),
+                bomCoords.getArtifactId(), bomCoords.getClassifier(), bomCoords.getType(), bomCoords.getVersion());
         final Map<String, Artifact> bomExtensions = collectBomExtensions(mvn, bomArtifact);
 
         List<Extension> missingFromBom = Collections.emptyList();
-        for (Extension ext : descriptor.getExtensions()) {
-            if (bomExtensions.remove(ext.getGroupId() + ":" + ext.getArtifactId()) == null) {
+        for (Extension ext : catalog.getExtensions()) {
+            if (bomExtensions.remove(ext.getArtifact().getGroupId() + ":" + ext.getArtifact().getArtifactId()) == null) {
                 if (missingFromBom.isEmpty()) {
                     missingFromBom = new ArrayList<>();
                 }
@@ -134,8 +146,7 @@ public void execute() throws MojoExecutionException, MojoFailureException {
             getLog().error("Extensions from " + jsonGroupId + ":" + jsonArtifactId + ":" + jsonVersion + " missing from "
                     + bomArtifact);
             for (Extension e : missingFromBom) {
-                getLog().error("- " + e.getGroupId() + ":" + e.getArtifactId() + ":" + e.getClassifier() + ":" + e.getType()
-                        + ":" + e.getVersion());
+                getLog().error("- " + e.getArtifact());
             }
         }
         throw new MojoExecutionException("Extensions referenced in " + bomArtifact + " and included in " + jsonGroupId + ":"
diff --git a/devtools/platform-descriptor-json/src/main/java/io/quarkus/platform/descriptor/loader/json/impl/QuarkusJsonPlatformBom.java b/devtools/platform-descriptor-json/src/main/java/io/quarkus/platform/descriptor/loader/json/impl/QuarkusJsonPlatformBom.java
deleted file mode 100644
index 691a6720279323..00000000000000
--- a/devtools/platform-descriptor-json/src/main/java/io/quarkus/platform/descriptor/loader/json/impl/QuarkusJsonPlatformBom.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package io.quarkus.platform.descriptor.loader.json.impl;
-
-public class QuarkusJsonPlatformBom {
-
-    public String groupId;
-    public String artifactId;
-    public String version;
-}
diff --git a/devtools/platform-descriptor-json/src/main/java/io/quarkus/platform/descriptor/loader/json/impl/QuarkusJsonPlatformDescriptor.java b/devtools/platform-descriptor-json/src/main/java/io/quarkus/platform/descriptor/loader/json/impl/QuarkusJsonPlatformDescriptor.java
deleted file mode 100644
index 36b136f64b3125..00000000000000
--- a/devtools/platform-descriptor-json/src/main/java/io/quarkus/platform/descriptor/loader/json/impl/QuarkusJsonPlatformDescriptor.java
+++ /dev/null
@@ -1,170 +0,0 @@
-package io.quarkus.platform.descriptor.loader.json.impl;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.Serializable;
-import java.nio.charset.StandardCharsets;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.stream.Collectors;
-
-import org.apache.maven.model.Dependency;
-
-import com.fasterxml.jackson.annotation.JsonIgnore;
-
-import io.quarkus.dependencies.Category;
-import io.quarkus.dependencies.Extension;
-import io.quarkus.devtools.messagewriter.MessageWriter;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
-import io.quarkus.platform.descriptor.ResourceInputStreamConsumer;
-import io.quarkus.platform.descriptor.ResourcePathConsumer;
-import io.quarkus.platform.descriptor.loader.json.ResourceLoader;
-
-public class QuarkusJsonPlatformDescriptor implements QuarkusPlatformDescriptor, Serializable {
-
-    private String bomGroupId;
-    private String bomArtifactId;
-    private String bomVersion;
-    private String quarkusVersion;
-
-    private List<Extension> extensions = Collections.emptyList();
-    private List<Category> categories = Collections.emptyList();
-    private Map<String, Object> metadata = Collections.emptyMap();
-    private transient ResourceLoader resourceLoader;
-    private transient MessageWriter log;
-
-    public QuarkusJsonPlatformDescriptor() {
-    }
-
-    public void setBom(QuarkusJsonPlatformBom bom) {
-        bomGroupId = bom.groupId;
-        bomArtifactId = bom.artifactId;
-        bomVersion = bom.version;
-    }
-
-    public void setQuarkusCoreVersion(String quarkusVersion) {
-        this.quarkusVersion = quarkusVersion;
-    }
-
-    public void setExtensions(List<Extension> extensions) {
-        this.extensions = extensions;
-    }
-
-    public void setMetadata(Map<String, Object> metadata) {
-        this.metadata = metadata;
-    }
-
-    void setResourceLoader(ResourceLoader resourceLoader) {
-        this.resourceLoader = resourceLoader;
-    }
-
-    void setMessageWriter(MessageWriter log) {
-        this.log = log;
-    }
-
-    void setQuarkusVersion(String quarkusVersion) {
-        this.quarkusVersion = quarkusVersion;
-    }
-
-    private MessageWriter getLog() {
-        return log == null ? log = MessageWriter.info() : log;
-    }
-
-    @Override
-    public String getBomGroupId() {
-        return bomGroupId;
-    }
-
-    @Override
-    public String getBomArtifactId() {
-        return bomArtifactId;
-    }
-
-    @Override
-    public String getBomVersion() {
-        return bomVersion;
-    }
-
-    @Override
-    public String getQuarkusVersion() {
-        return quarkusVersion;
-    }
-
-    @Override
-    public List<Extension> getExtensions() {
-        return extensions;
-    }
-
-    @Override
-    public Map<String, Object> getMetadata() {
-        return metadata;
-    }
-
-    @Override
-    @JsonIgnore
-    public List<Dependency> getManagedDependencies() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public String getTemplate(String name) {
-        getLog().debug("Loading Quarkus project template %s", name);
-        if (resourceLoader == null) {
-            throw new IllegalStateException("Resource loader has not been provided");
-        }
-        try {
-            return resourceLoader.loadResource(name, is -> {
-                try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
-                    return reader.lines().collect(Collectors.joining("\n"));
-                }
-            });
-        } catch (IOException e) {
-            throw new IllegalStateException("Failed to load " + name, e);
-        }
-    }
-
-    @Override
-    public <T> T loadResource(String name, ResourceInputStreamConsumer<T> consumer) throws IOException {
-        getLog().debug("Loading Quarkus platform resource %s", name);
-        if (resourceLoader == null) {
-            throw new IllegalStateException("Resource loader has not been provided");
-        }
-        return resourceLoader.loadResource(name, consumer);
-    }
-
-    @Override
-    public <T> T loadResourceAsPath(String name, ResourcePathConsumer<T> consumer) throws IOException {
-        getLog().debug("Loading Quarkus platform resource %s", name);
-        if (resourceLoader == null) {
-            throw new IllegalStateException("Resource loader has not been provided");
-        }
-        return resourceLoader.loadResourceAsPath(name, consumer);
-    }
-
-    @Override
-    public List<Category> getCategories() {
-        return categories;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-        QuarkusJsonPlatformDescriptor that = (QuarkusJsonPlatformDescriptor) o;
-        return bomGroupId.equals(that.bomGroupId) &&
-                bomArtifactId.equals(that.bomArtifactId) &&
-                bomVersion.equals(that.bomVersion);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(bomGroupId, bomArtifactId, bomVersion);
-    }
-}
diff --git a/devtools/platform-descriptor-json/src/main/java/io/quarkus/platform/descriptor/loader/json/impl/QuarkusJsonPlatformDescriptorLoaderBootstrap.java b/devtools/platform-descriptor-json/src/main/java/io/quarkus/platform/descriptor/loader/json/impl/QuarkusJsonPlatformDescriptorLoaderBootstrap.java
deleted file mode 100644
index 5c3134d7d70cba..00000000000000
--- a/devtools/platform-descriptor-json/src/main/java/io/quarkus/platform/descriptor/loader/json/impl/QuarkusJsonPlatformDescriptorLoaderBootstrap.java
+++ /dev/null
@@ -1,87 +0,0 @@
-package io.quarkus.platform.descriptor.loader.json.impl;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.FileSystem;
-import java.nio.file.FileSystems;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.Properties;
-import java.util.function.Function;
-
-import io.quarkus.maven.utilities.MojoUtils;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
-import io.quarkus.platform.descriptor.loader.QuarkusPlatformDescriptorLoader;
-import io.quarkus.platform.descriptor.loader.QuarkusPlatformDescriptorLoaderContext;
-import io.quarkus.platform.descriptor.loader.json.ArtifactResolver;
-import io.quarkus.platform.descriptor.loader.json.QuarkusJsonPlatformDescriptorLoaderContext;
-
-/**
- * This class is used to bootstrap the Quarkus platform descriptor from the classpath only.
- */
-public class QuarkusJsonPlatformDescriptorLoaderBootstrap
-        implements QuarkusPlatformDescriptorLoader<QuarkusPlatformDescriptor, QuarkusPlatformDescriptorLoaderContext> {
-
-    private static InputStream getResourceStream(String relativePath) {
-        final InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(relativePath);
-        if (is == null) {
-            throw new IllegalStateException("Failed to locate " + relativePath + " on the classpath");
-        }
-        return is;
-    }
-
-    @Override
-    public QuarkusPlatformDescriptor load(QuarkusPlatformDescriptorLoaderContext context) {
-
-        context.getMessageWriter().debug("Loading the default Quarkus Platform descriptor from the classpath");
-
-        final Properties props = new Properties();
-        final InputStream quarkusProps = getResourceStream("quarkus.properties");
-        try {
-            props.load(quarkusProps);
-        } catch (IOException e) {
-            throw new IllegalStateException("Failed to load properties quarkus.properties", e);
-        }
-
-        final Path resourceRoot;
-        try {
-            resourceRoot = MojoUtils.getClassOrigin(QuarkusJsonPlatformDescriptorLoaderBootstrap.class);
-        } catch (IOException e) {
-            throw new IllegalStateException("Failed to determine the resource root for " + getClass().getName(), e);
-        }
-
-        final ArtifactResolver resolver = new ArtifactResolver() {
-            @Override
-            public <T> T process(String groupId, String artifactId, String classifier, String type, String version,
-                    Function<Path, T> processor) {
-                throw new UnsupportedOperationException();
-            }
-        };
-
-        return new QuarkusJsonPlatformDescriptorLoaderImpl()
-                .load(new QuarkusJsonPlatformDescriptorLoaderContext(resolver, context.getMessageWriter()) {
-                    @Override
-                    public <T> T parseJson(Function<InputStream, T> parser) {
-                        if (Files.isDirectory(resourceRoot)) {
-                            return doParse(resourceRoot.resolve("quarkus-bom-descriptor/extensions.json"), parser);
-                        }
-                        try (FileSystem fs = FileSystems.newFileSystem(resourceRoot, null)) {
-                            return doParse(fs.getPath("/quarkus-bom-descriptor/extensions.json"), parser);
-                        } catch (IOException e) {
-                            throw new IllegalStateException("Failed to open " + resourceRoot, e);
-                        }
-                    }
-                });
-    }
-
-    private static <T> T doParse(Path p, Function<InputStream, T> parser) {
-        if (!Files.exists(p)) {
-            throw new IllegalStateException("Path does not exist: " + p);
-        }
-        try (InputStream is = Files.newInputStream(p)) {
-            return parser.apply(is);
-        } catch (IOException e) {
-            throw new IllegalStateException("Failed to read " + p, e);
-        }
-    }
-}
diff --git a/devtools/platform-descriptor-json/src/main/java/io/quarkus/platform/descriptor/loader/json/impl/QuarkusJsonPlatformDescriptorLoaderImpl.java b/devtools/platform-descriptor-json/src/main/java/io/quarkus/platform/descriptor/loader/json/impl/QuarkusJsonPlatformDescriptorLoaderImpl.java
deleted file mode 100644
index 75adb5f1ad0a2c..00000000000000
--- a/devtools/platform-descriptor-json/src/main/java/io/quarkus/platform/descriptor/loader/json/impl/QuarkusJsonPlatformDescriptorLoaderImpl.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package io.quarkus.platform.descriptor.loader.json.impl;
-
-import java.io.IOException;
-
-import com.fasterxml.jackson.core.json.JsonReadFeature;
-import com.fasterxml.jackson.databind.DeserializationFeature;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.PropertyNamingStrategies;
-import com.fasterxml.jackson.databind.json.JsonMapper;
-
-import io.quarkus.platform.descriptor.loader.json.QuarkusJsonPlatformDescriptorLoader;
-import io.quarkus.platform.descriptor.loader.json.QuarkusJsonPlatformDescriptorLoaderContext;
-
-public class QuarkusJsonPlatformDescriptorLoaderImpl
-        implements QuarkusJsonPlatformDescriptorLoader<QuarkusJsonPlatformDescriptor> {
-
-    @Override
-    public QuarkusJsonPlatformDescriptor load(final QuarkusJsonPlatformDescriptorLoaderContext context) {
-
-        final QuarkusJsonPlatformDescriptor platform = context
-                .parseJson(is -> {
-                    try {
-                        ObjectMapper mapper = JsonMapper.builder()
-                                .enable(JsonReadFeature.ALLOW_JAVA_COMMENTS)
-                                .enable(JsonReadFeature.ALLOW_LEADING_ZEROS_FOR_NUMBERS)
-                                .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
-                                .propertyNamingStrategy(PropertyNamingStrategies.KEBAB_CASE)
-                                .build();
-                        return mapper.readValue(is, QuarkusJsonPlatformDescriptor.class);
-                    } catch (IOException e) {
-                        throw new RuntimeException("Failed to parse JSON stream", e);
-                    }
-                });
-        platform.setResourceLoader(context.getResourceLoader());
-        platform.setMessageWriter(context.getMessageWriter());
-
-        return platform;
-    }
-}
diff --git a/devtools/platform-descriptor-json/src/main/resources/META-INF/services/io.quarkus.platform.descriptor.loader.QuarkusPlatformDescriptorLoader b/devtools/platform-descriptor-json/src/main/resources/META-INF/services/io.quarkus.platform.descriptor.loader.QuarkusPlatformDescriptorLoader
deleted file mode 100644
index 54b44251eaa4f8..00000000000000
--- a/devtools/platform-descriptor-json/src/main/resources/META-INF/services/io.quarkus.platform.descriptor.loader.QuarkusPlatformDescriptorLoader
+++ /dev/null
@@ -1 +0,0 @@
-io.quarkus.platform.descriptor.loader.json.impl.QuarkusJsonPlatformDescriptorLoaderBootstrap
\ No newline at end of file
diff --git a/devtools/platform-descriptor-json/src/main/resources/META-INF/services/io.quarkus.platform.descriptor.loader.json.QuarkusJsonPlatformDescriptorLoader b/devtools/platform-descriptor-json/src/main/resources/META-INF/services/io.quarkus.platform.descriptor.loader.json.QuarkusJsonPlatformDescriptorLoader
deleted file mode 100644
index 067557f6396e3e..00000000000000
--- a/devtools/platform-descriptor-json/src/main/resources/META-INF/services/io.quarkus.platform.descriptor.loader.json.QuarkusJsonPlatformDescriptorLoader
+++ /dev/null
@@ -1 +0,0 @@
-io.quarkus.platform.descriptor.loader.json.impl.QuarkusJsonPlatformDescriptorLoaderImpl
\ No newline at end of file
diff --git a/devtools/platform-descriptor-json/src/test/java/io/quarkus/platform/descriptor/tests/PlatformDescriptorLoaderTest.java b/devtools/platform-descriptor-json/src/test/java/io/quarkus/platform/descriptor/tests/PlatformDescriptorLoaderTest.java
deleted file mode 100644
index db36ce32603f2c..00000000000000
--- a/devtools/platform-descriptor-json/src/test/java/io/quarkus/platform/descriptor/tests/PlatformDescriptorLoaderTest.java
+++ /dev/null
@@ -1,65 +0,0 @@
-package io.quarkus.platform.descriptor.tests;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.Path;
-import java.util.function.Function;
-
-import org.junit.jupiter.api.Test;
-
-import io.quarkus.platform.descriptor.loader.json.ArtifactResolver;
-import io.quarkus.platform.descriptor.loader.json.QuarkusJsonPlatformDescriptorLoader;
-import io.quarkus.platform.descriptor.loader.json.QuarkusJsonPlatformDescriptorLoaderContext;
-import io.quarkus.platform.descriptor.loader.json.impl.QuarkusJsonPlatformDescriptor;
-import io.quarkus.platform.descriptor.loader.json.impl.QuarkusJsonPlatformDescriptorLoaderImpl;
-
-class PlatformDescriptorLoaderTest {
-
-    @Test
-    void test() {
-
-        QuarkusJsonPlatformDescriptorLoader<QuarkusJsonPlatformDescriptor> qpd = new QuarkusJsonPlatformDescriptorLoaderImpl();
-
-        final ArtifactResolver artifactResolver = new ArtifactResolver() {
-
-            @Override
-            public <T> T process(String groupId, String artifactId, String classifier, String type, String version,
-                    Function<Path, T> processor) {
-                throw new UnsupportedOperationException();
-            }
-        };
-
-        QuarkusJsonPlatformDescriptorLoaderContext context = new QuarkusJsonPlatformDescriptorLoaderContext(artifactResolver) {
-            @Override
-            public <T> T parseJson(Function<InputStream, T> parser) {
-                String resourceName = "fakeextensions.json";
-
-                final InputStream is = getClass().getClassLoader().getResourceAsStream(resourceName);
-                if (is == null) {
-                    throw new IllegalStateException("Failed to locate " + resourceName + " on the classpath");
-                }
-
-                try {
-                    return parser.apply(is);
-                } finally {
-                    try {
-                        is.close();
-                    } catch (IOException e) {
-                    }
-                }
-            }
-
-        };
-
-        QuarkusJsonPlatformDescriptor load = qpd.load(context);
-        assertNotNull(load);
-        assertEquals(85, load.getExtensions().size());
-        assertEquals(1, load.getCategories().size());
-        assertThat(load.getMetadata()).containsKeys("application-properties", "maven", "gradle");
-    }
-
-}
diff --git a/devtools/platform-descriptor-json/src/test/resources/fakeextensions.json b/devtools/platform-descriptor-json/src/test/resources/fakeextensions.json
deleted file mode 100644
index 5726a1feba9944..00000000000000
--- a/devtools/platform-descriptor-json/src/test/resources/fakeextensions.json
+++ /dev/null
@@ -1,1249 +0,0 @@
-
-{
-    "any-future-property" : "blah",
-    "metadata":{
-        "application-properties": {
-            "quarkus.native.builder-image" : "foo-bar"
-        },
-        "maven" :  {
-            "repositories" : [
-                {
-                    "id": "redhat",
-                    "url": "https://maven.repository.redhat.com",
-                    "releases-enabled": "true",
-                    "snapshots-enabled": "false"
-                }
-            ],
-            "plugin-repositories" : [
-                {
-                    "id": "redhat",
-                    "url": "https://maven.repository.redhat.com",
-                    "releases-enabled": "true",
-                    "snapshots-enabled": "false"
-                }
-            ]
-        },
-        "gradle" :  {
-            "repositories" : [
-                {
-                    "id": "redhat",
-                    "url": "https://maven.repository.redhat.com"
-                }
-            ]
-        }
-    },
-    "bom": {
-        "group-id": "io.quarkus",
-        "artifact-id": "quarkus-bom",
-        "version": "999-SNAPSHOT"
-    },
-    
-    "categories": [
-    	{
-    	"name" : "Web",
-    	"id" : "web",
-    	"description": "web is webby",
-    	"metadata": {
-    		"pinned": [
-    			"x.y.z",
-    			"1.2.3"
-    		]
-    	}	
-    	}
-    ],
-    
-    "extensions": [
-        {
-            "name": "Quarkus - Core",
-            "metadata": {
-                "keywords": [
-                ]
-            },
-            "short-name": "wonka",
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-core",
-            "version": "999-SNAPSHOT",
-            "description": "Build parent to bring in required dependencies"
-        },
-        {
-            "name": "ArC",
-            "short-name": "CDI",
-            "guide": "https://quarkus.io/guides/cdi-reference",
-            "metadata": {
-                "keywords": [
-                    "arc",
-                    "cdi",
-                    "dependency-injection",
-                    "di"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-arc",
-            "version": "999-SNAPSHOT",
-            "description": "Build time CDI dependency injection"
-        },
-        {
-            "metadata": {
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-caffeine",
-            "version": "999-SNAPSHOT",
-            "name": "Quarkus - Caffeine - Runtime",
-            "description": "A high performance caching library for Java 8+"
-        },
-        {
-            "metadata": {
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-jaxb",
-            "version": "999-SNAPSHOT",
-            "name": "Quarkus - JAXB - Runtime",
-            "description": "XML serialization support"
-        },
-        {
-            "name": "Jackson",
-            "metadata": {
-                "keywords": [
-                    "jackson",
-                    "json"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-jackson",
-            "version": "999-SNAPSHOT",
-            "description": "Jackson Databind support"
-        },
-        {
-            "name": "JSON-B",
-            "guide": "https://quarkus.io/guides/rest-json",
-            "metadata": {
-                "keywords": [
-                    "jsonb",
-                    "json-b",
-                    "json"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-jsonb",
-            "version": "999-SNAPSHOT",
-            "description": "JSON Binding support"
-        },
-        {
-            "name": "JSON-P",
-            "metadata": {
-                "keywords": [
-                    "jsonp",
-                    "json-p",
-                    "json"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-jsonp",
-            "version": "999-SNAPSHOT",
-            "description": "JSON Processing support"
-        },
-        {
-            "metadata": {
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-netty",
-            "version": "999-SNAPSHOT",
-            "name": "Quarkus - Netty - Runtime",
-            "description": "Netty is a non-blocking I/O client-server framework. Used by Quarkus as foundation layer."
-        },
-        {
-            "name": "Agroal - Database connection pool",
-            "metadata": {
-                "keywords": [
-                    "agroal",
-                    "database-connection-pool"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-agroal",
-            "version": "999-SNAPSHOT",
-            "description": "Pool your database connections (included in Hibernate ORM)"
-        },
-        {
-            "name": "Artemis Core",
-            "metadata": {
-                "keywords": [
-                    "artemis-core",
-                    "artemis"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-artemis-core",
-            "version": "999-SNAPSHOT",
-            "description": "Use ActiveMQ Artemis as message broker"
-        },
-        {
-            "name": "Artemis JMS",
-            "metadata": {
-                "keywords": [
-                    "artemis-jms",
-                    "artemis"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-artemis-jms",
-            "version": "999-SNAPSHOT",
-            "description": "Use ActiveMQ Artemis as a JMS implementation"
-        },
-        {
-            "metadata": {
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-elasticsearch-rest-client",
-            "version": "999-SNAPSHOT",
-            "name": "Quarkus - Elasticsearch REST client - Runtime",
-            "description": "Elasticsearch REST client"
-        },
-        {
-            "name": "Jaeger",
-            "metadata": {
-                "keywords": [
-                    "jaeger"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-security",
-            "version": "999-SNAPSHOT",
-            "description": "Security"
-        },
-        {
-            "metadata": {
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-elytron-security",
-            "version": "999-SNAPSHOT",
-            "name": "Quarkus - Elytron Security - Runtime",
-            "description": "Secure your services"
-        },
-        {
-            "name": "Properties File based Security",
-            "guide": "https://quarkus.io/guides/security-properties",
-            "metadata": {
-                "keywords": [
-                    "security"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-elytron-security-properties-file",
-            "version": "999-SNAPSHOT",
-            "description": "Secure your applications using properties files"
-        },
-        {
-            "name": "Elytron Security OAuth 2.0",
-            "metadata": {
-                "keywords": [
-                    "security",
-                    "oauth2"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-elytron-security-oauth2",
-            "version": "999-SNAPSHOT",
-            "description": "Secure your applications with OAuth2 opaque tokens"
-        },
-        {
-            "name": "OpenID Connect",
-            "guide": "https://quarkus.io/guides/security-openid-connect",
-            "metadata": {
-                "keywords": [
-                    "oauth2",
-                    "openid-connect"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-oidc",
-            "version": "999-SNAPSHOT",
-            "description": "Secure your applications with OpenID Connect and Keycloak"
-        },
-        {
-            "name": "Flyway",
-            "guide": "https://quarkus.io/guides/flyway",
-            "metadata": {
-                "keywords": [
-                    "flyway",
-                    "database",
-                    "data"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-flyway",
-            "version": "999-SNAPSHOT",
-            "description": "Handle your database schema migrations"
-        },
-        {
-            "name": "Hibernate ORM",
-            "short-name": "JPA",
-            "guide": "https://quarkus.io/guides/hibernate-orm",
-            "metadata": {
-                "keywords": [
-                    "hibernate-orm",
-                    "jpa",
-                    "hibernate"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-hibernate-orm",
-            "version": "999-SNAPSHOT",
-            "description": "Define your persistent model with Hibernate ORM and JPA"
-        },
-        {
-            "name": "Hibernate ORM with Panache",
-            "guide": "https://quarkus.io/guides/hibernate-orm-panache",
-            "metadata": {
-                "keywords": [
-                    "hibernate-orm-panache",
-                    "panache",
-                    "hibernate",
-                    "jpa"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-hibernate-orm-panache",
-            "version": "999-SNAPSHOT",
-            "description": "Define your persistent model in Hibernate ORM with Panache"
-        },
-        {
-            "name": "MongoDB with Panache",
-            "metadata": {
-                "keywords": [
-                    "mongo",
-                    "mongodb",
-                    "nosql",
-                    "datastore",
-                    "panache"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-mongodb-panache",
-            "version": "999-SNAPSHOT",
-            "description": "Use an active record or repository pattern with MongoDB"
-        },
-        {
-            "name": "Hibernate Search + Elasticsearch",
-            "guide": "https://quarkus.io/guides/hibernate-search-orm-elasticsearch",
-            "metadata": {
-                "keywords": [
-                    "hibernate-search-elasticsearch",
-                    "search",
-                    "full-text",
-                    "hibernate",
-                    "elasticsearch"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-hibernate-search-orm-elasticsearch",
-            "version": "999-SNAPSHOT",
-            "description": "Automatically index your Hibernate entities in Elasticsearch"
-        },
-        {
-            "name": "Hibernate Validator",
-            "short-name": "bean validation",
-            "guide": "https://quarkus.io/guides/validation",
-            "metadata": {
-                "keywords": [
-                    "hibernate-validator",
-                    "bean-validation",
-                    "validation"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-hibernate-validator",
-            "version": "999-SNAPSHOT",
-            "description": "Validate data coming to your REST endpoints"
-        },
-        {
-            "name": "Infinispan Client",
-            "guide": "https://quarkus.io/guides/infinispan-client",
-            "metadata": {
-                "keywords": [
-                    "infinispan-client",
-                    "data-grid-client",
-                    "infinispan"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-infinispan-client",
-            "version": "999-SNAPSHOT",
-            "description": "Connect to the Infinispan data grid for distributed caching"
-        },
-        {
-            "name": "Infinispan Embedded",
-            "metadata": {
-                "keywords": [
-                    "infinispan"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-infinispan-embedded",
-            "version": "999-SNAPSHOT",
-            "description": "Run an embedded Infinispan data grid server for distributed caching"
-        },
-        {
-            "name": "Security",
-            "metadata": {
-                "keywords": [
-                    "security"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-jaeger",
-            "version": "999-SNAPSHOT",
-            "description": "Trace your services with Jaeger"
-        },
-        {
-            "name": "JDBC Driver - PostgreSQL",
-            "metadata": {
-                "keywords": [
-                    "jdbc-postgresql",
-                    "jdbc",
-                    "postgresql"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-jdbc-postgresql",
-            "version": "999-SNAPSHOT",
-            "description": "PostgreSQL database connector"
-        },
-        {
-            "name": "JDBC Driver - H2",
-            "metadata": {
-                "keywords": [
-                    "jdbc-h2",
-                    "jdbc",
-                    "h2"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-jdbc-h2",
-            "version": "999-SNAPSHOT",
-            "description": "H2 database connector"
-        },
-        {
-            "name": "JDBC Driver - MariaDB",
-            "metadata": {
-                "keywords": [
-                    "jdbc-mariadb",
-                    "jdbc",
-                    "mariadb"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-jdbc-mariadb",
-            "version": "999-SNAPSHOT",
-            "description": "MariaDB database connector"
-        },
-        {
-            "name": "JDBC Driver - Microsoft SQL Server",
-            "metadata": {
-                "keywords": [
-                    "jdbc-mssql",
-                    "jdbc",
-                    "mssql",
-                    "sql-server"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-jdbc-mssql",
-            "version": "999-SNAPSHOT",
-            "description": "Microsoft SQL Server database connector"
-        },
-        {
-            "name": "JDBC Driver - MySQL",
-            "metadata": {
-                "keywords": [
-                    "jdbc-mysql",
-                    "jdbc",
-                    "mysql"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-jdbc-mysql",
-            "version": "999-SNAPSHOT",
-            "description": "MySQL database connector"
-        },
-        {
-            "metadata": {
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-jdbc-derby",
-            "version": "999-SNAPSHOT",
-            "name": "Quarkus - JDBC - Derby - Runtime",
-            "description": "Derby database connector"
-        },
-        {
-            "name": "Apache Kafka Client",
-            "metadata": {
-                "keywords": [
-                    "kafka"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-kafka-client",
-            "version": "999-SNAPSHOT",
-            "description": "A client for Apache Kafka"
-        },
-        {
-            "name": "Apache Kafka Streams",
-            "metadata": {
-                "keywords": [
-                    "kafka",
-                    "kafka-streams"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-kafka-streams",
-            "version": "999-SNAPSHOT",
-            "description": "Implement stream processing applications based on Apache Kafka"
-        },
-        {
-            "name": "SmallRye Health",
-            "short-name": "health",
-            "guide": "https://quarkus.io/guides/microprofile-health",
-            "metadata": {
-                "keywords": [
-                    "smallrye-health",
-                    "health-check",
-                    "health",
-                    "microprofile-health",
-                    "microprofile-health-check"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-smallrye-health",
-            "version": "999-SNAPSHOT",
-            "description": "Monitor service health"
-        },
-        {
-            "name": "SmallRye JWT",
-            "guide": "https://quarkus.io/guides/security-jwt",
-            "metadata": {
-                "keywords": [
-                    "smallrye-jwt",
-                    "jwt",
-                    "json-web-token",
-                    "rbac"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-smallrye-jwt",
-            "version": "999-SNAPSHOT",
-            "description": "Secure your applications with JSON Web Token"
-        },
-        {
-            "name": "SmallRye Context Propagation",
-            "short-name": "context propagation",
-            "metadata": {
-                "keywords": [
-                    "smallrye-context-propagation",
-                    "microprofile-context-propagation",
-                    "context-propagation",
-                    "context",
-                    "reactive"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-smallrye-context-propagation",
-            "version": "999-SNAPSHOT",
-            "description": "SmallRye Context Propagation"
-        },
-        {
-            "name": "SmallRye Reactive Streams Operators",
-            "short-name": "reactive streams",
-            "metadata": {
-                "keywords": [
-                    "smallrye-reactive-streams-operators",
-                    "smallrye-reactive-streams",
-                    "reactive-streams-operators",
-                    "reactive-streams",
-                    "microprofile-reactive-streams",
-                    "reactive"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-smallrye-reactive-streams-operators",
-            "version": "999-SNAPSHOT",
-            "description": "Operators for Reactive Streams programming"
-        },
-        {
-            "name": "SmallRye Reactive Type Converters",
-            "metadata": {
-                "keywords": [
-                    "smallrye-reactive-type-converters",
-                    "reactive-type-converters",
-                    "reactive-streams-operators",
-                    "reactive-streams",
-                    "microprofile-reactive-streams",
-                    "reactive"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-smallrye-reactive-type-converters",
-            "version": "999-SNAPSHOT",
-            "description": "Converters for reactive types from various reactive programming libraries"
-        },
-        {
-            "name": "SmallRye Reactive Messaging",
-            "guide": "https://quarkus.io/guides/reactive-messaging",
-            "metadata": {
-                "keywords": [
-                    "smallrye-reactive-messaging",
-                    "reactive-messaging",
-                    "reactive"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-smallrye-reactive-messaging",
-            "version": "999-SNAPSHOT",
-            "description": "Asynchronous messaging for Reactive Streams"
-        },
-        {
-            "name": "SmallRye Reactive Messaging - Kafka Connector",
-            "short-name": "kafka",
-            "guide": "https://quarkus.io/guides/kafka",
-            "metadata": {
-                "keywords": [
-                    "kafka",
-                    "reactive-kafka"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-smallrye-reactive-messaging-kafka",
-            "version": "999-SNAPSHOT",
-            "description": "Kafka reactive messaging connector"
-        },
-        {
-            "name": "SmallRye Reactive Messaging - AMQP Connector",
-            "guide": "https://quarkus.io/guides/amqp",
-            "metadata": {
-                "keywords": [
-                    "amqp",
-                    "reactive-amqp"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-smallrye-reactive-messaging-amqp",
-            "version": "999-SNAPSHOT",
-            "description": "AMQP reactive messaging connector"
-        },
-        {
-            "name": "SmallRye Reactive Messaging - MQTT Connector",
-            "metadata": {
-                "keywords": [
-                    "mqtt",
-                    "reactive-mqtt"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-smallrye-reactive-messaging-mqtt",
-            "version": "999-SNAPSHOT",
-            "description": "MQTT reactive messaging connector"
-        },
-        {
-            "name": "SmallRye Metrics",
-            "short-name": "metrics",
-            "guide": "https://quarkus.io/guides/microprofile-metrics",
-            "metadata": {
-                "keywords": [
-                    "smallrye-metrics",
-                    "metrics",
-                    "metric",
-                    "prometheus",
-                    "monitoring"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-smallrye-metrics",
-            "version": "999-SNAPSHOT",
-            "description": "Extract metrics out of your services"
-        },
-        {
-            "name": "SmallRye OpenAPI",
-            "guide": "https://quarkus.io/guides/openapi-swaggerui",
-            "metadata": {
-                "keywords": [
-                    "smallrye-openapi",
-                    "openapi",
-                    "open-api"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-smallrye-openapi",
-            "version": "999-SNAPSHOT",
-            "description": "Document your REST APIs with OpenAPI - comes with Swagger UI"
-        },
-        {
-            "name": "SmallRye OpenTracing",
-            "guide": "https://quarkus.io/guides/opentracing",
-            "metadata": {
-                "keywords": [
-                    "smallrye-opentracing",
-                    "opentracing",
-                    "tracing",
-                    "distributed-tracing",
-                    "jaeger"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-smallrye-opentracing",
-            "version": "999-SNAPSHOT",
-            "description": "Trace your services with Jaeger"
-        },
-        {
-            "name": "REST Client",
-            "guide": "https://quarkus.io/guides/rest-client",
-            "metadata": {
-                "keywords": [
-                    "rest-client",
-                    "web-client",
-                    "microprofile-rest-client"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-rest-client",
-            "version": "999-SNAPSHOT",
-            "description": "Call REST services"
-        },
-        {
-            "metadata": {
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-resteasy-common",
-            "version": "999-SNAPSHOT",
-            "name": "Quarkus - RESTEasy - Common - Runtime",
-            "description": "REST framework implementing JAX-RS and more"
-        },
-        {
-            "name": "RESTEasy JAX-RS",
-            "short-name": "jax-rs",
-            "guide": "https://quarkus.io/guides/rest-json",
-            "metadata": {
-                "keywords": [
-                    "resteasy",
-                    "jaxrs",
-                    "web",
-                    "rest"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-resteasy",
-            "version": "999-SNAPSHOT",
-            "description": "REST framework implementing JAX-RS and more"
-        },
-        {
-            "name": "RESTEasy Jackson",
-            "metadata": {
-                "keywords": [
-                    "resteasy-jackson",
-                    "jaxrs-json",
-                    "resteasy-json",
-                    "resteasy",
-                    "jaxrs",
-                    "json",
-                    "jackson"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-resteasy-jackson",
-            "version": "999-SNAPSHOT",
-            "description": "Jackson serialization support for RESTEasy"
-        },
-        {
-            "name": "RESTEasy JSON-B",
-            "guide": "https://quarkus.io/guides/rest-json",
-            "metadata": {
-                "keywords": [
-                    "resteasy-jsonb",
-                    "jaxrs-json",
-                    "resteasy-json",
-                    "resteasy",
-                    "jaxrs",
-                    "json",
-                    "jsonb"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-resteasy-jsonb",
-            "version": "999-SNAPSHOT",
-            "description": "JSON-B serialization support for RESTEasy"
-        },
-        {
-            "metadata": {
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-resteasy-jaxb",
-            "version": "999-SNAPSHOT",
-            "name": "Quarkus - RESTEasy - JAXB - Runtime",
-            "description": "XML serialization support for RESTEasy"
-        },
-        {
-            "metadata": {
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-resteasy-server-common",
-            "version": "999-SNAPSHOT",
-            "name": "Quarkus - RESTEasy - Server common - Runtime",
-            "description": "RESTEasy Server common"
-        },
-        {
-            "name": "Narayana JTA - Transaction manager",
-            "guide": "https://quarkus.io/guides/transaction",
-            "metadata": {
-                "keywords": [
-                    "narayana-jta",
-                    "narayana",
-                    "jta",
-                    "transactions",
-                    "transaction",
-                    "tx",
-                    "txs"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-narayana-jta",
-            "version": "999-SNAPSHOT",
-            "description": "JTA transaction support (included in Hibernate ORM)"
-        },
-        {
-            "name": "Undertow Servlet",
-            "short-name": "servlet",
-            "metadata": {
-                "keywords": [
-                    "undertow",
-                    "servlet"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-undertow",
-            "version": "999-SNAPSHOT",
-            "description": "Support for servlets"
-        },
-        {
-            "name": "SmallRye Fault Tolerance",
-            "metadata": {
-                "keywords": [
-                    "smallrye-fault-tolerance",
-                    "fault-tolerance",
-                    "microprofile-fault-tolerance",
-                    "circuit-breaker",
-                    "bulkhead"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-smallrye-fault-tolerance",
-            "version": "999-SNAPSHOT",
-            "description": "Define fault-tolerant services"
-        },
-        {
-            "name": "Eclipse Vert.x - Core",
-            "metadata": {
-                "keywords": [
-                    "eclipse-vert.x",
-                    "vertx",
-                    "vert.x",
-                    "reactive"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-vertx-core",
-            "version": "999-SNAPSHOT",
-            "description": "Vert.x Core"
-        },
-        {
-            "name": "Eclipse Vert.x",
-            "guide": "https://quarkus.io/guides/vertx",
-            "metadata": {
-                "keywords": [
-                    "eclipse-vert.x",
-                    "vertx",
-                    "vert.x",
-                    "reactive"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-vertx",
-            "version": "999-SNAPSHOT",
-            "description": "Reactive application toolkit"
-        },
-        {
-            "name": "Eclipse Vert.x - HTTP",
-            "metadata": {
-                "keywords": [
-                    "eclipse-vert.x",
-                    "vertx",
-                    "vert.x",
-                    "reactive",
-                    "vertx-http"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-vertx-http",
-            "version": "999-SNAPSHOT",
-            "description": "Vert.x HTTP"
-        },
-        {
-            "metadata": {
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-vertx-web",
-            "version": "999-SNAPSHOT",
-            "name": "Quarkus - Vert.x Web - Runtime",
-            "description": "Vert.x Web"
-        },
-        {
-            "name": "Reactive PostgreSQL client",
-            "metadata": {
-                "keywords": [
-                    "eclipse-vert.x",
-                    "vertx",
-                    "vert.x",
-                    "reactive",
-                    "database",
-                    "data",
-                    "postgresql"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-reactive-pg-client",
-            "version": "999-SNAPSHOT",
-            "description": "A reactive client for the PostgreSQL database"
-        },
-        {
-            "name": "Reactive MySQL client",
-            "metadata": {
-                "keywords": [
-                    "eclipse-vert.x",
-                    "vertx",
-                    "vert.x",
-                    "reactive",
-                    "database",
-                    "data",
-                    "mysql"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-reactive-mysql-client",
-            "version": "999-SNAPSHOT",
-            "description": "A reactive client for the MySQL database"
-        },
-        {
-            "name": "Mailer",
-            "guide": "https://quarkus.io/guides/mailer",
-            "metadata": {
-                "keywords": [
-                    "mail",
-                    "mailer"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-mailer",
-            "version": "999-SNAPSHOT",
-            "description": "Send emails"
-        },
-        {
-            "name": "MongoDB client",
-            "metadata": {
-                "keywords": [
-                    "mongo",
-                    "mongodb",
-                    "nosql",
-                    "datastore"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-mongodb-client",
-            "version": "999-SNAPSHOT",
-            "description": "An imperative and reactive client for MongoDB"
-        },
-        {
-            "name": "WebSockets",
-            "short-name": "websockets",
-            "guide": "https://quarkus.io/guides/websockets",
-            "metadata": {
-                "keywords": [
-                    "undertow-websockets",
-                    "undertow-websocket",
-                    "websocket",
-                    "websockets",
-                    "web-socket",
-                    "web-sockets"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-websockets",
-            "version": "999-SNAPSHOT",
-            "description": "WebSocket support"
-        },
-        {
-            "name": "Scheduler - tasks",
-            "guide": "https://quarkus.io/guides/scheduler",
-            "metadata": {
-                "keywords": [
-                    "scheduler",
-                    "tasks",
-                    "periodic-tasks"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-scheduler",
-            "version": "999-SNAPSHOT",
-            "description": "Schedule jobs and tasks"
-        },
-        {
-            "name": "Quarkus Extension for Spring DI API",
-            "guide": "https://quarkus.io/guides/spring-di",
-            "metadata": {
-                "keywords": [
-                    "spring-di"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-spring-di",
-            "version": "999-SNAPSHOT",
-            "description": "Define your dependency injection with Spring DI"
-        },
-        {
-            "name": "Quarkus Extension for Spring Web API",
-            "guide": "https://quarkus.io/guides/spring-web",
-            "metadata": {
-                "keywords": [
-                    "spring-web"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-spring-web",
-            "version": "999-SNAPSHOT",
-            "description": "Use Spring Web annotations to create your REST services"
-        },
-        {
-            "name": "Quarkus Extension for Spring Data JPA API",
-            "guide": "https://quarkus.io/guides/spring-data-jpa",
-            "metadata": {
-                "keywords": [
-                    "spring-data"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-spring-data-jpa",
-            "version": "999-SNAPSHOT",
-            "description": "Use Spring Data JPA annotations to create your data access layer"
-        },
-        {
-            "name": "Swagger UI",
-            "guide": "https://quarkus.io/guides/openapi-swaggerui",
-            "metadata": {
-                "keywords": [
-                    "swagger-ui"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-swagger-ui",
-            "version": "999-SNAPSHOT",
-            "description": "Swagger UI"
-        },
-        {
-            "name": "Kotlin",
-            "guide": "https://quarkus.io/guides/kotlin",
-            "metadata": {
-                "keywords": [
-                    "kotlin"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-kotlin",
-            "version": "999-SNAPSHOT",
-            "description": "Write your services in Kotlin"
-        },
-        {
-            "name": "AWS Lambda",
-            "metadata": {
-                "keywords": [
-                    "lambda",
-                    "aws",
-                    "amazon"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-amazon-lambda",
-            "version": "999-SNAPSHOT",
-            "description": "AWS Lambda support"
-        },
-        {
-            "metadata": {
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-amazon-lambda-http",
-            "version": "999-SNAPSHOT",
-            "name": "Quarkus - Amazon Lambda HTTP - Runtime",
-            "description": "Allows Java applications written for a servlet container to run in AWS Lambda"
-        },
-        {
-            "name": "Amazon DynamoDB client",
-            "metadata": {
-                "keywords": [
-                    "dynamodb",
-                    "dynamo",
-                    "aws",
-                    "amazon"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-amazon-dynamodb",
-            "version": "999-SNAPSHOT",
-            "description": "A client for the Amazon DynamoDB datastore"
-        },
-        {
-            "metadata": {
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-azure-functions-http",
-            "version": "999-SNAPSHOT",
-            "name": "Quarkus - HTTP Azure Functions - Runtime",
-            "description": "This package contains all Java interfaces and annotations to interact with Microsoft Azure functions runtime."
-        },
-        {
-            "name": "Kubernetes",
-            "guide": "https://quarkus.io/guides/kubernetes",
-            "metadata": {
-                "keywords": [
-                    "kubernetes"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-kubernetes",
-            "version": "999-SNAPSHOT",
-            "description": "Generate Kubernetes resources from annotations"
-        },
-        {
-            "name": "Kubernetes Client",
-            "metadata": {
-                "keywords": [
-                    "kubernetes-client"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-kubernetes-client",
-            "version": "999-SNAPSHOT",
-            "description": "Interact with Kubernetes and develop Kubernetes Operators"
-        },
-        {
-            "name": "Kogito",
-            "guide": "https://quarkus.io/guides/kogito",
-            "metadata": {
-                "keywords": [
-                    "kogito",
-                    "drools",
-                    "jbpm"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-kogito",
-            "version": "999-SNAPSHOT",
-            "description": "Add business automation capabilities with Kogito"
-        },
-        {
-            "name": "Apache Tika",
-            "metadata": {
-                "keywords": [
-                    "tika",
-                    "parser"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-tika",
-            "version": "999-SNAPSHOT",
-            "description": "Extract data from your documents with Apache Tika"
-        },
-        {
-            "name": "Neo4j client",
-            "metadata": {
-                "keywords": [
-                    "neo4j",
-                    "graph",
-                    "nosql",
-                    "datastore"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-neo4j",
-            "version": "999-SNAPSHOT",
-            "description": "A client for the Neo4j graph datastore"
-        },
-        {
-            "name": "Scala",
-            "metadata": {
-                "keywords": [
-                    "scala"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-scala",
-            "version": "999-SNAPSHOT",
-            "description": "Write your services in Scala"
-        },
-        {
-            "name": "JGit",
-            "metadata": {
-                "keywords": [
-                    "git"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-jgit",
-            "version": "999-SNAPSHOT",
-            "description": "Access your Git repositories"
-        },
-        {
-            "name": "Narayana STM - Software Transactional Memory",
-            "guide": "https://quarkus.io/guides/software-transactional-memory",
-            "metadata": {
-                "keywords": [
-                    "narayana-stm",
-                    "narayana",
-                    "stm",
-                    "transactions",
-                    "transaction",
-                    "software-transactional-memory",
-                    "tx",
-                    "txs"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-narayana-stm",
-            "version": "999-SNAPSHOT",
-            "description": "Software Transactional Memory (stm) support"
-        },
-        {
-            "name": "Elytron Security JDBC Realm",
-            "guide": "https://quarkus.io/guides/security-jdbc",
-            "metadata": {
-                "keywords": [
-                    "security",
-                    "jdbc"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-elytron-security-jdbc",
-            "version": "999-SNAPSHOT",
-            "description": "Secure your applications with username/password stored in a database"
-        },
-        {
-            "name": "Vault",
-            "guide": "https://quarkus.io/guides/vault",
-            "metadata": {
-                "keywords": [
-                    "vault",
-                    "security"
-                ]
-            },
-            "group-id": "io.quarkus",
-            "artifact-id": "quarkus-vault",
-            "version": "999-SNAPSHOT",
-            "description": "Store your credentials securely in HashiCorp Vault"
-        }
-    ]
-}
diff --git a/docs/pom.xml b/docs/pom.xml
index 6624a9ccb0c768..b5ff6cb6492c1a 100644
--- a/docs/pom.xml
+++ b/docs/pom.xml
@@ -38,8 +38,12 @@
         </dependency>
         <dependency>
             <groupId>io.quarkus</groupId>
-            <artifactId>quarkus-bootstrap-core</artifactId>
+            <artifactId>quarkus-devtools-registry-client</artifactId>
             <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-bootstrap-core</artifactId>
             <exclusions>
                 <exclusion>
                     <groupId>org.slf4j</groupId>
diff --git a/docs/src/main/java/io/quarkus/docs/generation/AllConfigGenerator.java b/docs/src/main/java/io/quarkus/docs/generation/AllConfigGenerator.java
index 264bfa90b81ac0..5fb7b4c9996041 100644
--- a/docs/src/main/java/io/quarkus/docs/generation/AllConfigGenerator.java
+++ b/docs/src/main/java/io/quarkus/docs/generation/AllConfigGenerator.java
@@ -21,11 +21,6 @@
 import org.eclipse.aether.resolution.ArtifactRequest;
 import org.eclipse.aether.resolution.ArtifactResult;
 
-import com.fasterxml.jackson.core.json.JsonReadFeature;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.PropertyNamingStrategies;
-import com.fasterxml.jackson.databind.json.JsonMapper;
-
 import io.quarkus.annotation.processor.generate_doc.ConfigDocGeneratedOutput;
 import io.quarkus.annotation.processor.generate_doc.ConfigDocItem;
 import io.quarkus.annotation.processor.generate_doc.ConfigDocItemScanner;
@@ -34,9 +29,18 @@
 import io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil;
 import io.quarkus.bootstrap.resolver.maven.BootstrapMavenException;
 import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver;
-import io.quarkus.docs.generation.ExtensionJson.Extension;
+import io.quarkus.maven.ArtifactCoords;
+import io.quarkus.registry.catalog.Extension;
+import io.quarkus.registry.catalog.json.JsonCatalogMapperHelper;
+import io.quarkus.registry.catalog.json.JsonExtensionCatalog;
 
 public class AllConfigGenerator {
+
+    public static Artifact toAetherArtifact(ArtifactCoords coords) {
+        return new DefaultArtifact(coords.getGroupId(), coords.getArtifactId(), coords.getClassifier(), coords.getType(),
+                coords.getVersion());
+    }
+
     public static void main(String[] args) throws BootstrapMavenException, IOException {
         if (args.length != 2) {
             // exit 1 will break Maven
@@ -52,31 +56,26 @@ public static void main(String[] args) throws BootstrapMavenException, IOExcepti
             // exit 0 will break Maven
             return;
         }
-        ObjectMapper mapper = JsonMapper.builder()
-                .enable(JsonReadFeature.ALLOW_JAVA_COMMENTS)
-                .enable(JsonReadFeature.ALLOW_LEADING_ZEROS_FOR_NUMBERS)
-                .propertyNamingStrategy(PropertyNamingStrategies.KEBAB_CASE)
-                .build();
         MavenArtifactResolver resolver = MavenArtifactResolver.builder().setWorkspaceDiscovery(false).build();
 
-        // let's read it (and ignore the fields we don't need)
-        ExtensionJson extensionJson = mapper.readValue(jsonFile, ExtensionJson.class);
+        final JsonExtensionCatalog extensionJson = JsonCatalogMapperHelper.deserialize(jsonFile.toPath(),
+                JsonExtensionCatalog.class);
 
         // now get all the listed extension jars via Maven
-        List<ArtifactRequest> requests = new ArrayList<>(extensionJson.extensions.size());
+        List<ArtifactRequest> requests = new ArrayList<>(extensionJson.getExtensions().size());
         Map<String, Extension> extensionsByGav = new HashMap<>();
         Map<String, Extension> extensionsByConfigRoots = new HashMap<>();
-        for (Extension extension : extensionJson.extensions) {
+        for (Extension extension : extensionJson.getExtensions()) {
             ArtifactRequest request = new ArtifactRequest();
-            Artifact artifact = new DefaultArtifact(extension.groupId, extension.artifactId, "jar", version);
+            Artifact artifact = toAetherArtifact(extension.getArtifact());
             request.setArtifact(artifact);
             requests.add(request);
             // record the extension for this GAV
-            extensionsByGav.put(extension.groupId + ":" + extension.artifactId, extension);
+            extensionsByGav.put(artifact.getGroupId() + ":" + artifact.getArtifactId(), extension);
         }
 
         // examine all the extension jars
-        List<ArtifactRequest> deploymentRequests = new ArrayList<>(extensionJson.extensions.size());
+        List<ArtifactRequest> deploymentRequests = new ArrayList<>(extensionJson.getExtensions().size());
         for (ArtifactResult result : resolver.resolve(requests)) {
             Artifact artifact = result.getArtifact();
             // which extension was this for?
@@ -140,17 +139,18 @@ public static void main(String[] args) throws BootstrapMavenException, IOExcepti
         for (Entry<String, Extension> entry : extensionsByConfigRoots.entrySet()) {
             List<ConfigDocItem> items = docItemsByConfigRoots.get(entry.getKey());
             if (items != null) {
-                String extensionName = entry.getValue().name;
+                String extensionName = entry.getValue().getName();
                 if (extensionName == null) {
-                    String extensionGav = entry.getValue().groupId + ":" + entry.getValue().artifactId;
                     // compute the docs file name for this extension
                     String docFileName = DocGeneratorUtil.computeExtensionDocFileName(entry.getKey());
                     // now approximate an extension file name based on it
                     extensionName = guessExtensionNameFromDocumentationFileName(docFileName);
-                    System.err.println("WARNING: Extension name missing for " + extensionGav + " using guessed extension name: "
-                            + extensionName);
+                    System.err.println("WARNING: Extension name missing for "
+                            + (entry.getValue().getArtifact().getGroupId() + ":"
+                                    + entry.getValue().getArtifact().getArtifactId())
+                            + " using guessed extension name: " + extensionName);
                 }
-                artifactIdsByName.put(extensionName, entry.getValue().artifactId);
+                artifactIdsByName.put(extensionName, entry.getValue().getArtifact().getArtifactId());
                 List<ConfigDocItem> existingConfigDocItems = sortedConfigItemsByExtension.get(extensionName);
                 if (existingConfigDocItems != null) {
                     DocGeneratorUtil.appendConfigItemsIntoExistingOnes(existingConfigDocItems, items);
diff --git a/docs/src/main/java/io/quarkus/docs/generation/ExtensionJson.java b/docs/src/main/java/io/quarkus/docs/generation/ExtensionJson.java
deleted file mode 100644
index c81ae589119246..00000000000000
--- a/docs/src/main/java/io/quarkus/docs/generation/ExtensionJson.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package io.quarkus.docs.generation;
-
-import java.util.List;
-
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-
-@JsonIgnoreProperties(ignoreUnknown = true)
-public class ExtensionJson {
-    public List<Extension> extensions;
-
-    @JsonIgnoreProperties(ignoreUnknown = true)
-    public static class Extension {
-        public String name, groupId, artifactId;
-    }
-}
\ No newline at end of file
diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/AppArtifact.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/AppArtifact.java
index 54065a7b2b4ce1..264ebd9ec2b692 100644
--- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/AppArtifact.java
+++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/AppArtifact.java
@@ -12,6 +12,10 @@ public class AppArtifact extends AppArtifactCoords implements Serializable {
 
     protected PathsCollection paths;
 
+    public AppArtifact(AppArtifactCoords coords) {
+        this(coords.getGroupId(), coords.getArtifactId(), coords.getClassifier(), coords.getType(), coords.getVersion());
+    }
+
     public AppArtifact(String groupId, String artifactId, String version) {
         super(groupId, artifactId, version);
     }
diff --git a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/ResolverSetupCleanup.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/ResolverSetupCleanup.java
index 0c0ce539333513..ca5ed97db15397 100644
--- a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/ResolverSetupCleanup.java
+++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/ResolverSetupCleanup.java
@@ -58,7 +58,7 @@ protected boolean cleanWorkDir() {
 
     protected BootstrapAppModelResolver initResolver(LocalProject currentProject) throws Exception {
         return new BootstrapAppModelResolver(MavenArtifactResolver.builder()
-                .setRepoHome(repoHome)
+                .setLocalRepository(repoHome.toString())
                 .setOffline(true)
                 .setWorkspaceDiscovery(false)
                 .setCurrentProject(currentProject)
diff --git a/independent-projects/bootstrap/maven-plugin/src/test/java/io/quarkus/maven/TreeMojoTestBase.java b/independent-projects/bootstrap/maven-plugin/src/test/java/io/quarkus/maven/TreeMojoTestBase.java
index 30d5d5fd120a26..fcf9969458c85a 100644
--- a/independent-projects/bootstrap/maven-plugin/src/test/java/io/quarkus/maven/TreeMojoTestBase.java
+++ b/independent-projects/bootstrap/maven-plugin/src/test/java/io/quarkus/maven/TreeMojoTestBase.java
@@ -42,7 +42,7 @@ public void setup() throws Exception {
 
         mvnResolver = MavenArtifactResolver.builder()
                 .setOffline(true)
-                .setRepoHome(repoHome)
+                .setLocalRepository(repoHome.toString())
                 .setRemoteRepositories(Collections.emptyList())
                 .build();
 
diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/MavenArtifactResolver.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/MavenArtifactResolver.java
index 911ae10d745bd0..4a2e108a3cf95c 100644
--- a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/MavenArtifactResolver.java
+++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/MavenArtifactResolver.java
@@ -4,6 +4,8 @@
 package io.quarkus.bootstrap.resolver.maven;
 
 import io.quarkus.bootstrap.model.AppArtifactKey;
+import io.quarkus.bootstrap.resolver.AppModelResolverException;
+import io.quarkus.bootstrap.util.PropertyUtils;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
@@ -19,6 +21,7 @@
 import org.eclipse.aether.RepositorySystem;
 import org.eclipse.aether.RepositorySystemSession;
 import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.artifact.DefaultArtifact;
 import org.eclipse.aether.collection.CollectRequest;
 import org.eclipse.aether.collection.CollectResult;
 import org.eclipse.aether.collection.DependencyCollectionException;
@@ -28,7 +31,6 @@
 import org.eclipse.aether.impl.RemoteRepositoryManager;
 import org.eclipse.aether.installation.InstallRequest;
 import org.eclipse.aether.installation.InstallationException;
-import org.eclipse.aether.repository.LocalRepository;
 import org.eclipse.aether.repository.RemoteRepository;
 import org.eclipse.aether.resolution.ArtifactDescriptorException;
 import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
@@ -45,6 +47,7 @@
 import org.eclipse.aether.util.artifact.JavaScopes;
 import org.eclipse.aether.util.version.GenericVersionScheme;
 import org.eclipse.aether.version.InvalidVersionSpecificationException;
+import org.eclipse.aether.version.Version;
 
 /**
  *
@@ -52,36 +55,18 @@
  */
 public class MavenArtifactResolver {
 
+    private static final String SECONDARY_LOCAL_REPO_PROP = "io.quarkus.maven.secondary-local-repo";
+
     public static class Builder extends BootstrapMavenContextConfig<Builder> {
 
-        private boolean reTryFailedResolutionsAgainstDefaultLocalRepo;
+        private Path secondaryLocalRepo;
 
         private Builder() {
             super();
         }
 
-        /**
-         * In case custom local repository location is configured using {@link #setRepoHome(Path)},
-         * this method can be used to enable artifact resolutions that failed for the configured
-         * custom local repository to be re-tried against the default user local repository before
-         * failing.
-         * <p>
-         * NOTE: the default behavior is <b>not</b> to use the default user local repository as the fallback one.
-         *
-         * @param value true if the failed resolution requests should be re-tried against the default
-         *        user local repo before failing
-         *
-         * @return this builder instance
-         */
-        public Builder setReTryFailedResolutionsAgainstDefaultLocalRepo(boolean value) {
-            this.reTryFailedResolutionsAgainstDefaultLocalRepo = value;
-            return this;
-        }
-
-        public Builder setRepoHome(Path home) {
-            if (home != null) {
-                setLocalRepository(home.toString());
-            }
+        public Builder setSecondaryLocalRepo(Path secondaryLocalRepo) {
+            this.secondaryLocalRepo = secondaryLocalRepo;
             return this;
         }
 
@@ -106,10 +91,14 @@ private MavenArtifactResolver(Builder builder) throws BootstrapMavenException {
         this.repoSystem = context.getRepositorySystem();
 
         final RepositorySystemSession session = context.getRepositorySystemSession();
-        if (builder.localRepo != null && builder.reTryFailedResolutionsAgainstDefaultLocalRepo) {
+        final String secondaryRepo = PropertyUtils.getProperty(SECONDARY_LOCAL_REPO_PROP);
+        if (secondaryRepo != null) {
+            builder.secondaryLocalRepo = Paths.get(secondaryRepo);
+        }
+        if (builder.secondaryLocalRepo != null) {
             localRepoManager = new MavenLocalRepositoryManager(
-                    repoSystem.newLocalRepositoryManager(session, new LocalRepository(builder.localRepo)),
-                    Paths.get(context.getLocalRepo()));
+                    session.getLocalRepositoryManager(),
+                    builder.secondaryLocalRepo);
             this.repoSession = new DefaultRepositorySystemSession(session).setLocalRepositoryManager(localRepoManager);
         } else {
             this.repoSession = session;
@@ -219,6 +208,26 @@ public VersionRangeResult resolveVersionRange(Artifact artifact) throws Bootstra
         }
     }
 
+    public String getLatestVersionFromRange(Artifact artifact, String range) throws AppModelResolverException {
+        return getLatest(resolveVersionRange(new DefaultArtifact(artifact.getGroupId(), artifact.getArtifactId(),
+                artifact.getClassifier(), artifact.getExtension(), range)));
+    }
+
+    private String getLatest(final VersionRangeResult rangeResult) {
+        final List<Version> versions = rangeResult.getVersions();
+        if (versions.isEmpty()) {
+            return null;
+        }
+        Version next = versions.get(0);
+        for (int i = 1; i < versions.size(); ++i) {
+            final Version candidate = versions.get(i);
+            if (candidate.compareTo(next) > 0) {
+                next = candidate;
+            }
+        }
+        return next.toString();
+    }
+
     public CollectResult collectDependencies(Artifact artifact, List<Dependency> deps) throws BootstrapMavenException {
         return collectDependencies(artifact, deps, Collections.emptyList());
     }
diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/MavenLocalRepositoryManager.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/MavenLocalRepositoryManager.java
index b59764b9340ee5..bf1b4ab3f89854 100644
--- a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/MavenLocalRepositoryManager.java
+++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/MavenLocalRepositoryManager.java
@@ -24,19 +24,13 @@
 public class MavenLocalRepositoryManager implements LocalRepositoryManager {
 
     private final LocalRepositoryManager delegate;
-    private final Path userLocalRepo;
-    private final Path appCreatorRepo;
-    private final boolean relinkResolvedArtifacts;
+    private final Path secondaryRepo;
+    private final Path originalRepo;
 
-    public MavenLocalRepositoryManager(LocalRepositoryManager delegate, Path userLocalRepo) {
-        this(delegate, userLocalRepo, false);
-    }
-
-    public MavenLocalRepositoryManager(LocalRepositoryManager delegate, Path userLocalRepo, boolean relinkResolvedArtifacts) {
+    public MavenLocalRepositoryManager(LocalRepositoryManager delegate, Path secondaryRepo) {
         this.delegate = delegate;
-        this.userLocalRepo = userLocalRepo;
-        this.appCreatorRepo = delegate.getRepository().getBasedir().toPath();
-        this.relinkResolvedArtifacts = relinkResolvedArtifacts;
+        this.secondaryRepo = secondaryRepo;
+        this.originalRepo = delegate.getRepository().getBasedir().toPath();
     }
 
     @Override
@@ -65,7 +59,7 @@ public String getPathForRemoteMetadata(Metadata metadata, RemoteRepository repos
     }
 
     public void relink(String groupId, String artifactId, String classifier, String type, String version, Path p) {
-        final Path creatorRepoPath = getLocalPath(appCreatorRepo, groupId, artifactId, classifier, type, version);
+        final Path creatorRepoPath = getLocalPath(originalRepo, groupId, artifactId, classifier, type, version);
         try {
             IoUtils.copy(p, creatorRepoPath);
         } catch (IOException e) {
@@ -80,23 +74,12 @@ public LocalArtifactResult find(RepositorySystemSession session, LocalArtifactRe
             return result;
         }
         final Artifact artifact = request.getArtifact();
-        final Path userRepoPath = getLocalPath(userLocalRepo, artifact.getGroupId(), artifact.getArtifactId(),
+        final Path secondaryLocation = getLocalPath(secondaryRepo, artifact.getGroupId(), artifact.getArtifactId(),
                 artifact.getClassifier(), artifact.getExtension(), artifact.getVersion());
-        if (!Files.exists(userRepoPath)) {
+        if (!Files.exists(secondaryLocation)) {
             return result;
         }
-        if (relinkResolvedArtifacts) {
-            final Path creatorRepoPath = getLocalPath(appCreatorRepo, artifact.getGroupId(), artifact.getArtifactId(),
-                    artifact.getClassifier(), artifact.getExtension(), artifact.getVersion());
-            try {
-                IoUtils.copy(userRepoPath, creatorRepoPath);
-            } catch (IOException e) {
-                throw new IllegalStateException("Failed to copy " + userRepoPath + " to a staging repo", e);
-            }
-            result.setFile(creatorRepoPath.toFile());
-        } else {
-            result.setFile(userRepoPath.toFile());
-        }
+        result.setFile(secondaryLocation.toFile());
         artifact.setFile(result.getFile());
         result.setAvailable(true);
         return result;
@@ -114,23 +97,12 @@ public LocalMetadataResult find(RepositorySystemSession session, LocalMetadataRe
             return result;
         }
         final Metadata metadata = request.getMetadata();
-        final Path userRepoPath = getMetadataPath(userLocalRepo, metadata.getGroupId(), metadata.getArtifactId(),
+        final Path userRepoPath = getMetadataPath(secondaryRepo, metadata.getGroupId(), metadata.getArtifactId(),
                 metadata.getType(), metadata.getVersion());
         if (!Files.exists(userRepoPath)) {
             return result;
         }
-        if (relinkResolvedArtifacts) {
-            final Path creatorRepoPath = getMetadataPath(appCreatorRepo, metadata.getGroupId(), metadata.getArtifactId(),
-                    metadata.getType(), metadata.getVersion());
-            try {
-                IoUtils.copy(userRepoPath, creatorRepoPath);
-            } catch (IOException e) {
-                throw new IllegalStateException("Failed to copy " + userRepoPath + " to a staging repo", e);
-            }
-            result.setFile(creatorRepoPath.toFile());
-        } else {
-            result.setFile(userRepoPath.toFile());
-        }
+        result.setFile(userRepoPath.toFile());
         metadata.setFile(result.getFile());
         return result;
     }
diff --git a/independent-projects/tools/artifact-api/pom.xml b/independent-projects/tools/artifact-api/pom.xml
new file mode 100644
index 00000000000000..0aee54bb768f53
--- /dev/null
+++ b/independent-projects/tools/artifact-api/pom.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>io.quarkus</groupId>
+        <artifactId>quarkus-tools-parent</artifactId>
+        <version>999-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>quarkus-devtools-artifact-api</artifactId>
+    <name>Quarkus - Dev tools - Maven Artifact API</name>
+
+    <dependencies>
+    </dependencies>
+</project>
diff --git a/independent-projects/tools/artifact-api/src/main/java/io/quarkus/maven/ArtifactCoords.java b/independent-projects/tools/artifact-api/src/main/java/io/quarkus/maven/ArtifactCoords.java
new file mode 100644
index 00000000000000..529f8a1d256749
--- /dev/null
+++ b/independent-projects/tools/artifact-api/src/main/java/io/quarkus/maven/ArtifactCoords.java
@@ -0,0 +1,129 @@
+package io.quarkus.maven;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+public class ArtifactCoords implements Serializable {
+
+    public static final String TYPE_JAR = "jar";
+    public static final String TYPE_POM = "pom";
+
+    public static ArtifactCoords fromString(String str) {
+        return new ArtifactCoords(split(str, new String[5]));
+    }
+
+    public static ArtifactCoords pom(String groupId, String artifactId, String version) {
+        return new ArtifactCoords(groupId, artifactId, null, TYPE_POM, version);
+    }
+
+    protected static String[] split(String str, String[] parts) {
+        final int versionSep = str.lastIndexOf(':');
+        if (versionSep <= 0 || versionSep == str.length() - 1) {
+            throw new IllegalArgumentException("One of type, version or separating them ':' is missing from '" + str + "'");
+        }
+        parts[4] = str.substring(versionSep + 1);
+        ArtifactKey.split(str, parts, versionSep);
+        return parts;
+    }
+
+    protected final String groupId;
+    protected final String artifactId;
+    protected final String classifier;
+    protected final String type;
+    protected final String version;
+
+    protected transient ArtifactKey key;
+
+    protected ArtifactCoords(String[] parts) {
+        groupId = parts[0];
+        artifactId = parts[1];
+        classifier = parts[2];
+        type = parts[3] == null ? TYPE_JAR : parts[3];
+        version = parts[4];
+    }
+
+    public ArtifactCoords(ArtifactKey key, String version) {
+        this.key = key;
+        this.groupId = key.getGroupId();
+        this.artifactId = key.getArtifactId();
+        this.classifier = key.getClassifier();
+        this.type = key.getType();
+        this.version = version;
+    }
+
+    public ArtifactCoords(String groupId, String artifactId, String version) {
+        this(groupId, artifactId, "", TYPE_JAR, version);
+    }
+
+    public ArtifactCoords(String groupId, String artifactId, String type, String version) {
+        this(groupId, artifactId, "", type, version);
+    }
+
+    public ArtifactCoords(String groupId, String artifactId, String classifier, String type, String version) {
+        this.groupId = groupId;
+        this.artifactId = artifactId;
+        this.classifier = classifier == null ? "" : classifier;
+        this.type = type == null ? TYPE_JAR : type;
+        this.version = version;
+    }
+
+    public String getGroupId() {
+        return groupId;
+    }
+
+    public String getArtifactId() {
+        return artifactId;
+    }
+
+    public String getClassifier() {
+        return classifier;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    public ArtifactKey getKey() {
+        return key == null ? key = new ArtifactKey(groupId, artifactId, classifier, type) : key;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        ArtifactCoords that = (ArtifactCoords) o;
+        return Objects.equals(groupId, that.groupId) &&
+                Objects.equals(artifactId, that.artifactId) &&
+                Objects.equals(classifier, that.classifier) &&
+                Objects.equals(type, that.type) &&
+                Objects.equals(version, that.version);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(groupId, artifactId, classifier, type, version);
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder buf = new StringBuilder();
+        append(buf);
+        return buf.toString();
+    }
+
+    protected StringBuilder append(final StringBuilder buf) {
+        buf.append(groupId).append(':').append(artifactId).append(':');
+        if (classifier != null && !classifier.isEmpty()) {
+            buf.append(classifier);
+        }
+        return buf.append(':').append(type).append(':').append(version);
+    }
+}
diff --git a/independent-projects/tools/artifact-api/src/main/java/io/quarkus/maven/ArtifactKey.java b/independent-projects/tools/artifact-api/src/main/java/io/quarkus/maven/ArtifactKey.java
new file mode 100644
index 00000000000000..7283a1e9388ba1
--- /dev/null
+++ b/independent-projects/tools/artifact-api/src/main/java/io/quarkus/maven/ArtifactKey.java
@@ -0,0 +1,171 @@
+package io.quarkus.maven;
+
+import java.io.Serializable;
+
+public class ArtifactKey implements Serializable {
+
+    public static ArtifactKey fromString(String str) {
+        return new ArtifactKey(split(str, new String[4], str.length()));
+    }
+
+    protected static String[] split(String str, String[] parts, int fromIndex) {
+        int i = str.lastIndexOf(':', fromIndex - 1);
+        if (i <= 0) {
+            throw new IllegalArgumentException("GroupId and artifactId separating ':' is absent or not in the right place in '"
+                    + str.substring(0, fromIndex) + "'");
+        }
+        parts[3] = str.substring(i + 1, fromIndex);
+        fromIndex = i;
+        i = str.lastIndexOf(':', fromIndex - 1);
+        if (i < 0) {
+            parts[0] = str.substring(0, fromIndex);
+            if ((parts[1] = parts[3]).isEmpty()) {
+                throw new IllegalArgumentException("ArtifactId is empty in `" + str + "`");
+            }
+            parts[2] = "";
+            parts[3] = null;
+            return parts;
+        }
+        if (i == 0) {
+            throw new IllegalArgumentException(
+                    "One of groupId or artifactId is missing from '" + str.substring(0, fromIndex) + "'");
+        }
+        if (i == fromIndex - 1) {
+            parts[2] = "";
+        } else {
+            parts[2] = str.substring(i + 1, fromIndex);
+        }
+
+        fromIndex = i;
+        i = str.lastIndexOf(':', fromIndex - 1);
+        if (i < 0) {
+            parts[0] = str.substring(0, fromIndex);
+            if ((parts[1] = parts[2]).isEmpty()) {
+                throw new IllegalArgumentException("ArtifactId is empty in `" + str + "`");
+            }
+            parts[2] = parts[3];
+            parts[3] = null;
+            return parts;
+        }
+        if (i == 0 || i == fromIndex - 1) {
+            throw new IllegalArgumentException(
+                    "One of groupId or artifactId is missing from '" + str.substring(0, fromIndex) + "'");
+        }
+
+        parts[0] = str.substring(0, i);
+        parts[1] = str.substring(i + 1, fromIndex);
+        if (parts[3].isEmpty()) {
+            parts[3] = null;
+        }
+        return parts;
+    }
+
+    protected final String groupId;
+    protected final String artifactId;
+    protected final String classifier;
+    protected final String type;
+
+    public ArtifactKey(String[] parts) {
+        this.groupId = parts[0];
+        this.artifactId = parts[1];
+        if (parts.length == 2 || parts[2] == null) {
+            this.classifier = "";
+        } else {
+            this.classifier = parts[2];
+        }
+        if (parts.length <= 3 || parts[3] == null) {
+            this.type = "jar";
+        } else {
+            this.type = parts[3];
+        }
+    }
+
+    public ArtifactKey(String groupId, String artifactId) {
+        this(groupId, artifactId, null);
+    }
+
+    public ArtifactKey(String groupId, String artifactId, String classifier) {
+        this(groupId, artifactId, classifier, null);
+    }
+
+    public ArtifactKey(String groupId, String artifactId, String classifier, String type) {
+        this.groupId = groupId;
+        this.artifactId = artifactId;
+        this.classifier = classifier == null ? "" : classifier;
+        this.type = type;
+    }
+
+    public String getGroupId() {
+        return groupId;
+    }
+
+    public String getArtifactId() {
+        return artifactId;
+    }
+
+    public String getClassifier() {
+        return classifier;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((artifactId == null) ? 0 : artifactId.hashCode());
+        result = prime * result + ((classifier == null) ? 0 : classifier.hashCode());
+        result = prime * result + ((groupId == null) ? 0 : groupId.hashCode());
+        result = prime * result + ((type == null) ? 0 : type.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        ArtifactKey other = (ArtifactKey) obj;
+        if (artifactId == null) {
+            if (other.artifactId != null)
+                return false;
+        } else if (!artifactId.equals(other.artifactId))
+            return false;
+        if (classifier == null) {
+            if (other.classifier != null)
+                return false;
+        } else if (!classifier.equals(other.classifier))
+            return false;
+        if (groupId == null) {
+            if (other.groupId != null)
+                return false;
+        } else if (!groupId.equals(other.groupId))
+            return false;
+        if (type == null) {
+            if (other.type != null)
+                return false;
+        } else if (!type.equals(other.type))
+            return false;
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder buf = new StringBuilder();
+        buf.append(groupId).append(':').append(artifactId);
+        if (!classifier.isEmpty()) {
+            buf.append(':').append(classifier);
+        } else if (type != null) {
+            buf.append(':');
+        }
+        if (type != null) {
+            buf.append(':').append(type);
+        }
+        return buf.toString();
+    }
+}
diff --git a/independent-projects/tools/devtools-common/pom.xml b/independent-projects/tools/devtools-common/pom.xml
index 400f63d0b7b355..330f979aaf37d7 100644
--- a/independent-projects/tools/devtools-common/pom.xml
+++ b/independent-projects/tools/devtools-common/pom.xml
@@ -28,7 +28,7 @@
     <dependencies>
         <dependency>
             <groupId>io.quarkus</groupId>
-            <artifactId>quarkus-bootstrap-maven-resolver</artifactId>
+            <artifactId>quarkus-devtools-registry-client</artifactId>
         </dependency>
         <dependency>
             <groupId>io.quarkus</groupId>
@@ -42,10 +42,6 @@
             <groupId>io.quarkus.qute</groupId>
             <artifactId>qute-generator</artifactId>
         </dependency>
-        <dependency>
-            <groupId>io.quarkus</groupId>
-            <artifactId>quarkus-platform-descriptor-api</artifactId>
-        </dependency>
         <dependency>
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-compress</artifactId>
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/codestarts/QuarkusPlatformCodestartResourceLoader.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/codestarts/QuarkusPlatformCodestartResourceLoader.java
index 48a11a54038d4c..7753ff673ebd11 100644
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/codestarts/QuarkusPlatformCodestartResourceLoader.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/codestarts/QuarkusPlatformCodestartResourceLoader.java
@@ -1,21 +1,21 @@
 package io.quarkus.devtools.codestarts;
 
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
+import io.quarkus.platform.descriptor.loader.json.ResourceLoader;
 import java.io.IOException;
 
 public final class QuarkusPlatformCodestartResourceLoader implements CodestartPathLoader {
-    private QuarkusPlatformDescriptor platformDescr;
+    private ResourceLoader resourceLoader;
 
-    private QuarkusPlatformCodestartResourceLoader(QuarkusPlatformDescriptor platformDescr) {
-        this.platformDescr = platformDescr;
+    private QuarkusPlatformCodestartResourceLoader(ResourceLoader resourceLoader) {
+        this.resourceLoader = resourceLoader;
     }
 
-    public static CodestartPathLoader platformPathLoader(QuarkusPlatformDescriptor platformDescr) {
-        return new QuarkusPlatformCodestartResourceLoader(platformDescr);
+    public static CodestartPathLoader platformPathLoader(ResourceLoader resourceLoader) {
+        return new QuarkusPlatformCodestartResourceLoader(resourceLoader);
     }
 
     @Override
     public <T> T loadResourceAsPath(String name, PathConsumer<T> consumer) throws IOException {
-        return platformDescr.loadResourceAsPath(name, consumer::consume);
+        return resourceLoader.loadResourceAsPath(name, consumer::consume);
     }
 }
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/codestarts/jbang/QuarkusJBangCodestartCatalog.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/codestarts/jbang/QuarkusJBangCodestartCatalog.java
index 9739d1e5438751..29e91350b5a50c 100644
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/codestarts/jbang/QuarkusJBangCodestartCatalog.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/codestarts/jbang/QuarkusJBangCodestartCatalog.java
@@ -7,7 +7,7 @@
 import io.quarkus.devtools.codestarts.CodestartPathLoader;
 import io.quarkus.devtools.codestarts.DataKey;
 import io.quarkus.devtools.codestarts.core.GenericCodestartCatalog;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
+import io.quarkus.platform.descriptor.loader.json.ResourceLoader;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -47,9 +47,9 @@ private QuarkusJBangCodestartCatalog(Collection<Codestart> codestarts) {
         super(codestarts);
     }
 
-    public static QuarkusJBangCodestartCatalog fromQuarkusPlatformDescriptor(QuarkusPlatformDescriptor platformDescriptor)
+    public static QuarkusJBangCodestartCatalog fromResourceLoader(ResourceLoader resourceLoader)
             throws IOException {
-        final CodestartPathLoader pathLoader = platformPathLoader(platformDescriptor);
+        final CodestartPathLoader pathLoader = platformPathLoader(resourceLoader);
         final Collection<Codestart> codestarts = CodestartCatalogLoader.loadCodestarts(pathLoader,
                 QUARKUS_JBANG_CODESTARTS_DIR);
         return new QuarkusJBangCodestartCatalog(codestarts);
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/codestarts/quarkus/QuarkusCodestartCatalog.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/codestarts/quarkus/QuarkusCodestartCatalog.java
index f3a42620ab91ae..23987c70fc7b4b 100644
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/codestarts/quarkus/QuarkusCodestartCatalog.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/codestarts/quarkus/QuarkusCodestartCatalog.java
@@ -3,7 +3,6 @@
 import static io.quarkus.devtools.codestarts.QuarkusPlatformCodestartResourceLoader.platformPathLoader;
 import static io.quarkus.devtools.codestarts.core.CodestartCatalogs.findLanguageName;
 
-import io.quarkus.dependencies.Extension;
 import io.quarkus.devtools.codestarts.Codestart;
 import io.quarkus.devtools.codestarts.CodestartCatalogLoader;
 import io.quarkus.devtools.codestarts.CodestartException;
@@ -11,9 +10,13 @@
 import io.quarkus.devtools.codestarts.CodestartStructureException;
 import io.quarkus.devtools.codestarts.CodestartType;
 import io.quarkus.devtools.codestarts.DataKey;
+import io.quarkus.devtools.codestarts.QuarkusPlatformCodestartResourceLoader;
 import io.quarkus.devtools.codestarts.core.GenericCodestartCatalog;
+import io.quarkus.devtools.project.QuarkusProjectHelper;
 import io.quarkus.devtools.project.extensions.Extensions;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
+import io.quarkus.platform.descriptor.loader.json.ResourceLoader;
+import io.quarkus.registry.catalog.Extension;
+import io.quarkus.registry.catalog.ExtensionCatalog;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.ArrayList;
@@ -62,18 +65,11 @@ private QuarkusCodestartCatalog(Collection<Codestart> codestarts,
         this.extensionsMapping = extensionsMapping;
     }
 
-    public static QuarkusCodestartCatalog fromQuarkusPlatformDescriptor(QuarkusPlatformDescriptor platformDescriptor)
-            throws IOException {
-        final CodestartPathLoader pathLoader = platformPathLoader(platformDescriptor);
-        final Collection<Codestart> codestarts = CodestartCatalogLoader.loadCodestarts(pathLoader, QUARKUS_CODESTARTS_DIR);
-        final Map<String, Extension> extensionsMapping = buildExtensionsMapping(platformDescriptor.getExtensions());
-        return new QuarkusCodestartCatalog(codestarts, extensionsMapping);
-    }
-
     public static QuarkusCodestartCatalog fromQuarkusPlatformDescriptorAndDirectories(
-            QuarkusPlatformDescriptor platformDescriptor, Collection<Path> directories)
+            ExtensionCatalog catalog, Collection<Path> directories)
             throws IOException {
-        final CodestartPathLoader pathLoader = platformPathLoader(platformDescriptor);
+        final CodestartPathLoader pathLoader = QuarkusPlatformCodestartResourceLoader
+                .platformPathLoader(QuarkusProjectHelper.getResourceLoader(catalog));
         final Map<String, Codestart> codestarts = CodestartCatalogLoader.loadCodestarts(pathLoader, QUARKUS_CODESTARTS_DIR)
                 .stream()
                 .collect(Collectors.toMap(Codestart::getName, Function.identity()));
@@ -83,10 +79,18 @@ public static QuarkusCodestartCatalog fromQuarkusPlatformDescriptorAndDirectorie
             // On duplicates, directories override platform codestarts
             codestarts.putAll(dirCodestarts);
         }
-        final Map<String, Extension> extensionsMapping = buildExtensionsMapping(platformDescriptor.getExtensions());
+        final Map<String, Extension> extensionsMapping = buildExtensionsMapping(catalog.getExtensions());
         return new QuarkusCodestartCatalog(codestarts.values(), extensionsMapping);
     }
 
+    public static QuarkusCodestartCatalog fromExtensionsCatalog(ExtensionCatalog catalog, ResourceLoader resourceLoader)
+            throws IOException {
+        final CodestartPathLoader pathLoader = platformPathLoader(resourceLoader);
+        final Collection<Codestart> codestarts = CodestartCatalogLoader.loadCodestarts(pathLoader, QUARKUS_CODESTARTS_DIR);
+        final Map<String, Extension> extensionCodestartMapping = buildExtensionsMapping(catalog.getExtensions());
+        return new QuarkusCodestartCatalog(codestarts, extensionCodestartMapping);
+    }
+
     @Override
     protected Collection<Codestart> select(QuarkusCodestartProjectInput projectInput) {
         // Add codestarts from extension and for tooling
@@ -207,8 +211,15 @@ public static boolean isExample(Codestart codestart) {
         return codestart.getType() == CodestartType.CODE && codestart.getSpec().getTags().contains(Tag.EXAMPLE.key());
     };
 
-    private static Map<String, Extension> buildExtensionsMapping(Collection<Extension> extensions) {
-        return extensions.stream()
-                .collect(Collectors.toMap(Extensions::toGA, Function.identity()));
+    private static Map<String, Extension> buildExtensionsMapping(
+            Collection<Extension> extensions) {
+        final Map<String, Extension> map = new HashMap<>(extensions.size());
+        extensions.forEach(e -> {
+            if (e.getCodestart() != null) {
+                map.put(e.getArtifact().getGroupId() + ":" + e.getArtifact().getArtifactId(), e);
+            }
+        });
+        return map;
     }
+
 }
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/AddExtensions.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/AddExtensions.java
index bef30a8ae8f652..c157105dc0c6b3 100644
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/AddExtensions.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/AddExtensions.java
@@ -11,7 +11,6 @@
 import io.quarkus.devtools.project.extensions.ExtensionManager;
 import io.quarkus.platform.tools.ToolsConstants;
 import io.quarkus.platform.tools.ToolsUtils;
-import io.quarkus.registry.ExtensionRegistry;
 import java.util.HashMap;
 import java.util.Set;
 
@@ -24,7 +23,6 @@ public class AddExtensions {
     public static final String EXTENSIONS = ToolsUtils.dotJoin(ToolsConstants.QUARKUS, NAME, "extensions");
     public static final String OUTCOME_UPDATED = ToolsUtils.dotJoin(ToolsConstants.QUARKUS, NAME, "outcome", "updated");
     public static final String EXTENSION_MANAGER = ToolsUtils.dotJoin(ToolsConstants.QUARKUS, NAME, "extension-manager");
-    public static final String EXTENSION_REGISTRY = ToolsUtils.dotJoin(ToolsConstants.QUARKUS, NAME, "extension-registry");
 
     private final QuarkusCommandInvocation invocation;
     private final AddExtensionsCommandHandler handler = new AddExtensionsCommandHandler();
@@ -42,11 +40,6 @@ public AddExtensions extensionManager(ExtensionManager extensionManager) {
         return this;
     }
 
-    public AddExtensions extensionRegistry(ExtensionRegistry extensionRegistry) {
-        invocation.setValue(EXTENSION_REGISTRY, requireNonNull(extensionRegistry, "extensionRegistry is required"));
-        return this;
-    }
-
     public AddExtensions extensions(Set<String> extensions) {
         invocation.setValue(EXTENSIONS, extensions);
         return this;
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateJBangProject.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateJBangProject.java
index 8843f4f55cece2..c40162c5157274 100644
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateJBangProject.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateJBangProject.java
@@ -7,10 +7,7 @@
 import io.quarkus.devtools.commands.data.QuarkusCommandInvocation;
 import io.quarkus.devtools.commands.data.QuarkusCommandOutcome;
 import io.quarkus.devtools.commands.handlers.CreateJBangProjectCommandHandler;
-import io.quarkus.devtools.project.BuildTool;
 import io.quarkus.devtools.project.QuarkusProject;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
-import java.nio.file.Path;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
@@ -19,16 +16,13 @@
 public class CreateJBangProject {
     public static final String NAME = "create-jbang";
 
-    private final Path projectDirPath;
-    private final QuarkusPlatformDescriptor platformDescr;
-    private BuildTool buildTool = BuildTool.MAVEN;
+    private final QuarkusProject quarkusProject;
 
     private Set<String> extensions = new HashSet<>();
     private Map<String, Object> values = new HashMap<>();
 
-    public CreateJBangProject(Path projectDirPath, QuarkusPlatformDescriptor platformDescr) {
-        this.projectDirPath = requireNonNull(projectDirPath, "projectDirPath is required");
-        this.platformDescr = requireNonNull(platformDescr, "platformDescr is required");
+    public CreateJBangProject(QuarkusProject quarkusProject) {
+        this.quarkusProject = requireNonNull(quarkusProject, "quarkusProject is required");
     }
 
     public CreateJBangProject extensions(Set<String> extensions) {
@@ -48,7 +42,6 @@ public CreateJBangProject setValue(String name, Object value) {
 
     public QuarkusCommandOutcome execute() throws QuarkusCommandException {
         setValue(EXTENSIONS, extensions);
-        final QuarkusProject quarkusProject = QuarkusProject.of(projectDirPath, platformDescr, buildTool);
         final QuarkusCommandInvocation invocation = new QuarkusCommandInvocation(quarkusProject, values);
         return new CreateJBangProjectCommandHandler().execute(invocation);
     }
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateProject.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateProject.java
index 51e83ea8095ff7..0c78f241e84e5c 100644
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateProject.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateProject.java
@@ -7,14 +7,11 @@
 import io.quarkus.devtools.commands.data.QuarkusCommandInvocation;
 import io.quarkus.devtools.commands.data.QuarkusCommandOutcome;
 import io.quarkus.devtools.commands.handlers.CreateProjectCommandHandler;
-import io.quarkus.devtools.commands.handlers.LegacyCreateProjectCommandHandler;
 import io.quarkus.devtools.project.BuildTool;
 import io.quarkus.devtools.project.QuarkusProject;
 import io.quarkus.devtools.project.codegen.SourceType;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
 import io.quarkus.platform.tools.ToolsConstants;
 import io.quarkus.platform.tools.ToolsUtils;
-import java.nio.file.Path;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -42,18 +39,14 @@ public class CreateProject {
 
     private static final Pattern JAVA_VERSION_PATTERN = Pattern.compile("(?:1\\.)?(\\d+)(?:\\..*)?");
 
-    private boolean legacyCodegen = false;
-    private final Path projectDirPath;
-    private final QuarkusPlatformDescriptor platformDescr;
+    private QuarkusProject quarkusProject;
     private String javaTarget;
     private Set<String> extensions = new HashSet<>();
-    private BuildTool buildTool = BuildTool.MAVEN;
 
     private Map<String, Object> values = new HashMap<>();
 
-    public CreateProject(final Path projectDirPath, QuarkusPlatformDescriptor platformDescr) {
-        this.projectDirPath = requireNonNull(projectDirPath, "projectDirPath is required");
-        this.platformDescr = requireNonNull(platformDescr, "platformDescr is required");
+    public CreateProject(QuarkusProject project) {
+        this.quarkusProject = requireNonNull(project, "project is required");
     }
 
     public CreateProject groupId(String groupId) {
@@ -71,16 +64,27 @@ public CreateProject version(String version) {
         return this;
     }
 
+    @Deprecated
     public CreateProject quarkusMavenPluginVersion(String version) {
         setValue(QUARKUS_MAVEN_PLUGIN_VERSION, version);
         return this;
     }
 
+    @Deprecated
     public CreateProject quarkusGradlePluginVersion(String version) {
         setValue(QUARKUS_GRADLE_PLUGIN_VERSION, version);
         return this;
     }
 
+    public CreateProject quarkusPluginVersion(String version) {
+        if (quarkusProject.getBuildTool().equals(BuildTool.MAVEN)) {
+            setValue(QUARKUS_MAVEN_PLUGIN_VERSION, version);
+        } else {
+            setValue(QUARKUS_GRADLE_PLUGIN_VERSION, version);
+        }
+        return this;
+    }
+
     public CreateProject sourceType(SourceType sourceType) {
         setValue(SOURCE_TYPE, sourceType);
         return this;
@@ -140,43 +144,6 @@ public CreateProject overrideExamples(Set<String> overrideExamples) {
         return this;
     }
 
-    /**
-     * @deprecated As of release 1.10, codestarts are default. Legacy codegen is scheduled to be removed:
-     *             https://github.com/quarkusio/quarkus/issues/12897
-     */
-    @Deprecated
-    public CreateProject codestartsEnabled(boolean value) {
-        return this.legacyCodegen(!value);
-    }
-
-    /**
-     * @deprecated As of release 1.10, codestarts are default. Legacy codegen is scheduled to be removed:
-     *             https://github.com/quarkusio/quarkus/issues/12897
-     */
-    @Deprecated
-    public CreateProject codestartsEnabled() {
-        return this.legacyCodegen(false);
-    }
-
-    /**
-     * @deprecated As of release 1.10, codestarts are default. Legacy codegen is scheduled to be removed:
-     *             https://github.com/quarkusio/quarkus/issues/12897
-     */
-    @Deprecated
-    public CreateProject legacyCodegen() {
-        return this.legacyCodegen(true);
-    }
-
-    /**
-     * @deprecated As of release 1.10, codestarts are default. Legacy codegen is scheduled to be removed:
-     *             https://github.com/quarkusio/quarkus/issues/12897
-     */
-    @Deprecated
-    public CreateProject legacyCodegen(boolean value) {
-        this.legacyCodegen = value;
-        return this;
-    }
-
     public CreateProject noExamples(boolean value) {
         setValue(NO_EXAMPLES, value);
         return this;
@@ -211,11 +178,6 @@ public CreateProject setValue(String name, Object value) {
         return this;
     }
 
-    public CreateProject buildTool(BuildTool buildTool) {
-        this.buildTool = requireNonNull(buildTool, "buildTool is required");
-        return this;
-    }
-
     public boolean doCreateProject(final Map<String, Object> context) throws QuarkusCommandException {
         if (context != null && !context.isEmpty()) {
             for (Map.Entry<String, Object> entry : context.entrySet()) {
@@ -244,11 +206,7 @@ public QuarkusCommandOutcome execute() throws QuarkusCommandException {
             }
         }
         setValue(EXTENSIONS, extensions);
-        final QuarkusProject quarkusProject = QuarkusProject.of(projectDirPath, platformDescr, buildTool);
         final QuarkusCommandInvocation invocation = new QuarkusCommandInvocation(quarkusProject, values);
-        if (legacyCodegen) {
-            return new LegacyCreateProjectCommandHandler().execute(invocation);
-        }
         return new CreateProjectCommandHandler().execute(invocation);
     }
 
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/ListExtensions.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/ListExtensions.java
index 8e777a62165f81..f19a132d98925c 100644
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/ListExtensions.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/ListExtensions.java
@@ -11,7 +11,6 @@
 import io.quarkus.devtools.project.extensions.ExtensionManager;
 import io.quarkus.platform.tools.ToolsConstants;
 import io.quarkus.platform.tools.ToolsUtils;
-import io.quarkus.registry.ExtensionRegistry;
 import java.util.HashMap;
 
 /**
@@ -26,7 +25,6 @@ public class ListExtensions {
     public static final String FORMAT = ToolsUtils.dotJoin(PARAM_PREFIX, "format");
     public static final String SEARCH = ToolsUtils.dotJoin(PARAM_PREFIX, "search");
     public static final String EXTENSION_MANAGER = ToolsUtils.dotJoin(PARAM_PREFIX, "extension-manager");
-    public static final String EXTENSION_REGISTRY = ToolsUtils.dotJoin(PARAM_PREFIX, "extension-registry");
 
     private final QuarkusCommandInvocation invocation;
     private final ListExtensionsCommandHandler handler = new ListExtensionsCommandHandler();
@@ -64,11 +62,6 @@ public ListExtensions extensionManager(ExtensionManager extensionManager) {
         return this;
     }
 
-    public ListExtensions extensionRegistry(ExtensionRegistry extensionRegistry) {
-        invocation.setValue(EXTENSION_REGISTRY, requireNonNull(extensionRegistry, "extensionRegistry is required"));
-        return this;
-    }
-
     public ListExtensions search(String search) {
         invocation.setValue(SEARCH, search);
         return this;
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/ListPlatforms.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/ListPlatforms.java
new file mode 100644
index 00000000000000..191c93b6a1e23b
--- /dev/null
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/ListPlatforms.java
@@ -0,0 +1,27 @@
+package io.quarkus.devtools.commands;
+
+import io.quarkus.devtools.commands.data.QuarkusCommandException;
+import io.quarkus.devtools.commands.data.QuarkusCommandInvocation;
+import io.quarkus.devtools.commands.data.QuarkusCommandOutcome;
+import io.quarkus.devtools.commands.handlers.ListPlatformsCommandHandler;
+import io.quarkus.devtools.messagewriter.MessageWriter;
+import io.quarkus.devtools.project.QuarkusProject;
+import java.util.HashMap;
+
+public class ListPlatforms {
+
+    private final QuarkusCommandInvocation invocation;
+    private final ListPlatformsCommandHandler handler = new ListPlatformsCommandHandler();
+
+    public ListPlatforms(QuarkusProject quarkusProject) {
+        this.invocation = new QuarkusCommandInvocation(quarkusProject);
+    }
+
+    public ListPlatforms(QuarkusProject quarkusProject, MessageWriter messageWriter) {
+        this.invocation = new QuarkusCommandInvocation(quarkusProject, new HashMap<>(), messageWriter);
+    }
+
+    public QuarkusCommandOutcome execute() throws QuarkusCommandException {
+        return handler.execute(invocation);
+    }
+}
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/data/QuarkusCommandInvocation.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/data/QuarkusCommandInvocation.java
index 4470fa21e42f05..a46feadfaf8a1d 100644
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/data/QuarkusCommandInvocation.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/data/QuarkusCommandInvocation.java
@@ -4,7 +4,7 @@
 
 import io.quarkus.devtools.messagewriter.MessageWriter;
 import io.quarkus.devtools.project.QuarkusProject;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
+import io.quarkus.registry.catalog.ExtensionCatalog;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -40,8 +40,7 @@ public MessageWriter log() {
         return log;
     }
 
-    public QuarkusPlatformDescriptor getPlatformDescriptor() {
-        return quarkusProject.getPlatformDescriptor();
+    public ExtensionCatalog getExtensionsCatalog() {
+        return quarkusProject.getExtensionsCatalog();
     }
-
 }
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/data/SelectionResult.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/data/SelectionResult.java
index c97d6afcc0a313..4ab793f86aa216 100644
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/data/SelectionResult.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/data/SelectionResult.java
@@ -1,21 +1,21 @@
 package io.quarkus.devtools.commands.data;
 
-import io.quarkus.dependencies.Extension;
+import io.quarkus.registry.catalog.Extension;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Iterator;
-import java.util.Set;
 
 public class SelectionResult implements Iterable<Extension> {
 
-    private final Set<Extension> extensions;
+    private final Collection<Extension> extensions;
     private final boolean matches;
 
-    public SelectionResult(Set<Extension> extensions, boolean matches) {
+    public SelectionResult(Collection<Extension> extensions, boolean matches) {
         this.extensions = extensions;
         this.matches = matches;
     }
 
-    public Set<Extension> getExtensions() {
+    public Collection<Extension> getExtensions() {
         return extensions;
     }
 
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/AddExtensionsCommandHandler.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/AddExtensionsCommandHandler.java
index cfd2475e67dba6..3f7a694471dde9 100644
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/AddExtensionsCommandHandler.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/AddExtensionsCommandHandler.java
@@ -3,6 +3,8 @@
 import static io.quarkus.devtools.commands.AddExtensions.EXTENSION_MANAGER;
 import static io.quarkus.devtools.messagewriter.MessageIcons.NOK_ICON;
 
+import io.quarkus.bootstrap.model.AppArtifactCoords;
+import io.quarkus.bootstrap.model.AppArtifactKey;
 import io.quarkus.devtools.commands.AddExtensions;
 import io.quarkus.devtools.commands.data.QuarkusCommandException;
 import io.quarkus.devtools.commands.data.QuarkusCommandInvocation;
@@ -11,12 +13,18 @@
 import io.quarkus.devtools.project.extensions.ExtensionInstallPlan;
 import io.quarkus.devtools.project.extensions.ExtensionManager;
 import io.quarkus.devtools.project.extensions.ExtensionManager.InstallResult;
-import io.quarkus.registry.DefaultExtensionRegistry;
-import io.quarkus.registry.ExtensionRegistry;
-import io.quarkus.registry.MultipleExtensionsFoundException;
+import io.quarkus.maven.ArtifactCoords;
+import io.quarkus.registry.catalog.Extension;
+import io.quarkus.registry.catalog.ExtensionCatalog;
+import io.quarkus.registry.catalog.ExtensionOrigin;
+import io.quarkus.registry.catalog.ExtensionPredicate;
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
+import java.util.List;
 import java.util.Set;
+import org.apache.commons.lang3.StringUtils;
 
 /**
  * This class is thread-safe. It extracts extensions to be added to the project from an instance of
@@ -31,16 +39,13 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws
             return QuarkusCommandOutcome.success().setValue(AddExtensions.OUTCOME_UPDATED, false);
         }
 
-        ExtensionRegistry extensionRegistry = invocation.getValue(AddExtensions.EXTENSION_REGISTRY);
-        if (extensionRegistry == null) {
-            extensionRegistry = DefaultExtensionRegistry.fromPlatform(invocation.getPlatformDescriptor());
-        }
-        String quarkusVersion = invocation.getPlatformDescriptor().getQuarkusVersion();
+        String quarkusVersion = invocation.getExtensionsCatalog().getQuarkusCoreVersion();
 
         final ExtensionManager extensionManager = invocation.getValue(EXTENSION_MANAGER,
                 invocation.getQuarkusProject().getExtensionManager());
         try {
-            ExtensionInstallPlan extensionInstallPlan = extensionRegistry.planInstallation(quarkusVersion, extensionsQuery);
+            ExtensionInstallPlan extensionInstallPlan = planInstallation(quarkusVersion, extensionsQuery,
+                    invocation.getExtensionsCatalog());
             if (extensionInstallPlan.isNotEmpty()) {
                 final InstallResult result = extensionManager.install(extensionInstallPlan);
                 result.getInstalled()
@@ -65,4 +70,83 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws
         return new QuarkusCommandOutcome(false).setValue(AddExtensions.OUTCOME_UPDATED, false);
     }
 
+    public ExtensionInstallPlan planInstallation(String quarkusCore, Collection<String> keywords,
+            ExtensionCatalog catalog) {
+        ExtensionInstallPlan.Builder builder = ExtensionInstallPlan.builder();
+        boolean multipleKeywords = keywords.size() > 1;
+        for (String keyword : keywords) {
+            int countColons = StringUtils.countMatches(keyword, ":");
+            // Check if it's just groupId:artifactId
+            if (countColons == 1) {
+                AppArtifactKey artifactKey = AppArtifactKey.fromString(keyword);
+                builder.addManagedExtension(new AppArtifactCoords(artifactKey, null));
+                continue;
+            } else if (countColons > 1) {
+                // it's a gav
+                builder.addIndependentExtension(AppArtifactCoords.fromString(keyword));
+                continue;
+            }
+            List<Extension> listed = listInternalExtensions(quarkusCore, keyword, catalog.getExtensions());
+            if (listed.size() != 1 && multipleKeywords) {
+                // No extension found for this keyword. Return empty immediately
+                return ExtensionInstallPlan.EMPTY;
+            }
+            // If it's a pattern allow multiple results
+            // See https://github.com/quarkusio/quarkus/issues/11086#issuecomment-666360783
+            else if (listed.size() > 1 && !ExtensionPredicate.isPattern(keyword)) {
+                throw new MultipleExtensionsFoundException(keyword, listed);
+            }
+            for (Extension e : listed) {
+                String groupId = e.getArtifact().getGroupId();
+                String artifactId = e.getArtifact().getArtifactId();
+                String version = e.getArtifact().getVersion();
+                AppArtifactCoords extensionCoords = new AppArtifactCoords(groupId, artifactId, version);
+
+                boolean managed = false;
+                // TODO this is not properly picking the platform BOMs
+                for (ExtensionOrigin origin : e.getOrigins()) {
+                    if (origin.isPlatform()) {
+                        builder.addManagedExtension(extensionCoords);
+                        final ArtifactCoords bomCoords = origin.getBom();
+                        builder.addPlatform(new AppArtifactCoords(bomCoords.getGroupId(), bomCoords.getArtifactId(),
+                                null, "pom", bomCoords.getVersion()));
+                        managed = true;
+                        break;
+                    }
+                }
+                if (!managed) {
+                    builder.addIndependentExtension(extensionCoords);
+                }
+            }
+            // TODO
+            //if (!listed.isEmpty()) {
+            //    builder.addPlatform(new AppArtifactCoords(catalog.getBomGroupId(), catalog.getBomArtifactId(), null, "pom",
+            //            catalog.getBomVersion()));
+            //}
+        }
+        return builder.build();
+    }
+
+    private List<Extension> listInternalExtensions(String quarkusCore, String keyword, Collection<Extension> extensions) {
+        List<Extension> result = new ArrayList<>();
+        ExtensionPredicate predicate = null;
+        if (keyword != null && !keyword.isEmpty()) {
+            predicate = new ExtensionPredicate(keyword);
+        }
+        for (Extension extension : extensions) {
+            // If no filter is defined, just return the tuple
+            if (predicate == null) {
+                result.add(extension);
+            } else {
+                // If there is an exact match, return only this
+                if (predicate.isExactMatch(extension)) {
+                    return Collections.singletonList(extension);
+                } else if (predicate.test(extension)) {
+                    result.add(extension);
+                }
+            }
+        }
+        return result;
+    }
+
 }
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/CreateJBangProjectCommandHandler.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/CreateJBangProjectCommandHandler.java
index 826cc668100cdb..daf07947d68eef 100644
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/CreateJBangProjectCommandHandler.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/CreateJBangProjectCommandHandler.java
@@ -1,19 +1,19 @@
 package io.quarkus.devtools.commands.handlers;
 
-import static io.quarkus.devtools.codestarts.jbang.QuarkusJBangCodestartCatalog.JBangDataKey.QUARKUS_BOM_ARTIFACT_ID;
-import static io.quarkus.devtools.codestarts.jbang.QuarkusJBangCodestartCatalog.JBangDataKey.QUARKUS_BOM_GROUP_ID;
-import static io.quarkus.devtools.codestarts.jbang.QuarkusJBangCodestartCatalog.JBangDataKey.QUARKUS_BOM_VERSION;
 import static io.quarkus.devtools.commands.handlers.QuarkusCommandHandlers.computeCoordsFromQuery;
 
 import io.quarkus.bootstrap.model.AppArtifactCoords;
 import io.quarkus.devtools.codestarts.jbang.QuarkusJBangCodestartCatalog;
 import io.quarkus.devtools.codestarts.jbang.QuarkusJBangCodestartProjectInput;
+import io.quarkus.devtools.codestarts.jbang.QuarkusJBangCodestartProjectInputBuilder;
 import io.quarkus.devtools.commands.data.QuarkusCommandException;
 import io.quarkus.devtools.commands.data.QuarkusCommandInvocation;
 import io.quarkus.devtools.commands.data.QuarkusCommandOutcome;
 import io.quarkus.devtools.messagewriter.MessageIcons;
+import io.quarkus.devtools.project.QuarkusProject;
 import io.quarkus.devtools.project.codegen.ProjectGenerator;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
+import io.quarkus.maven.ArtifactCoords;
+import io.quarkus.registry.catalog.ExtensionCatalog;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.Collections;
@@ -30,13 +30,24 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws
             throw new QuarkusCommandException("Failed to create project because of invalid extensions");
         }
 
-        final QuarkusJBangCodestartProjectInput input = QuarkusJBangCodestartProjectInput.builder()
+        final ExtensionCatalog catalog = invocation.getExtensionsCatalog();
+
+        final QuarkusJBangCodestartProjectInputBuilder builder = QuarkusJBangCodestartProjectInput.builder()
                 .addExtensions(extensionsToAdd)
                 .setNoJBangWrapper(invocation.getBooleanValue("noJBangWrapper"))
-                .putData(QUARKUS_BOM_GROUP_ID, invocation.getPlatformDescriptor().getBomGroupId())
-                .putData(QUARKUS_BOM_ARTIFACT_ID, invocation.getPlatformDescriptor().getBomArtifactId())
-                .putData(QUARKUS_BOM_VERSION, invocation.getPlatformDescriptor().getBomVersion())
-                .build();
+                .putData("quarkus.version", invocation.getExtensionsCatalog().getQuarkusCoreVersion());
+
+        if (catalog.getBom() != null) {
+            // TODO properly import the BOMs
+            final ArtifactCoords firstBom = catalog.getBom();
+            builder.putData(QuarkusJBangCodestartCatalog.JBangDataKey.QUARKUS_BOM_GROUP_ID.key(),
+                    firstBom.getGroupId())
+                    .putData(QuarkusJBangCodestartCatalog.JBangDataKey.QUARKUS_BOM_ARTIFACT_ID.key(),
+                            firstBom.getArtifactId())
+                    .putData(QuarkusJBangCodestartCatalog.JBangDataKey.QUARKUS_BOM_VERSION.key(),
+                            firstBom.getVersion());
+        }
+        final QuarkusJBangCodestartProjectInput input = builder.build();
 
         final Path projectDir = invocation.getQuarkusProject().getProjectDirPath();
         try {
@@ -46,7 +57,7 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws
                         + extensionsToAdd.stream().map(e -> "- " + e.getGroupId() + ":" + e.getArtifactId() + "\n")
                                 .collect(Collectors.joining()));
             }
-            getCatalog(invocation.getPlatformDescriptor()).createProject(input).generate(projectDir);
+            getCatalog(invocation.getQuarkusProject()).createProject(input).generate(projectDir);
             invocation.log()
                     .info("\n-----------\n" + MessageIcons.NOOP_ICON
                             + " jbang project has been successfully generated in:\n--> "
@@ -57,7 +68,7 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws
         return QuarkusCommandOutcome.success();
     }
 
-    private QuarkusJBangCodestartCatalog getCatalog(QuarkusPlatformDescriptor platformDescriptor) throws IOException {
-        return QuarkusJBangCodestartCatalog.fromQuarkusPlatformDescriptor(platformDescriptor);
+    private QuarkusJBangCodestartCatalog getCatalog(QuarkusProject project) throws IOException {
+        return QuarkusJBangCodestartCatalog.fromResourceLoader(project.getCodestartsResourceLoader());
     }
 }
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/CreateProjectCommandHandler.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/CreateProjectCommandHandler.java
index 8e65c76429181f..ae235cbbe29efa 100644
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/CreateProjectCommandHandler.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/CreateProjectCommandHandler.java
@@ -25,8 +25,9 @@
 import io.quarkus.devtools.commands.data.QuarkusCommandOutcome;
 import io.quarkus.devtools.messagewriter.MessageIcons;
 import io.quarkus.devtools.project.codegen.ProjectGenerator;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
+import io.quarkus.maven.ArtifactCoords;
 import io.quarkus.platform.tools.ToolsUtils;
+import io.quarkus.registry.catalog.ExtensionCatalog;
 import java.io.IOException;
 import java.util.Collections;
 import java.util.HashMap;
@@ -45,11 +46,15 @@ public class CreateProjectCommandHandler implements QuarkusCommandHandler {
 
     @Override
     public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws QuarkusCommandException {
-        final QuarkusPlatformDescriptor platformDescr = invocation.getPlatformDescriptor();
-        invocation.setValue(BOM_GROUP_ID, platformDescr.getBomGroupId());
-        invocation.setValue(BOM_ARTIFACT_ID, platformDescr.getBomArtifactId());
-        invocation.setValue(QUARKUS_VERSION, platformDescr.getQuarkusVersion());
-        invocation.setValue(BOM_VERSION, platformDescr.getBomVersion());
+        final ExtensionCatalog platformDescr = invocation.getExtensionsCatalog();
+        final ArtifactCoords bom = platformDescr.getBom();
+        if (bom == null) {
+            throw new QuarkusCommandException("The platform BOM is missing");
+        }
+        invocation.setValue(BOM_GROUP_ID, bom.getGroupId());
+        invocation.setValue(BOM_ARTIFACT_ID, bom.getArtifactId());
+        invocation.setValue(BOM_VERSION, bom.getVersion());
+        invocation.setValue(QUARKUS_VERSION, platformDescr.getQuarkusCoreVersion());
         final Set<String> extensionsQuery = invocation.getValue(ProjectGenerator.EXTENSIONS, Collections.emptySet());
 
         final Properties quarkusProps = ToolsUtils.readQuarkusProperties(platformDescr);
@@ -106,7 +111,8 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws
             }
 
             final QuarkusCodestartCatalog catalog = QuarkusCodestartCatalog
-                    .fromQuarkusPlatformDescriptor(invocation.getPlatformDescriptor());
+                    .fromExtensionsCatalog(invocation.getQuarkusProject().getExtensionsCatalog(),
+                            invocation.getQuarkusProject().getCodestartsResourceLoader());
             final CodestartProjectDefinition projectDefinition = catalog.createProject(input);
             projectDefinition.generate(invocation.getQuarkusProject().getProjectDirPath());
             invocation.log()
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/LegacyCreateProjectCommandHandler.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/LegacyCreateProjectCommandHandler.java
deleted file mode 100644
index e4fd4e1e8389e8..00000000000000
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/LegacyCreateProjectCommandHandler.java
+++ /dev/null
@@ -1,203 +0,0 @@
-package io.quarkus.devtools.commands.handlers;
-
-import static io.quarkus.devtools.commands.handlers.QuarkusCommandHandlers.computeCoordsFromQuery;
-import static io.quarkus.devtools.project.codegen.ProjectGenerator.*;
-
-import io.quarkus.bootstrap.model.AppArtifactCoords;
-import io.quarkus.devtools.codestarts.utils.NestedMaps;
-import io.quarkus.devtools.commands.data.QuarkusCommandException;
-import io.quarkus.devtools.commands.data.QuarkusCommandInvocation;
-import io.quarkus.devtools.commands.data.QuarkusCommandOutcome;
-import io.quarkus.devtools.messagewriter.MessageIcons;
-import io.quarkus.devtools.project.BuildTool;
-import io.quarkus.devtools.project.buildfile.GroovyGradleBuildFilesCreator;
-import io.quarkus.devtools.project.buildfile.KotlinGradleBuildFilesCreator;
-import io.quarkus.devtools.project.codegen.ProjectGenerator;
-import io.quarkus.devtools.project.codegen.ProjectGeneratorRegistry;
-import io.quarkus.devtools.project.codegen.SourceType;
-import io.quarkus.devtools.project.codegen.rest.BasicRestProjectGenerator;
-import io.quarkus.devtools.project.extensions.ExtensionManager;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
-import io.quarkus.platform.tools.ToolsUtils;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Properties;
-import java.util.Set;
-
-/**
- * Instances of this class are thread-safe. They create a new project extracting all the necessary properties from an instance
- * of {@link QuarkusCommandInvocation}.
- */
-public class LegacyCreateProjectCommandHandler implements QuarkusCommandHandler {
-
-    @Override
-    public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws QuarkusCommandException {
-        final QuarkusPlatformDescriptor platformDescr = invocation.getPlatformDescriptor();
-        invocation.setValue(BOM_GROUP_ID, platformDescr.getBomGroupId());
-        invocation.setValue(BOM_ARTIFACT_ID, platformDescr.getBomArtifactId());
-        invocation.setValue(QUARKUS_VERSION, platformDescr.getQuarkusVersion());
-        invocation.setValue(BOM_VERSION, platformDescr.getBomVersion());
-        final Set<String> extensionsQuery = invocation.getValue(ProjectGenerator.EXTENSIONS, Collections.emptySet());
-
-        final Properties quarkusProps = ToolsUtils.readQuarkusProperties(platformDescr);
-        quarkusProps.forEach((k, v) -> {
-            String name = k.toString().replace("-", "_");
-            if (!invocation.hasValue(name)) {
-                invocation.setValue(k.toString().replace("-", "_"), v.toString());
-            }
-        });
-
-        addPlatformDataToLegacyCodegen(invocation);
-
-        try {
-            String className = invocation.getStringValue(CLASS_NAME);
-            if (className != null) {
-                className = invocation.getValue(SOURCE_TYPE, SourceType.JAVA).stripExtensionFrom(className);
-                int idx = className.lastIndexOf('.');
-                if (idx >= 0) {
-                    String pkgName = invocation.getStringValue(PACKAGE_NAME);
-                    if (pkgName == null) {
-                        invocation.setValue(PACKAGE_NAME, className.substring(0, idx));
-                    }
-                    className = className.substring(idx + 1);
-                }
-                invocation.setValue(CLASS_NAME, className);
-            }
-
-            // Default to cleaned groupId if packageName not set
-            final String pkgName = invocation.getStringValue(PACKAGE_NAME);
-            final String groupId = invocation.getStringValue(PROJECT_GROUP_ID);
-            if (pkgName == null && groupId != null) {
-                invocation.setValue(PACKAGE_NAME, groupId.replace("-", ".").replace("_", "."));
-            }
-
-            final List<AppArtifactCoords> extensionsToAdd = computeCoordsFromQuery(invocation, extensionsQuery);
-
-            // extensionsToAdd is null when an error occurred while matching extensions
-            if (extensionsToAdd != null) {
-                ProjectGeneratorRegistry.get(BasicRestProjectGenerator.NAME).generate(invocation);
-
-                //TODO ia3andy extensions should be added directly during the project generation
-                if (invocation.getQuarkusProject().getBuildTool().equals(BuildTool.GRADLE)) {
-                    final GroovyGradleBuildFilesCreator generator = new GroovyGradleBuildFilesCreator(
-                            invocation.getQuarkusProject());
-                    generator.create(
-                            invocation.getStringValue(PROJECT_GROUP_ID),
-                            invocation.getStringValue(PROJECT_ARTIFACT_ID),
-                            invocation.getStringValue(PROJECT_VERSION),
-                            quarkusProps,
-                            extensionsToAdd);
-                } else if (invocation.getQuarkusProject().getBuildTool().equals(BuildTool.GRADLE_KOTLIN_DSL)) {
-                    final KotlinGradleBuildFilesCreator generator = new KotlinGradleBuildFilesCreator(
-                            invocation.getQuarkusProject());
-                    generator.create(
-                            invocation.getStringValue(PROJECT_GROUP_ID),
-                            invocation.getStringValue(PROJECT_ARTIFACT_ID),
-                            invocation.getStringValue(PROJECT_VERSION),
-                            quarkusProps,
-                            extensionsToAdd);
-                } else {
-                    final ExtensionManager.InstallResult result = invocation.getQuarkusProject().getExtensionManager()
-                            .install(extensionsToAdd);
-                    result.getInstalled()
-                            .forEach(a -> invocation.log()
-                                    .info(MessageIcons.OK_ICON + " Extension " + a.getGroupId() + ":" + a.getArtifactId()
-                                            + " has been installed"));
-                }
-            }
-        } catch (IOException e) {
-            throw new QuarkusCommandException("Failed to create project", e);
-        }
-        return QuarkusCommandOutcome.success();
-    }
-
-    // # CLOSE YOUR EYES PLEASE
-    static void addPlatformDataToLegacyCodegen(QuarkusCommandInvocation invocation) {
-        final BuildTool buildTool = invocation.getQuarkusProject().getBuildTool();
-        if (BuildTool.MAVEN == buildTool) {
-            final Optional<List> mavenRepositories = NestedMaps.getValue(invocation.getPlatformDescriptor().getMetadata(),
-                    "maven.repositories");
-            if (mavenRepositories.isPresent()
-                    && !mavenRepositories.get().isEmpty()) {
-                // We only take the first one here to make things simpler:
-                final Map<String, Object> repo = (Map<String, Object>) mavenRepositories.get().get(0);
-                if (repo != null && repo.get("id") instanceof String && repo.get("url") instanceof String) {
-                    final StringBuilder repositories = new StringBuilder()
-                            .append("\n")
-                            .append("    <repositories>\n")
-                            .append("        <repository>\n")
-                            .append("            <id>").append(repo.get("id")).append("</id>\n")
-                            .append("            <url>").append(repo.get("url")).append("</url>\n")
-                            .append("            <releases>\n")
-                            .append("                <enabled>").append(repo.getOrDefault("releases-enabled", true))
-                            .append("</enabled>\n")
-                            .append("            </releases>\n")
-                            .append("            <snapshots>\n")
-                            .append("                <enabled>").append(repo.getOrDefault("snapshots-enabled", true))
-                            .append("</enabled>\n")
-                            .append("            </snapshots>\n")
-                            .append("        </repository>\n")
-                            .append("    </repositories>\n");
-                    invocation.setValue(MAVEN_REPOSITORIES, repositories.toString());
-                }
-            }
-            final Optional<List> mavenPluginRepositories = NestedMaps
-                    .getValue(invocation.getPlatformDescriptor().getMetadata(), "maven.plugin-repositories");
-            if (mavenPluginRepositories.isPresent()
-                    && !mavenPluginRepositories.get().isEmpty()) {
-                // We only take the first one here to make things simpler:
-                final Map<String, Object> repo = (Map<String, Object>) mavenPluginRepositories.get().get(0);
-                if (repo != null && repo.get("id") instanceof String && repo.get("url") instanceof String) {
-                    final StringBuilder pluginRepositories = new StringBuilder()
-                            .append("\n")
-                            .append("    <pluginRepositories>\n")
-                            .append("        <pluginRepository>\n")
-                            .append("            <id>").append(repo.get("id")).append("</id>\n")
-                            .append("            <url>").append(repo.get("url")).append("</url>\n")
-                            .append("            <releases>\n")
-                            .append("                <enabled>").append(repo.getOrDefault("releases-enabled", true))
-                            .append("</enabled>\n")
-                            .append("            </releases>\n")
-                            .append("            <snapshots>\n")
-                            .append("                <enabled>").append(repo.getOrDefault("snapshots-enabled", true))
-                            .append("</enabled>\n")
-                            .append("            </snapshots>\n")
-                            .append("        </pluginRepository>\n")
-                            .append("    </pluginRepositories>\n");
-                    invocation.setValue(MAVEN_PLUGIN_REPOSITORIES, pluginRepositories.toString());
-                }
-            }
-        } else if (BuildTool.GRADLE == buildTool || BuildTool.GRADLE_KOTLIN_DSL == buildTool) {
-            final Optional<List> gradleRepositories = NestedMaps
-                    .getValue(invocation.getPlatformDescriptor().getMetadata(), "gradle.repositories");
-            if (gradleRepositories.isPresent()
-                    && !gradleRepositories.get().isEmpty()) {
-                // We only take the first one here to make things simpler:
-                final Map<String, Object> repo = (Map<String, Object>) gradleRepositories.get().get(0);
-                if (repo != null && repo.get("url") instanceof String) {
-                    final String repositories = buildTool == BuildTool.GRADLE
-                            ? "\n     maven { url \"" + repo.get("url") + "\" }"
-                            : "\n     maven { url = uri(\"" + repo.get("url") + "\") }";
-                    invocation.setValue(MAVEN_REPOSITORIES, repositories);
-                }
-            }
-            final Optional<List> gradlePluginRepositories = NestedMaps
-                    .getValue(invocation.getPlatformDescriptor().getMetadata(), "gradle.plugin-repositories");
-            if (gradlePluginRepositories.isPresent()
-                    && !gradlePluginRepositories.get().isEmpty()) {
-                // We only take the first one here to make things simpler:
-                final Map<String, Object> repo = (Map<String, Object>) gradlePluginRepositories.get().get(0);
-                if (repo != null && repo.get("url") instanceof String) {
-                    final String pluginRepositories = buildTool == BuildTool.GRADLE
-                            ? "\n     maven { url \"" + repo.get("url") + "\" }"
-                            : "\n     maven { url = uri(\"" + repo.get("url") + "\") }";
-                    invocation.setValue(MAVEN_PLUGIN_REPOSITORIES, pluginRepositories);
-                }
-            }
-        }
-    }
-    // # YOU CAN OPEN NOW :)
-}
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/ListExtensionsCommandHandler.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/ListExtensionsCommandHandler.java
index 9544073e89b166..b8cc1238c903bb 100644
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/ListExtensionsCommandHandler.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/ListExtensionsCommandHandler.java
@@ -5,7 +5,6 @@
 
 import io.quarkus.bootstrap.model.AppArtifactCoords;
 import io.quarkus.bootstrap.model.AppArtifactKey;
-import io.quarkus.dependencies.Extension;
 import io.quarkus.devtools.commands.ListExtensions;
 import io.quarkus.devtools.commands.data.QuarkusCommandException;
 import io.quarkus.devtools.commands.data.QuarkusCommandInvocation;
@@ -13,12 +12,12 @@
 import io.quarkus.devtools.messagewriter.MessageWriter;
 import io.quarkus.devtools.project.BuildTool;
 import io.quarkus.devtools.project.extensions.ExtensionManager;
-import io.quarkus.registry.DefaultExtensionRegistry;
-import io.quarkus.registry.ExtensionRegistry;
+import io.quarkus.registry.catalog.Extension;
+import io.quarkus.registry.catalog.ExtensionOrigin;
 import java.io.IOException;
 import java.util.Collection;
+import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.function.BiConsumer;
 import java.util.function.Function;
 
@@ -28,9 +27,10 @@
  */
 public class ListExtensionsCommandHandler implements QuarkusCommandHandler {
 
-    private static final String FULL_FORMAT = "%-8s %-50s %-50s %-25s%n%s";
+    private static final String FULL_FORMAT = "%-8s %-50s %-50s %-25s%s";
     private static final String CONCISE_FORMAT = "%-50s %-50s";
     private static final String NAME_FORMAT = "%-50s";
+    private static final String ORIGINS_FORMAT = "%-50s %-60s %s";
 
     @Override
     public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws QuarkusCommandException {
@@ -43,10 +43,40 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws
         final String search = invocation.getValue(ListExtensions.SEARCH, "*");
         final ExtensionManager extensionManager = invocation.getValue(ListExtensions.EXTENSION_MANAGER,
                 invocation.getQuarkusProject().getExtensionManager());
-        ExtensionRegistry extensionRegistry = invocation.getValue(ListExtensions.EXTENSION_REGISTRY);
-        if (extensionRegistry == null) {
-            extensionRegistry = DefaultExtensionRegistry.fromPlatform(invocation.getPlatformDescriptor());
+
+        final Collection<Extension> extensions = search == null ? invocation.getExtensionsCatalog().getExtensions()
+                : QuarkusCommandHandlers.select(search, invocation.getExtensionsCatalog().getExtensions(), true)
+                        .getExtensions();
+
+        if (extensions.isEmpty()) {
+            log.info("No extension found with pattern '%s'", search);
+            return QuarkusCommandOutcome.success();
+        }
+
+        if (!cli) {
+            String extensionStatus = all ? "available" : "installable";
+            if (installedOnly)
+                extensionStatus = "installed";
+            log.info("%nCurrent Quarkus extensions %s: ", extensionStatus);
+        }
+
+        BiConsumer<MessageWriter, Object[]> currentFormatter;
+        switch (format.toLowerCase()) {
+            case "name":
+                currentFormatter = this::nameFormatter;
+                break;
+            case "full":
+                currentFormatter = this::fullFormatter;
+                log.info(String.format(FULL_FORMAT, "Status", "Extension", "ArtifactId", "Updated Version", "Guide"));
+                break;
+            case "origins":
+                currentFormatter = this::originsFormatter;
+                break;
+            case "concise":
+            default:
+                currentFormatter = this::conciseFormatter;
         }
+
         Map<AppArtifactKey, AppArtifactCoords> installedByKey;
         try {
             installedByKey = extensionManager.getInstalled().stream()
@@ -54,78 +84,74 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws
         } catch (IOException e) {
             throw new QuarkusCommandException("Failed to determine the list of installed extensions", e);
         }
-        String quarkusVersion = invocation.getPlatformDescriptor().getQuarkusVersion();
-        Collection<Extension> platformExtensions = extensionRegistry.list(quarkusVersion, search);
-        if (platformExtensions.isEmpty()) {
-            log.info("No extension found with pattern '%s'", search);
-        } else {
-            if (!cli) {
-                String extensionStatus = all ? "available" : "installable";
-                if (installedOnly)
-                    extensionStatus = "installed";
-                log.info("%nCurrent Quarkus extensions %s: ", extensionStatus);
-            }
-
-            BiConsumer<MessageWriter, String[]> currentFormatter;
-            switch (format.toLowerCase()) {
-                case "name":
-                    currentFormatter = this::nameFormatter;
-                    break;
-                case "full":
-                    currentFormatter = this::fullFormatter;
-                    currentFormatter.accept(log,
-                            new String[] { "Status", "Extension", "ArtifactId", "Updated Version", "Guide" });
-                    break;
-                case "concise":
-                default:
-                    currentFormatter = this::conciseFormatter;
-            }
-
-            platformExtensions.forEach(platformExtension -> display(log, platformExtension,
-                    installedByKey.get(toKey(platformExtension)), all, installedOnly, currentFormatter));
-            final BuildTool buildTool = invocation.getQuarkusProject().getBuildTool();
-            boolean isGradle = BuildTool.GRADLE.equals(buildTool) || BuildTool.GRADLE_KOTLIN_DSL.equals(buildTool);
-
-            if (!cli) {
-                if ("concise".equalsIgnoreCase(format)) {
-                    if (isGradle) {
-                        log.info("\nTo get more information, append --format=full to your command line.");
-                    } else {
-                        log.info(
-                                "\nTo get more information, append -Dquarkus.extension.format=full to your command line.");
-                    }
-                }
-
+        extensions.stream()
+                .filter(e -> !e.isUnlisted())
+                .forEach(e -> display(log, e, installedByKey.get(toKey(e)), all, installedOnly, currentFormatter));
+        final BuildTool buildTool = invocation.getQuarkusProject().getBuildTool();
+        boolean isGradle = BuildTool.GRADLE.equals(buildTool) || BuildTool.GRADLE_KOTLIN_DSL.equals(buildTool);
+
+        if (!cli) {
+            if ("concise".equalsIgnoreCase(format)) {
                 if (isGradle) {
-                    log.info("\nAdd an extension to your project by adding the dependency to your " +
-                            "build.gradle or use `./gradlew addExtension --extensions=\"artifactId\"`");
+                    log.info("\nTo get more information, append --format=full to your command line.");
                 } else {
-                    log.info("\nAdd an extension to your project by adding the dependency to your " +
-                            "pom.xml or use `./mvnw quarkus:add-extension -Dextensions=\"artifactId\"`");
+                    log.info(
+                            "\nTo get more information, append -Dquarkus.extension.format=full to your command line.");
                 }
             }
 
+            if (isGradle) {
+                log.info("\nAdd an extension to your project by adding the dependency to your " +
+                        "build.gradle or use `./gradlew addExtension --extensions=\"artifactId\"`");
+            } else {
+                log.info("\nAdd an extension to your project by adding the dependency to your " +
+                        "pom.xml or use `./mvnw quarkus:add-extension -Dextensions=\"artifactId\"`");
+            }
         }
 
         return QuarkusCommandOutcome.success();
     }
 
-    private void conciseFormatter(MessageWriter writer, String[] cols) {
-        writer.info(String.format(CONCISE_FORMAT, cols[1], cols[2]));
+    private void conciseFormatter(MessageWriter writer, Object[] cols) {
+        Extension e = (Extension) cols[1];
+        writer.info(String.format(CONCISE_FORMAT, e.getName(), e.getArtifact().getArtifactId()));
     }
 
-    private void fullFormatter(MessageWriter writer, String[] cols) {
-        writer.info(String.format(FULL_FORMAT, cols[0], cols[1], cols[2], cols[3], cols[4]));
+    private void fullFormatter(MessageWriter writer, Object[] cols) {
+        Extension e = (Extension) cols[1];
+        writer.info(String.format(FULL_FORMAT, cols[0], e.getName(), e.getArtifact().getArtifactId(), cols[2],
+                e.getGuide() == null ? "" : e.getGuide()));
     }
 
-    private void nameFormatter(MessageWriter writer, String[] cols) {
-        writer.info(String.format(NAME_FORMAT, cols[2]));
+    private void nameFormatter(MessageWriter writer, Object[] cols) {
+        Extension e = (Extension) cols[1];
+        writer.info(String.format(NAME_FORMAT, e.getArtifact().getArtifactId()));
     }
 
-    private void display(MessageWriter messageWriter, final Extension platformExtension, final AppArtifactCoords installed,
+    private void originsFormatter(MessageWriter writer, Object[] cols) {
+        Extension e = (Extension) cols[1];
+        String origin = null;
+        int i = 0;
+        final List<ExtensionOrigin> origins = e.getOrigins();
+        while (i < origins.size() && origin == null) {
+            final ExtensionOrigin o = origins.get(i++);
+            if (o.isPlatform()) {
+                origin = o.getBom().toString();
+            }
+        }
+        writer.info(String.format(ORIGINS_FORMAT, e.getName(), e.getArtifact().getVersion(), origin == null ? "" : origin));
+        while (i < origins.size()) {
+            final ExtensionOrigin o = origins.get(i++);
+            if (o.isPlatform()) {
+                writer.info(String.format(ORIGINS_FORMAT, "", "", o.getBom().toString()));
+            }
+        }
+    }
+
+    private void display(MessageWriter messageWriter, final Extension e, final AppArtifactCoords installed,
             boolean all,
             boolean installedOnly,
-            BiConsumer<MessageWriter, String[]> formatter) {
+            BiConsumer<MessageWriter, Object[]> formatter) {
         if (installedOnly && installed == null) {
             return;
         }
@@ -140,24 +166,17 @@ private void display(MessageWriter messageWriter, final Extension platformExtens
             final String installedVersion = installed.getVersion();
             if (installedVersion == null) {
                 label = "default";
-                version = platformExtension.getVersion();
-            } else if (installedVersion.equalsIgnoreCase(platformExtension.getVersion())) {
+                version = e.getArtifact().getVersion();
+            } else if (installedVersion.equalsIgnoreCase(e.getArtifact().getVersion())) {
                 label = "custom";
                 version = installedVersion;
             } else {
                 label = "custom*";
-                version = String.format("%s* <> %s", installedVersion, platformExtension.getVersion());
+                version = String.format("%s* <> %s", installedVersion, e.getArtifact().getVersion());
             }
         }
 
-        String[] result = new String[] { label, platformExtension.getName(), platformExtension.getArtifactId(), version,
-                platformExtension.getGuide() };
-
-        for (int i = 0; i < result.length; i++) {
-            result[i] = Objects.toString(result[i], "");
-        }
-
-        formatter.accept(messageWriter, result);
+        formatter.accept(messageWriter, new Object[] { label, e, version });
     }
 
 }
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/ListPlatformsCommandHandler.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/ListPlatformsCommandHandler.java
new file mode 100644
index 00000000000000..c2b23ad05b3b7e
--- /dev/null
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/ListPlatformsCommandHandler.java
@@ -0,0 +1,27 @@
+package io.quarkus.devtools.commands.handlers;
+
+import io.quarkus.devtools.commands.data.QuarkusCommandException;
+import io.quarkus.devtools.commands.data.QuarkusCommandInvocation;
+import io.quarkus.devtools.commands.data.QuarkusCommandOutcome;
+import io.quarkus.devtools.messagewriter.MessageWriter;
+import io.quarkus.maven.ArtifactCoords;
+import io.quarkus.registry.catalog.ExtensionCatalog;
+import io.quarkus.registry.catalog.ExtensionOrigin;
+
+public class ListPlatformsCommandHandler implements QuarkusCommandHandler {
+
+    @Override
+    public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws QuarkusCommandException {
+        final ExtensionCatalog catalog = invocation.getQuarkusProject().getExtensionsCatalog();
+        logPlatform(catalog, invocation.log());
+        catalog.getDerivedFrom().forEach(o -> logPlatform(o, invocation.log()));
+        return QuarkusCommandOutcome.success();
+    }
+
+    private static void logPlatform(ExtensionOrigin o, MessageWriter log) {
+        final ArtifactCoords bom = o.isPlatform() ? o.getBom() : null;
+        if (bom != null) {
+            log.info(bom.toString());
+        }
+    }
+}
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/MultipleExtensionsFoundException.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/MultipleExtensionsFoundException.java
similarity index 69%
rename from independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/MultipleExtensionsFoundException.java
rename to independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/MultipleExtensionsFoundException.java
index d036b8393b1dee..571d1ebfba405e 100644
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/MultipleExtensionsFoundException.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/MultipleExtensionsFoundException.java
@@ -1,14 +1,11 @@
-package io.quarkus.registry;
+package io.quarkus.devtools.commands.handlers;
 
-import io.quarkus.dependencies.Extension;
+import io.quarkus.registry.catalog.Extension;
 import java.util.Collection;
 import java.util.Objects;
 
 /**
- * Thrown when multiple extensions are found for a given extension when
- * {@link ExtensionRegistry#planInstallation(String, Collection)} is called
- *
- * @see {@link ExtensionRegistry#planInstallation(String, Collection)}
+ * Thrown when multiple extensions are found for a given installation plan
  */
 public class MultipleExtensionsFoundException extends RuntimeException {
 
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/QuarkusCommandHandlers.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/QuarkusCommandHandlers.java
index 3a80dde24a6ba0..70a75f0e0e4bd0 100644
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/QuarkusCommandHandlers.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/QuarkusCommandHandlers.java
@@ -5,15 +5,16 @@
 
 import io.quarkus.bootstrap.model.AppArtifactCoords;
 import io.quarkus.bootstrap.model.AppArtifactKey;
-import io.quarkus.dependencies.Extension;
 import io.quarkus.devtools.commands.data.QuarkusCommandInvocation;
 import io.quarkus.devtools.commands.data.SelectionResult;
 import io.quarkus.devtools.project.extensions.Extensions;
+import io.quarkus.maven.ArtifactKey;
+import io.quarkus.registry.catalog.Extension;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
-import java.util.LinkedHashSet;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.regex.Pattern;
 import java.util.regex.PatternSyntaxException;
@@ -35,7 +36,7 @@ static List<AppArtifactCoords> computeCoordsFromQuery(final QuarkusCommandInvoca
             } else if (countColons > 1) {
                 builder.add(AppArtifactCoords.fromString(query));
             } else {
-                Collection<Extension> extensions = invocation.getPlatformDescriptor().getExtensions();
+                Collection<Extension> extensions = invocation.getExtensionsCatalog().getExtensions();
                 SelectionResult result = select(query, extensions, false);
                 if (result.matches()) {
                     final Set<AppArtifactCoords> withStrippedVersion = result.getExtensions().stream().map(Extensions::toCoords)
@@ -46,7 +47,7 @@ static List<AppArtifactCoords> computeCoordsFromQuery(final QuarkusCommandInvoca
                     StringBuilder sb = new StringBuilder();
                     // We have 3 cases, we can still have a single candidate, but the match is on label
                     // or we have several candidates, or none
-                    Set<Extension> candidates = result.getExtensions();
+                    Collection<Extension> candidates = result.getExtensions();
                     if (candidates.isEmpty()) {
                         // No matches at all.
                         invocation.log().error("Cannot find a dependency matching '" + query + "', maybe a typo?");
@@ -71,77 +72,73 @@ static List<AppArtifactCoords> computeCoordsFromQuery(final QuarkusCommandInvoca
      * Selection algorithm.
      *
      * @param query the query
-     * @param allPlatformExtensions the list of all platform extensions
+     * @param allExtensions the list of all platform extensions
      * @param labelLookup whether or not the query must be tested against the labels of the extensions. Should
      *        be {@code false} by default.
      * @return the list of matching candidates and whether or not a match has been found.
      */
-    static SelectionResult select(final String query, final Collection<Extension> allPlatformExtensions,
+    static SelectionResult select(final String query, final Collection<Extension> allExtensions,
             final boolean labelLookup) {
         String q = query.trim().toLowerCase();
 
-        // Try exact matches
-        Set<Extension> matchesNameOrArtifactId = allPlatformExtensions.stream()
-                .filter(extension -> extension.getName().equalsIgnoreCase(q) || matchesArtifactId(extension.getArtifactId(), q))
-                .collect(Collectors.toSet());
-        if (matchesNameOrArtifactId.size() == 1) {
-            return new SelectionResult(matchesNameOrArtifactId, true);
-        }
+        final Map<ArtifactKey, Extension> matches = new LinkedHashMap<>();
 
-        final List<Extension> listedPlatformExtensions = allPlatformExtensions.stream()
-                .filter(e -> !e.isUnlisted()).collect(Collectors.toList());
+        if (!isExpression(q)) {
+            // Try exact matches
+            allExtensions.stream()
+                    .filter(extension -> extension.getName().equalsIgnoreCase(q)
+                            || matchesArtifactId(extension.getArtifact().getArtifactId(), q))
+                    .forEach(e -> matches.putIfAbsent(e.getArtifact().getKey(), e));
+            if (matches.size() == 1) {
+                return new SelectionResult(matches.values(), true);
+            }
 
-        // Try short names
-        Set<Extension> matchesShortName = listedPlatformExtensions.stream().filter(extension -> matchesShortName(extension, q))
-                .collect(Collectors.toSet());
+            final List<Extension> listedExtensions = getListedExtensions(allExtensions);
 
-        if (matchesShortName.size() == 1 && matchesNameOrArtifactId.isEmpty()) {
-            return new SelectionResult(matchesShortName, true);
-        }
+            // Try short names
+            listedExtensions.stream().filter(extension -> matchesShortName(extension, q))
+                    .forEach(e -> matches.putIfAbsent(e.getArtifact().getKey(), e));
+            if (matches.size() == 1) {
+                return new SelectionResult(matches.values(), true);
+            }
 
-        // Partial matches on name, artifactId and short names
-        Set<Extension> partialMatches = listedPlatformExtensions.stream()
-                .filter(extension -> extension.getName().toLowerCase().contains(q)
-                        || extension.getArtifactId().toLowerCase().contains(q)
-                        || extension.getShortName().toLowerCase().contains(q))
-                .collect(Collectors.toSet());
-        // Even if we have a single partial match, if the name, artifactId and short names are ambiguous, so not
-        // consider it as a match.
-        if (partialMatches.size() == 1 && matchesNameOrArtifactId.isEmpty() && matchesShortName.isEmpty()) {
-            return new SelectionResult(partialMatches, true);
-        }
+            // Partial matches on name, artifactId and short names
+            listedExtensions.stream()
+                    .filter(extension -> extension.getName().toLowerCase().contains(q)
+                            || extension.getArtifact().getArtifactId().toLowerCase().contains(q)
+                            || extension.getShortName().toLowerCase().contains(q))
+                    .forEach(e -> matches.putIfAbsent(e.getArtifact().getKey(), e));
+            // Even if we have a single partial match, if the name, artifactId and short names are ambiguous, so not
+            // consider it as a match.
+            if (matches.size() == 1) {
+                return new SelectionResult(matches.values(), true);
+            }
 
-        // find by labels
-        List<Extension> matchesLabels;
-        if (labelLookup) {
-            matchesLabels = listedPlatformExtensions.stream()
-                    .filter(extension -> extension.labelsForMatching().contains(q)).collect(Collectors.toList());
-        } else {
-            matchesLabels = Collections.emptyList();
+            // find by labels
+            if (labelLookup) {
+                listedExtensions.stream()
+                        .filter(extension -> extension.labelsForMatching().contains(q))
+                        .forEach(e -> matches.put(e.getArtifact().getKey(), e));
+            }
+            return new SelectionResult(matches.values(), false);
         }
-
+        final List<Extension> listedExtensions = getListedExtensions(allExtensions);
         // find by pattern
-        Set<Extension> matchesPatterns;
         Pattern pattern = toRegex(q);
         if (pattern != null) {
-            matchesPatterns = listedPlatformExtensions.stream()
+            listedExtensions.stream()
                     .filter(extension -> pattern.matcher(extension.getName().toLowerCase()).matches()
-                            || pattern.matcher(extension.getArtifactId().toLowerCase()).matches()
+                            || pattern.matcher(extension.getArtifact().getArtifactId().toLowerCase()).matches()
                             || pattern.matcher(extension.getShortName().toLowerCase()).matches()
                             || matchLabels(pattern, extension.getKeywords()))
-                    .collect(Collectors.toSet());
-            return new SelectionResult(matchesPatterns, true);
-        } else {
-            matchesPatterns = Collections.emptySet();
+                    .forEach(e -> matches.putIfAbsent(e.getArtifact().getKey(), e));
         }
+        return new SelectionResult(matches.values(), !matches.isEmpty());
+    }
 
-        Set<Extension> candidates = new LinkedHashSet<>();
-        candidates.addAll(matchesNameOrArtifactId);
-        candidates.addAll(matchesShortName);
-        candidates.addAll(partialMatches);
-        candidates.addAll(matchesLabels);
-        candidates.addAll(matchesPatterns);
-        return new SelectionResult(candidates, false);
+    private static List<Extension> getListedExtensions(final Collection<Extension> allExtensions) {
+        return allExtensions.stream()
+                .filter(e -> !e.isUnlisted()).collect(Collectors.toList());
     }
 
     private static boolean matchLabels(Pattern pattern, List<String> labels) {
@@ -166,11 +163,8 @@ private static Pattern toRegex(final String str) {
     }
 
     private static String wildcardToRegex(String wildcard) {
-        if (wildcard == null || wildcard.isEmpty()) {
-            return null;
-        }
         // don't try with file match char in pattern
-        if (!(wildcard.contains("*") || wildcard.contains("?"))) {
+        if (!isExpression(wildcard)) {
             return null;
         }
         StringBuffer s = new StringBuffer(wildcard.length());
@@ -210,6 +204,10 @@ private static String wildcardToRegex(String wildcard) {
         return (s.toString());
     }
 
+    private static boolean isExpression(String s) {
+        return s == null || s.isEmpty() ? false : s.contains("*") || s.contains("?");
+    }
+
     private static boolean matchesShortName(Extension extension, String q) {
         return q.equalsIgnoreCase(extension.getShortName());
     }
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/RemoveExtensionsCommandHandler.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/RemoveExtensionsCommandHandler.java
index 56f863acb113ba..798a187841b5d5 100644
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/RemoveExtensionsCommandHandler.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/RemoveExtensionsCommandHandler.java
@@ -38,20 +38,16 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws
         final ExtensionManager extensionManager = invocation.getValue(EXTENSION_MANAGER,
                 invocation.getQuarkusProject().getExtensionManager());
         try {
-            if (extensionsToRemove != null) {
-                final Set<AppArtifactKey> keys = extensionsToRemove.stream().map(AppArtifactCoords::getKey)
-                        .collect(Collectors.toSet());
-                final UninstallResult result = extensionManager.uninstall(keys);
-                result.getUninstalled()
-                        .forEach(a -> invocation.log()
-                                .info(MessageIcons.OK_ICON + " Extension " + a.getGroupId() + ":" + a.getArtifactId()
-                                        + " has been uninstalled"));
-                return new QuarkusCommandOutcome(true).setValue(RemoveExtensions.OUTCOME_UPDATED, result.isSourceUpdated());
-            }
+            final Set<AppArtifactKey> keys = extensionsToRemove.stream().map(AppArtifactCoords::getKey)
+                    .collect(Collectors.toSet());
+            final UninstallResult result = extensionManager.uninstall(keys);
+            result.getUninstalled()
+                    .forEach(a -> invocation.log()
+                            .info(MessageIcons.OK_ICON + " Extension " + a.getGroupId() + ":" + a.getArtifactId()
+                                    + " has been uninstalled"));
+            return new QuarkusCommandOutcome(true).setValue(RemoveExtensions.OUTCOME_UPDATED, result.isSourceUpdated());
         } catch (IOException e) {
             throw new QuarkusCommandException("Failed to remove extensions", e);
         }
-
-        return new QuarkusCommandOutcome(true).setValue(RemoveExtensions.OUTCOME_UPDATED, false);
     }
 }
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/BuildTool.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/BuildTool.java
index fb59fa39d4421e..9678932a9ae176 100644
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/BuildTool.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/BuildTool.java
@@ -4,7 +4,7 @@
 import io.quarkus.devtools.project.buildfile.KotlinGradleBuildFile;
 import io.quarkus.devtools.project.buildfile.MavenBuildFile;
 import io.quarkus.devtools.project.extensions.ExtensionManager;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
+import io.quarkus.registry.catalog.ExtensionCatalog;
 import java.nio.file.Path;
 
 /**
@@ -59,7 +59,7 @@ public String getBuildDirectory() {
     }
 
     public ExtensionManager createExtensionManager(final Path projectDirPath,
-            final QuarkusPlatformDescriptor platformDescriptor) {
+            ExtensionCatalog catalog) {
         switch (this) {
             case GRADLE:
                 return new GroovyGradleBuildFile();
@@ -67,7 +67,7 @@ public ExtensionManager createExtensionManager(final Path projectDirPath,
                 return new KotlinGradleBuildFile();
             case MAVEN:
             default:
-                return new MavenBuildFile(projectDirPath, platformDescriptor);
+                return new MavenBuildFile(projectDirPath, catalog);
         }
     }
 
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/QuarkusProject.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/QuarkusProject.java
index 976040c3a961e3..bc98410fcb6ccb 100644
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/QuarkusProject.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/QuarkusProject.java
@@ -2,47 +2,38 @@
 
 import static java.util.Objects.requireNonNull;
 
-import io.quarkus.devtools.project.buildfile.MavenBuildFile;
+import io.quarkus.devtools.messagewriter.MessageWriter;
 import io.quarkus.devtools.project.extensions.ExtensionManager;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
+import io.quarkus.platform.descriptor.loader.json.ResourceLoader;
+import io.quarkus.registry.catalog.ExtensionCatalog;
 import java.nio.file.Path;
 
 public final class QuarkusProject {
 
     private final Path projectDirPath;
-    private final QuarkusPlatformDescriptor platformDescriptor;
+    private ExtensionCatalog catalog;
+    private ResourceLoader resourceLoader;
     private final ExtensionManager extensionManager;
 
-    private QuarkusProject(final Path projectDirPath, final QuarkusPlatformDescriptor platformDescriptor,
-            final ExtensionManager extensionManager) {
+    private QuarkusProject(Path projectDirPath, ExtensionCatalog catalog, ResourceLoader codestartPathLoader,
+            MessageWriter log,
+            ExtensionManager extensionManager) {
         this.projectDirPath = requireNonNull(projectDirPath, "projectDirPath is required");
-        this.platformDescriptor = requireNonNull(platformDescriptor, "platformDescriptor is required");
+        this.catalog = requireNonNull(catalog, "catalog is required");
+        this.resourceLoader = requireNonNull(codestartPathLoader, "resourceLoader is required");
         this.extensionManager = requireNonNull(extensionManager, "extensionManager is required");
     }
 
-    public static QuarkusProject of(final Path projectDirPath, final QuarkusPlatformDescriptor platformDescriptor,
+    public static QuarkusProject of(final Path projectDirPath, final ExtensionCatalog catalog,
+            ResourceLoader resourceLoader, MessageWriter log,
             final ExtensionManager extensionManager) {
-        return new QuarkusProject(projectDirPath, platformDescriptor, extensionManager);
-    }
-
-    public static QuarkusProject of(final Path projectDirPath, final QuarkusPlatformDescriptor platformDescriptor,
-            final BuildTool buildTool) {
-        return new QuarkusProject(projectDirPath, platformDescriptor,
-                buildTool.createExtensionManager(projectDirPath, platformDescriptor));
-    }
-
-    public static QuarkusProject maven(final Path projectDirPath, final QuarkusPlatformDescriptor platformDescriptor) {
-        return new QuarkusProject(projectDirPath, platformDescriptor,
-                new MavenBuildFile(projectDirPath, platformDescriptor));
+        return new QuarkusProject(projectDirPath, catalog, resourceLoader, log, extensionManager);
     }
 
-    public static QuarkusProject resolveExistingProject(final Path projectDirPath,
-            final QuarkusPlatformDescriptor descriptor) {
-        final BuildTool buildTool = resolveExistingProjectBuildTool(projectDirPath);
-        if (buildTool == null) {
-            throw new IllegalStateException("This is neither a Maven or Gradle project");
-        }
-        return of(projectDirPath, descriptor, buildTool);
+    public static QuarkusProject of(Path projectDirPath, ExtensionCatalog catalog, ResourceLoader codestartsResourceLoader,
+            MessageWriter log, BuildTool buildTool) {
+        return new QuarkusProject(projectDirPath, catalog, codestartsResourceLoader, log,
+                buildTool.createExtensionManager(projectDirPath, catalog));
     }
 
     public Path getProjectDirPath() {
@@ -57,8 +48,12 @@ public ExtensionManager getExtensionManager() {
         return extensionManager;
     }
 
-    public QuarkusPlatformDescriptor getPlatformDescriptor() {
-        return platformDescriptor;
+    public ExtensionCatalog getExtensionsCatalog() {
+        return catalog;
+    }
+
+    public ResourceLoader getCodestartsResourceLoader() {
+        return resourceLoader;
     }
 
     public static BuildTool resolveExistingProjectBuildTool(Path projectDirPath) {
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/QuarkusProjectHelper.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/QuarkusProjectHelper.java
new file mode 100644
index 00000000000000..3f17041afc77c0
--- /dev/null
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/QuarkusProjectHelper.java
@@ -0,0 +1,171 @@
+package io.quarkus.devtools.project;
+
+import io.quarkus.bootstrap.model.AppArtifactCoords;
+import io.quarkus.bootstrap.resolver.maven.BootstrapMavenException;
+import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver;
+import io.quarkus.devtools.messagewriter.MessageWriter;
+import io.quarkus.devtools.project.extensions.ExtensionManager;
+import io.quarkus.platform.descriptor.loader.json.ClassPathResourceLoader;
+import io.quarkus.platform.tools.ToolsConstants;
+import io.quarkus.platform.tools.ToolsUtils;
+import io.quarkus.registry.ExtensionCatalogResolver;
+import io.quarkus.registry.RegistryResolutionException;
+import io.quarkus.registry.catalog.ExtensionCatalog;
+import io.quarkus.registry.config.RegistriesConfig;
+import io.quarkus.registry.config.RegistriesConfigLocator;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.artifact.DefaultArtifact;
+
+public class QuarkusProjectHelper {
+
+    private static final String CODESTARTS_ARTIFACTS = "codestarts-artifacts";
+
+    private static RegistriesConfig toolsConfig;
+    private static MessageWriter log;
+    private static MavenArtifactResolver artifactResolver;
+    private static ExtensionCatalogResolver catalogResolver;
+
+    public static QuarkusProject getProject(Path projectDir) {
+        BuildTool buildTool = QuarkusProject.resolveExistingProjectBuildTool(projectDir);
+        if (buildTool == null) {
+            buildTool = BuildTool.MAVEN;
+        }
+        return getProject(projectDir, buildTool);
+    }
+
+    public static QuarkusProject getProject(Path projectDir, String quarkusVersion) {
+        // TODO remove this method once the default registry becomes available
+        BuildTool buildTool = QuarkusProject.resolveExistingProjectBuildTool(projectDir);
+        if (buildTool == null) {
+            buildTool = BuildTool.MAVEN;
+        }
+        return getProject(projectDir, buildTool, quarkusVersion);
+    }
+
+    public static QuarkusProject getProject(Path projectDir, BuildTool buildTool, String quarkusVersion) {
+        // TODO remove this method once the default registry becomes available
+        final ExtensionCatalogResolver catalogResolver = getCatalogResolver();
+        if (catalogResolver.hasRegistries()) {
+            return getProject(projectDir, buildTool);
+        }
+        return QuarkusProjectHelper.getProject(projectDir,
+                ToolsUtils.resolvePlatformDescriptorDirectly(null, null, quarkusVersion, artifactResolver(), messageWriter()),
+                buildTool);
+    }
+
+    public static QuarkusProject getProject(Path projectDir, BuildTool buildTool) {
+        final ExtensionCatalog catalog;
+        try {
+            catalog = resolveExtensionCatalog();
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to resolve the Quarkus extension catalog", e);
+        }
+
+        return getProject(projectDir, catalog, buildTool, messageWriter());
+    }
+
+    public static QuarkusProject getProject(Path projectDir, ExtensionCatalog catalog, BuildTool buildTool) {
+        return getProject(projectDir, catalog, buildTool, messageWriter());
+    }
+
+    public static QuarkusProject getProject(Path projectDir, ExtensionCatalog catalog, BuildTool buildTool,
+            MessageWriter log) {
+        return QuarkusProject.of(projectDir, catalog, getResourceLoader(catalog, artifactResolver()),
+                log, buildTool);
+    }
+
+    public static QuarkusProject getProject(Path projectDir, ExtensionManager extManager) throws RegistryResolutionException {
+        return getProject(projectDir, resolveExtensionCatalog(), extManager, messageWriter());
+    }
+
+    public static ExtensionCatalog resolveExtensionCatalog() throws RegistryResolutionException {
+        return getCatalogResolver().resolveExtensionCatalog();
+    }
+
+    public static QuarkusProject getProject(Path projectDir, ExtensionCatalog catalog, ExtensionManager extManager,
+            MessageWriter log) {
+        return QuarkusProject.of(projectDir, catalog, getResourceLoader(catalog, artifactResolver()),
+                log, extManager);
+    }
+
+    public static ClassPathResourceLoader getResourceLoader(ExtensionCatalog catalog) {
+        return getResourceLoader(catalog, artifactResolver());
+    }
+
+    public static ClassPathResourceLoader getResourceLoader(ExtensionCatalog catalog, MavenArtifactResolver mvn) {
+        Object o = catalog.getMetadata().get(CODESTARTS_ARTIFACTS);
+        final List<Artifact> codestartsArtifacts;
+        if (o == null) {
+            // This is hardcoded temporarily
+            codestartsArtifacts = Arrays
+                    .asList(new DefaultArtifact(ToolsConstants.IO_QUARKUS, "quarkus-platform-descriptor-json", "", "jar",
+                            catalog.getQuarkusCoreVersion()));
+        } else {
+            @SuppressWarnings({ "unchecked" })
+            final List<Object> list = o instanceof List ? (List<Object>) o : Arrays.asList(o);
+            codestartsArtifacts = new ArrayList<>(list.size());
+            for (Object i : list) {
+                AppArtifactCoords coords = AppArtifactCoords.fromString(i.toString());
+                codestartsArtifacts.add(new DefaultArtifact(coords.getGroupId(), coords.getArtifactId(), coords.getClassifier(),
+                        coords.getType(), coords.getVersion()));
+            }
+        }
+
+        final URL[] urls = new URL[codestartsArtifacts.size()];
+        for (int i = 0; i < codestartsArtifacts.size(); ++i) {
+            try {
+                urls[i] = mvn.resolve(codestartsArtifacts.get(i)).getArtifact().getFile().toURI().toURL();
+            } catch (Exception e) {
+                throw new RuntimeException("Failed to resolve codestart artifact " + codestartsArtifacts.get(i), e);
+            }
+        }
+
+        return new ClassPathResourceLoader(new URLClassLoader(urls, null));
+    }
+
+    public static ExtensionCatalogResolver getCatalogResolver() {
+        return catalogResolver == null ? catalogResolver = getCatalogResolver(artifactResolver(), messageWriter())
+                : catalogResolver;
+    }
+
+    public static ExtensionCatalogResolver getCatalogResolver(MessageWriter log) {
+        return catalogResolver == null ? catalogResolver = getCatalogResolver(artifactResolver(), log)
+                : catalogResolver;
+    }
+
+    public static ExtensionCatalogResolver getCatalogResolver(MavenArtifactResolver resolver, MessageWriter log) {
+        return ExtensionCatalogResolver.builder()
+                .artifactResolver(resolver)
+                .config(toolsConfig())
+                .messageWriter(log)
+                .build();
+    }
+
+    private static RegistriesConfig toolsConfig() {
+        return toolsConfig == null ? toolsConfig = RegistriesConfigLocator.resolveConfig() : toolsConfig;
+    }
+
+    private static MessageWriter messageWriter() {
+        return log == null ? log = toolsConfig().isDebug() ? MessageWriter.debug() : MessageWriter.info() : log;
+    }
+
+    private static MavenArtifactResolver artifactResolver() {
+        if (artifactResolver == null) {
+            try {
+                artifactResolver = MavenArtifactResolver.builder()
+                        .setArtifactTransferLogging(toolsConfig().isDebug())
+                        .setWorkspaceDiscovery(false)
+                        .build();
+            } catch (BootstrapMavenException e) {
+                throw new IllegalStateException("Failed to initialize the Maven artifact resolver", e);
+            }
+        }
+        return artifactResolver;
+    }
+}
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/AbstractGradleBuildFile.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/AbstractGradleBuildFile.java
index d981fe23cb76e6..2a7f495f1a2269 100644
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/AbstractGradleBuildFile.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/AbstractGradleBuildFile.java
@@ -2,7 +2,7 @@
 
 import io.quarkus.bootstrap.model.AppArtifactCoords;
 import io.quarkus.bootstrap.model.AppArtifactKey;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
+import io.quarkus.registry.catalog.ExtensionCatalog;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -27,13 +27,13 @@ abstract class AbstractGradleBuildFile extends BuildFile {
 
     private final AtomicReference<Model> modelReference = new AtomicReference<>();
 
-    public AbstractGradleBuildFile(final Path projectDirPath, final QuarkusPlatformDescriptor platformDescriptor) {
-        this(projectDirPath, platformDescriptor, null);
+    public AbstractGradleBuildFile(final Path projectDirPath, final ExtensionCatalog catalog) {
+        this(projectDirPath, catalog, null);
     }
 
-    public AbstractGradleBuildFile(final Path projectDirPath, final QuarkusPlatformDescriptor platformDescriptor,
+    public AbstractGradleBuildFile(final Path projectDirPath, final ExtensionCatalog catalog,
             Path rootProjectPath) {
-        super(projectDirPath, platformDescriptor);
+        super(projectDirPath, catalog);
         this.rootProjectPath = rootProjectPath;
     }
 
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/AbstractGradleBuildFilesCreator.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/AbstractGradleBuildFilesCreator.java
index 1e0da5432ab958..8bae8894e615ae 100644
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/AbstractGradleBuildFilesCreator.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/AbstractGradleBuildFilesCreator.java
@@ -3,8 +3,9 @@
 import io.quarkus.bootstrap.model.AppArtifactCoords;
 import io.quarkus.devtools.project.QuarkusProject;
 import io.quarkus.devtools.project.buildfile.AbstractGradleBuildFile.Model;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
+import io.quarkus.maven.ArtifactCoords;
 import io.quarkus.platform.tools.ToolsUtils;
+import io.quarkus.registry.catalog.ExtensionCatalog;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -122,20 +123,21 @@ protected void writeToProjectFile(final String fileName, final byte[] content) t
     }
 
     private void createProperties() throws IOException {
-        final QuarkusPlatformDescriptor platform = quarkusProject.getPlatformDescriptor();
+        final ExtensionCatalog platform = quarkusProject.getExtensionsCatalog();
         Properties props = getModel().getPropertiesContent();
         if (props.getProperty("quarkusPluginVersion") == null) {
             props.setProperty("quarkusPluginVersion",
                     ToolsUtils.getGradlePluginVersion(ToolsUtils.readQuarkusProperties(platform)));
         }
+        final ArtifactCoords bom = platform.getBom();
         if (props.getProperty("quarkusPlatformGroupId") == null) {
-            props.setProperty("quarkusPlatformGroupId", platform.getBomGroupId());
+            props.setProperty("quarkusPlatformGroupId", bom.getGroupId());
         }
         if (props.getProperty("quarkusPlatformArtifactId") == null) {
-            props.setProperty("quarkusPlatformArtifactId", platform.getBomArtifactId());
+            props.setProperty("quarkusPlatformArtifactId", bom.getArtifactId());
         }
         if (props.getProperty("quarkusPlatformVersion") == null) {
-            props.setProperty("quarkusPlatformVersion", platform.getBomVersion());
+            props.setProperty("quarkusPlatformVersion", bom.getVersion());
         }
     }
 
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/AbstractGroovyGradleBuildFile.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/AbstractGroovyGradleBuildFile.java
index 3cc1acb842165a..1296a842cf7b0e 100644
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/AbstractGroovyGradleBuildFile.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/AbstractGroovyGradleBuildFile.java
@@ -2,7 +2,7 @@
 
 import io.quarkus.bootstrap.model.AppArtifactCoords;
 import io.quarkus.devtools.project.BuildTool;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
+import io.quarkus.registry.catalog.ExtensionCatalog;
 import java.nio.file.Path;
 
 public abstract class AbstractGroovyGradleBuildFile extends AbstractGradleBuildFile {
@@ -10,13 +10,13 @@ public abstract class AbstractGroovyGradleBuildFile extends AbstractGradleBuildF
     static final String BUILD_GRADLE_PATH = "build.gradle";
     static final String SETTINGS_GRADLE_PATH = "settings.gradle";
 
-    public AbstractGroovyGradleBuildFile(Path projectDirPath, QuarkusPlatformDescriptor platformDescriptor) {
-        super(projectDirPath, platformDescriptor);
+    public AbstractGroovyGradleBuildFile(Path projectDirPath, ExtensionCatalog catalog) {
+        super(projectDirPath, catalog);
     }
 
-    public AbstractGroovyGradleBuildFile(Path projectDirPath, QuarkusPlatformDescriptor platformDescriptor,
+    public AbstractGroovyGradleBuildFile(Path projectDirPath, ExtensionCatalog catalog,
             Path rootProjectPath) {
-        super(projectDirPath, platformDescriptor, rootProjectPath);
+        super(projectDirPath, catalog, rootProjectPath);
     }
 
     @Override
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/AbstractKotlinGradleBuildFile.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/AbstractKotlinGradleBuildFile.java
deleted file mode 100644
index abe1582185163c..00000000000000
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/AbstractKotlinGradleBuildFile.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package io.quarkus.devtools.project.buildfile;
-
-import io.quarkus.bootstrap.model.AppArtifactCoords;
-import io.quarkus.devtools.project.BuildTool;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
-import java.nio.file.Path;
-
-public abstract class AbstractKotlinGradleBuildFile extends AbstractGradleBuildFile {
-
-    static final String BUILD_GRADLE_PATH = "build.gradle.kts";
-    static final String SETTINGS_GRADLE_PATH = "settings.gradle.kts";
-
-    public AbstractKotlinGradleBuildFile(Path projectDirPath, QuarkusPlatformDescriptor platformDescriptor) {
-        super(projectDirPath, platformDescriptor);
-    }
-
-    public AbstractKotlinGradleBuildFile(Path projectDirPath, QuarkusPlatformDescriptor platformDescriptor,
-            Path rootProjectPath) {
-        super(projectDirPath, platformDescriptor, rootProjectPath);
-    }
-
-    @Override
-    String getSettingsGradlePath() {
-        return SETTINGS_GRADLE_PATH;
-    }
-
-    @Override
-    String getBuildGradlePath() {
-        return BUILD_GRADLE_PATH;
-    }
-
-    @Override
-    protected boolean addDependency(AppArtifactCoords coords, boolean managed) {
-        return addDependencyInModel(getModel(), coords, managed);
-    }
-
-    @Override
-    public BuildTool getBuildTool() {
-        return BuildTool.GRADLE_KOTLIN_DSL;
-    }
-
-    static boolean addDependencyInModel(Model model, AppArtifactCoords coords, boolean managed) {
-        return addDependencyInModel(model,
-                String.format("    implementation(%s)%n", createDependencyCoordinatesString(coords, managed, '"')));
-    }
-
-}
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/BuildFile.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/BuildFile.java
index 74e852db3078fc..811ca352715c8a 100644
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/BuildFile.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/BuildFile.java
@@ -1,15 +1,16 @@
 package io.quarkus.devtools.project.buildfile;
 
-import static io.quarkus.devtools.project.extensions.Extensions.findInList;
 import static java.util.Objects.requireNonNull;
 import static java.util.stream.Collectors.toList;
 
 import io.quarkus.bootstrap.model.AppArtifactCoords;
 import io.quarkus.bootstrap.model.AppArtifactKey;
-import io.quarkus.dependencies.Extension;
 import io.quarkus.devtools.project.extensions.ExtensionInstallPlan;
 import io.quarkus.devtools.project.extensions.ExtensionManager;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
+import io.quarkus.devtools.project.extensions.Extensions;
+import io.quarkus.maven.ArtifactKey;
+import io.quarkus.registry.catalog.Extension;
+import io.quarkus.registry.catalog.ExtensionCatalog;
 import java.io.IOException;
 import java.io.UncheckedIOException;
 import java.nio.file.Files;
@@ -17,17 +18,19 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
 
 public abstract class BuildFile implements ExtensionManager {
 
     private final Path projectDirPath;
-    private final QuarkusPlatformDescriptor platformDescriptor;
+    private final ExtensionCatalog catalog;
 
-    public BuildFile(final Path projectDirPath, final QuarkusPlatformDescriptor platformDescriptor) {
+    public BuildFile(final Path projectDirPath, ExtensionCatalog catalog) {
         this.projectDirPath = requireNonNull(projectDirPath, "projectPath is required");
-        this.platformDescriptor = requireNonNull(platformDescriptor, "platformDescriptor is required");
+        this.catalog = requireNonNull(catalog, "catalog is required");
     }
 
     @Override
@@ -127,16 +130,24 @@ protected void writeToProjectFile(final String fileName, final byte[] content) t
     }
 
     private boolean isQuarkusExtension(final AppArtifactKey key) {
-        // This will not always be true as the platform descriptor does not contain the list of all available extensions
-        return isDefinedInRegistry(platformDescriptor.getExtensions(), key);
+        if (catalog != null) {
+            return findInList(catalog.getExtensions(), key).isPresent();
+        }
+        return isDefinedInRegistry(catalog.getExtensions(), key);
     }
 
     private Set<AppArtifactKey> getDependenciesKeys() throws IOException {
         return getDependencies().stream().map(AppArtifactCoords::getKey).collect(Collectors.toSet());
     }
 
-    public static boolean isDefinedInRegistry(List<Extension> registry, final AppArtifactKey key) {
-        return findInList(registry, key).isPresent();
+    public static boolean isDefinedInRegistry(Collection<Extension> registry, final AppArtifactKey key) {
+        return Extensions.findInList(registry, key).isPresent();
+    }
+
+    private static Optional<io.quarkus.registry.catalog.Extension> findInList(
+            Collection<io.quarkus.registry.catalog.Extension> list, final AppArtifactKey key) {
+        ArtifactKey k = new ArtifactKey(key.getGroupId(), key.getArtifactId(), key.getClassifier(), key.getType());
+        return list.stream().filter(e -> Objects.equals(e.getArtifact().getKey(), k)).findFirst();
     }
 
 }
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/GroovyGradleBuildFilesCreator.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/GroovyGradleBuildFilesCreator.java
index e825a78b299c37..7e5c6affbeb3fd 100644
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/GroovyGradleBuildFilesCreator.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/GroovyGradleBuildFilesCreator.java
@@ -2,6 +2,7 @@
 
 import io.quarkus.bootstrap.model.AppArtifactCoords;
 import io.quarkus.devtools.project.QuarkusProject;
+import io.quarkus.maven.ArtifactCoords;
 import java.io.IOException;
 
 public final class GroovyGradleBuildFilesCreator extends AbstractGradleBuildFilesCreator {
@@ -30,8 +31,8 @@ void createBuildContent(String groupId, String version) throws IOException {
             res.append(System.lineSeparator()).append("    id 'io.quarkus'").append(System.lineSeparator());
             res.append("}");
         }
-        if (!containsBOM(getQuarkusProject().getPlatformDescriptor().getBomGroupId(),
-                getQuarkusProject().getPlatformDescriptor().getBomArtifactId())) {
+        final ArtifactCoords bom = getQuarkusProject().getExtensionsCatalog().getBom();
+        if (!containsBOM(bom.getGroupId(), bom.getArtifactId())) {
             res.append(System.lineSeparator());
             res.append("dependencies {").append(System.lineSeparator());
             res.append(
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/KotlinGradleBuildFilesCreator.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/KotlinGradleBuildFilesCreator.java
index 679a9d27927451..7715483cd94744 100644
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/KotlinGradleBuildFilesCreator.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/KotlinGradleBuildFilesCreator.java
@@ -2,22 +2,27 @@
 
 import io.quarkus.bootstrap.model.AppArtifactCoords;
 import io.quarkus.devtools.project.QuarkusProject;
+import io.quarkus.devtools.project.buildfile.AbstractGradleBuildFile.Model;
+import io.quarkus.maven.ArtifactCoords;
 import java.io.IOException;
 
 public class KotlinGradleBuildFilesCreator extends AbstractGradleBuildFilesCreator {
 
+    static final String BUILD_GRADLE_PATH = "build.gradle.kts";
+    static final String SETTINGS_GRADLE_PATH = "settings.gradle.kts";
+
     public KotlinGradleBuildFilesCreator(QuarkusProject quarkusProject) {
         super(quarkusProject);
     }
 
     @Override
     String getSettingsGradlePath() {
-        return AbstractKotlinGradleBuildFile.SETTINGS_GRADLE_PATH;
+        return SETTINGS_GRADLE_PATH;
     }
 
     @Override
     String getBuildGradlePath() {
-        return AbstractKotlinGradleBuildFile.BUILD_GRADLE_PATH;
+        return BUILD_GRADLE_PATH;
     }
 
     @Override
@@ -30,8 +35,8 @@ void createBuildContent(String groupId, String version) throws IOException {
             res.append(System.lineSeparator()).append("    id(\"io.quarkus\")").append(System.lineSeparator());
             res.append("}");
         }
-        if (!containsBOM(getQuarkusProject().getPlatformDescriptor().getBomGroupId(),
-                getQuarkusProject().getPlatformDescriptor().getBomArtifactId())) {
+        final ArtifactCoords bom = getQuarkusProject().getExtensionsCatalog().getBom();
+        if (!containsBOM(bom.getGroupId(), bom.getArtifactId())) {
             res.append(System.lineSeparator());
             res.append("dependencies {").append(System.lineSeparator());
             res.append(
@@ -91,6 +96,12 @@ void createSettingsContent(String artifactId) throws IOException {
 
     @Override
     void addDependencyInBuildFile(AppArtifactCoords coords) throws IOException {
-        AbstractKotlinGradleBuildFile.addDependencyInModel(getModel(), coords, false);
+        addDependencyInModel(getModel(), coords, false);
+    }
+
+    static boolean addDependencyInModel(Model model, AppArtifactCoords coords, boolean managed) {
+        return AbstractGradleBuildFile.addDependencyInModel(model,
+                String.format("    implementation(%s)%n",
+                        AbstractGradleBuildFile.createDependencyCoordinatesString(coords, managed, '"')));
     }
 }
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/MavenBuildFile.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/MavenBuildFile.java
index 9a2d8b4828ddfa..06fe843d92377b 100644
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/MavenBuildFile.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/MavenBuildFile.java
@@ -7,7 +7,7 @@
 import io.quarkus.devtools.project.BuildTool;
 import io.quarkus.devtools.project.extensions.Extensions;
 import io.quarkus.maven.utilities.MojoUtils;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
+import io.quarkus.registry.catalog.ExtensionCatalog;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -30,8 +30,8 @@ public class MavenBuildFile extends BuildFile {
 
     private final AtomicReference<Model> modelRef = new AtomicReference<>();
 
-    public MavenBuildFile(final Path projectDirPath, final QuarkusPlatformDescriptor platformDescriptor) {
-        super(projectDirPath, platformDescriptor);
+    public MavenBuildFile(final Path projectDirPath, ExtensionCatalog catalog) {
+        super(projectDirPath, catalog);
     }
 
     @Override
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/codegen/ProjectGeneratorRegistry.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/codegen/ProjectGeneratorRegistry.java
deleted file mode 100644
index 4090c600ef7b54..00000000000000
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/codegen/ProjectGeneratorRegistry.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package io.quarkus.devtools.project.codegen;
-
-import java.util.Map;
-import java.util.NoSuchElementException;
-import java.util.ServiceLoader;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * @author <a href="claprun@redhat.com">Christophe Laprun</a>
- */
-public class ProjectGeneratorRegistry {
-
-    private static final Map<String, ProjectGenerator> generators = new ConcurrentHashMap<>(7);
-    private static final ProjectGeneratorRegistry INSTANCE = new ProjectGeneratorRegistry();
-
-    private ProjectGeneratorRegistry() {
-        loadGenerators();
-    }
-
-    public static ProjectGeneratorRegistry getInstance() {
-        return INSTANCE;
-    }
-
-    public static ProjectGenerator get(String name) throws NoSuchElementException {
-        final ProjectGenerator generator = generators.get(name);
-        if (generator == null) {
-            throw new NoSuchElementException("Unknown generator: " + name);
-        }
-
-        return generator;
-    }
-
-    private static void register(ProjectGenerator generator) {
-        if (generator != null) {
-            generators.put(generator.getName(), generator);
-        } else {
-            throw new NullPointerException("Cannot register null generator");
-        }
-    }
-
-    private static void loadGenerators() {
-        ServiceLoader<ProjectGenerator> serviceLoader = ServiceLoader.load(ProjectGenerator.class);
-        serviceLoader.iterator().forEachRemaining(ProjectGeneratorRegistry::register);
-    }
-}
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/codegen/rest/BasicRestProjectGenerator.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/codegen/rest/BasicRestProjectGenerator.java
deleted file mode 100644
index bcd21bc2f7b2fa..00000000000000
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/codegen/rest/BasicRestProjectGenerator.java
+++ /dev/null
@@ -1,277 +0,0 @@
-package io.quarkus.devtools.project.codegen.rest;
-
-import static java.lang.String.format;
-
-import io.quarkus.devtools.commands.data.QuarkusCommandInvocation;
-import io.quarkus.devtools.project.BuildTool;
-import io.quarkus.devtools.project.codegen.ProjectGenerator;
-import io.quarkus.devtools.project.codegen.SourceType;
-import io.quarkus.devtools.project.codegen.writer.FileProjectWriter;
-import io.quarkus.devtools.project.codegen.writer.ProjectWriter;
-import io.quarkus.maven.utilities.MojoUtils;
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.util.Map.Entry;
-
-public class BasicRestProjectGenerator implements ProjectGenerator {
-
-    public static final String NAME = "basic-rest";
-
-    public BasicRestProjectGenerator() {
-    }
-
-    @Override
-    public String getName() {
-        return NAME;
-    }
-
-    @Override
-    public void generate(QuarkusCommandInvocation invocation) throws IOException {
-        final FileProjectWriter writer = new FileProjectWriter(invocation.getQuarkusProject().getProjectDirPath().toFile());
-        writer.init();
-        generate(writer, invocation);
-    }
-
-    void generate(ProjectWriter writer, QuarkusCommandInvocation invocation) throws IOException {
-        final BasicRestProject project = new BasicRestProject(writer, invocation);
-
-        project.initProject();
-        project.setupContext();
-
-        project.createClasses();
-
-        project.createIndexPage();
-        project.createDockerFiles();
-        project.createDockerIgnore();
-        project.createApplicationConfig();
-        project.createReadme();
-
-        project.createGitIgnore();
-    }
-
-    private class BasicRestProject {
-        private QuarkusCommandInvocation invocation;
-        private String path = "/hello";
-        private ProjectWriter writer;
-        private String srcMainPath;
-        private String testMainPath;
-        private String nativeTestMainPath;
-        private SourceType type;
-
-        private BasicRestProject(final ProjectWriter writer, QuarkusCommandInvocation invocation) {
-            this.writer = writer;
-            this.invocation = invocation;
-            this.type = invocation.getValue(SOURCE_TYPE, SourceType.JAVA);
-        }
-
-        @SuppressWarnings("unchecked")
-        private <T> T get(final String key, final T defaultValue) {
-            return invocation.getValue(key, defaultValue);
-        }
-
-        private boolean initProject() throws IOException {
-            boolean newProject = initBuildTool();
-
-            path = get(RESOURCE_PATH, path);
-
-            srcMainPath = writer.mkdirs(type.getSrcDir());
-            testMainPath = writer.mkdirs(type.getTestSrcDir());
-            // for gradle we want to place the native tests in under 'src/native-test/java'
-            // since gradle's idiomatic way of running integration tests is to create a new source set
-            switch (getBuildTool()) {
-                case GRADLE:
-                case GRADLE_KOTLIN_DSL:
-                    nativeTestMainPath = writer.mkdirs(type.getTestSrcDir().replace("test", "native-test"));
-                    break;
-                default:
-                    nativeTestMainPath = testMainPath;
-            }
-            return newProject;
-        }
-
-        private boolean initBuildTool() throws IOException {
-            BuildTool buildTool = getBuildTool();
-            if (!invocation.hasValue(ADDITIONAL_GITIGNORE_ENTRIES)) {
-                invocation.setValue(ADDITIONAL_GITIGNORE_ENTRIES, buildTool.getGitIgnoreEntries());
-            }
-            if (!invocation.hasValue(BUILD_DIRECTORY)) {
-                invocation.setValue(BUILD_DIRECTORY, buildTool.getBuildDirectory());
-            }
-            if (!invocation.hasValue(MAVEN_REPOSITORIES)) {
-                invocation.setValue(MAVEN_REPOSITORIES, "");
-            }
-            if (!invocation.hasValue(MAVEN_PLUGIN_REPOSITORIES)) {
-                invocation.setValue(MAVEN_PLUGIN_REPOSITORIES, "");
-            }
-
-            boolean newProject = !writer.exists(buildTool.getDependenciesFile());
-            if (newProject) {
-                for (String buildFile : buildTool.getBuildFiles()) {
-                    generate(type.getBuildFileResourceTemplate(getName(), buildFile), invocation, buildFile, buildFile);
-                }
-            } else {
-                String[] gav;
-                if (BuildTool.MAVEN.equals(buildTool)) {
-                    ByteArrayInputStream buildFileInputStream = new ByteArrayInputStream(
-                            writer.getContent(buildTool.getDependenciesFile()));
-                    gav = MojoUtils.readGavFromPom(buildFileInputStream);
-                } else {
-                    gav = new String[3];
-                    for (String buildFile : buildTool.getBuildFiles()) {
-                        if (writer.exists(buildFile)) {
-                            try (ByteArrayInputStream buildFileInputStream = new ByteArrayInputStream(
-                                    writer.getContent(buildFile))) {
-                                gav = MojoUtils.readGavFromSettingsGradle(buildFileInputStream, gav);
-                            }
-                        }
-                    }
-                }
-                if (gav[0] != null) {
-                    invocation.setValue(PROJECT_GROUP_ID, gav[0]);
-                }
-                if (gav[1] != null) {
-                    invocation.setValue(PROJECT_ARTIFACT_ID, gav[1]);
-                }
-            }
-            return newProject;
-        }
-
-        private BuildTool getBuildTool() {
-            return invocation.getQuarkusProject().getBuildTool();
-        }
-
-        private void generate(final String templateName, QuarkusCommandInvocation invocation, final String outputFilePath,
-                final String resourceType)
-                throws IOException {
-            if (!writer.exists(outputFilePath)) {
-                String template = invocation.getPlatformDescriptor().getTemplate(templateName);
-                if (template == null) {
-                    throw new IOException("Template resource is missing: " + templateName);
-                }
-                for (Entry<String, Object> e : invocation.getValues().entrySet()) {
-                    if (e.getValue() instanceof String) { // Exclude null values (classname and path can be null)
-                        template = template.replace(format("${%s}", e.getKey()), (String) e.getValue());
-                    }
-                }
-
-                // do some nasty replacements for Java target if we want to generate Java 11 projects
-                if ("11".equals(invocation.getValue(JAVA_TARGET))) {
-                    if (BuildTool.GRADLE.equals(invocation.getQuarkusProject().getBuildTool())
-                            || BuildTool.GRADLE_KOTLIN_DSL.equals(invocation.getQuarkusProject().getBuildTool())) {
-                        template = template.replace("JavaVersion.VERSION_1_8", "JavaVersion.VERSION_11");
-                    } else {
-                        template = template.replace("<maven.compiler.source>1.8</maven.compiler.source>",
-                                "<maven.compiler.source>11</maven.compiler.source>");
-                        template = template.replace("<maven.compiler.target>1.8</maven.compiler.target>",
-                                "<maven.compiler.target>11</maven.compiler.target>");
-                        // Kotlin
-                        template = template.replace("<jvmTarget>1.8</jvmTarget>", "<jvmTarget>11</jvmTarget>");
-                        // Scala
-                        // For now, we keep Java 8 as a target for Scala as we don't want to upgrade to 2.13
-                        // template = template.replace("<arg>-target:jvm-1.8</arg>", "<arg>-target:jvm-11</arg>");
-                    }
-                }
-
-                writer.write(outputFilePath, template);
-            }
-        }
-
-        private void createIndexPage() throws IOException {
-            // Generate index page
-            String resources = "src/main/resources/META-INF/resources";
-            String index = writer.mkdirs(resources) + "/index.html";
-            if (!writer.exists(index)) {
-                generate("templates/index.ftl", invocation, index, "welcome page");
-            }
-
-        }
-
-        private void createDockerFiles() throws IOException {
-            String dockerRoot = "src/main/docker";
-            String dockerRootDir = writer.mkdirs(dockerRoot);
-            generate("templates/dockerfile-native.ftl", invocation, dockerRootDir + "/Dockerfile.native",
-                    "native docker file");
-            generate("templates/dockerfile-jvm.ftl", invocation, dockerRootDir + "/Dockerfile.jvm", "jvm docker file");
-            generate("templates/dockerfile-legacy-jar.ftl", invocation, dockerRootDir + "/Dockerfile.legacy-jar",
-                    "jvm docker file");
-        }
-
-        private void createReadme() throws IOException {
-            String readme = writer.mkdirs("") + "README.md";
-            BuildTool buildTool = getBuildTool();
-            switch (buildTool) {
-                case MAVEN:
-                    generate("templates/README.maven.ftl", invocation, readme, "read me");
-                    break;
-                case GRADLE:
-                case GRADLE_KOTLIN_DSL:
-                    generate("templates/README.gradle.ftl", invocation, readme, "read me");
-                    break;
-                default:
-                    throw new IllegalStateException("buildTool is none of Maven or Gradle");
-            }
-        }
-
-        private void createDockerIgnore() throws IOException {
-            String docker = writer.mkdirs("") + ".dockerignore";
-            generate("templates/dockerignore.ftl", invocation, docker, "docker ignore");
-        }
-
-        private void createGitIgnore() throws IOException {
-            String gitignore = writer.mkdirs("") + ".gitignore";
-            generate("templates/gitignore.ftl", invocation, gitignore, "git ignore");
-        }
-
-        private void createApplicationConfig() throws IOException {
-            String meta = "src/main/resources";
-            String file = writer.mkdirs(meta) + "/application.properties";
-            if (!writer.exists(file)) {
-                writer.write(file, "# Configuration file" + System.lineSeparator() + "# key = value");
-            }
-        }
-
-        private void setupContext() throws IOException {
-            if (invocation.getValue(CLASS_NAME) != null) {
-                String packageName = invocation.getStringValue(PACKAGE_NAME);
-
-                if (packageName != null) {
-                    String packageDir = srcMainPath + '/' + packageName.replace('.', '/');
-                    String originalTestMainPath = testMainPath;
-                    String testPackageDir = testMainPath + '/' + packageName.replace('.', '/');
-                    srcMainPath = writer.mkdirs(packageDir);
-                    testMainPath = writer.mkdirs(testPackageDir);
-
-                    if (!originalTestMainPath.equals(nativeTestMainPath)) {
-                        String nativeTestPackageDir = nativeTestMainPath + '/' + packageName.replace('.', '/');
-                        nativeTestMainPath = writer.mkdirs(nativeTestPackageDir);
-                    } else {
-                        nativeTestMainPath = testMainPath;
-                    }
-
-                } else {
-                    throw new NullPointerException("Need a non-null package name");
-                }
-            }
-        }
-
-        private void createClasses() throws IOException {
-            Object className = invocation.getValue(CLASS_NAME);
-            // If className is null we disable the generation of the JAX-RS resource.
-            if (className != null) {
-                String extension = type.getExtension();
-                String classFile = srcMainPath + '/' + className + extension;
-                String testClassFile = testMainPath + '/' + className + "Test" + extension;
-                String itTestClassFile = nativeTestMainPath + '/' + "Native" + className + "IT" + extension;
-                String name = getName();
-                String srcResourceTemplate = type.getSrcResourceTemplate(name);
-                Object isSpring = invocation.getValue(IS_SPRING);
-                if (isSpring != null && (Boolean) invocation.getValue(IS_SPRING).equals(Boolean.TRUE)) {
-                    srcResourceTemplate = type.getSrcSpringControllerTemplate(name);
-                }
-                generate(srcResourceTemplate, invocation, classFile, "resource code");
-                generate(type.getTestResourceTemplate(name), invocation, testClassFile, "test code");
-                generate(type.getNativeTestResourceTemplate(name), invocation, itTestClassFile, "IT code");
-            }
-        }
-    }
-}
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/extensions/ExtensionManager.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/extensions/ExtensionManager.java
index 6f0b7001944645..77148ae77bcaca 100644
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/extensions/ExtensionManager.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/extensions/ExtensionManager.java
@@ -2,7 +2,6 @@
 
 import io.quarkus.bootstrap.model.AppArtifactCoords;
 import io.quarkus.bootstrap.model.AppArtifactKey;
-import io.quarkus.dependencies.Extension;
 import io.quarkus.devtools.project.BuildTool;
 import java.io.IOException;
 import java.util.Collection;
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/extensions/Extensions.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/extensions/Extensions.java
index b6039f608c3aaa..f6643cd4d9f331 100644
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/extensions/Extensions.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/extensions/Extensions.java
@@ -2,8 +2,8 @@
 
 import io.quarkus.bootstrap.model.AppArtifactCoords;
 import io.quarkus.bootstrap.model.AppArtifactKey;
-import io.quarkus.dependencies.Extension;
-import java.util.List;
+import io.quarkus.registry.catalog.Extension;
+import java.util.Collection;
 import java.util.Objects;
 import java.util.Optional;
 import org.apache.maven.model.Dependency;
@@ -13,7 +13,10 @@ private Extensions() {
     }
 
     public static AppArtifactKey toKey(final Extension extension) {
-        return toKey(extension.toDependency(false));
+        return new AppArtifactKey(extension.getArtifact().getGroupId(),
+                extension.getArtifact().getArtifactId(),
+                extension.getArtifact().getClassifier(),
+                extension.getArtifact().getType());
     }
 
     public static AppArtifactKey toKey(final Dependency dependency) {
@@ -21,7 +24,7 @@ public static AppArtifactKey toKey(final Dependency dependency) {
                 dependency.getType());
     }
 
-    public static Optional<Extension> findInList(List<Extension> list, final AppArtifactKey key) {
+    public static Optional<Extension> findInList(Collection<Extension> list, final AppArtifactKey key) {
         return list.stream().filter(e -> Objects.equals(toCoords(e).getKey(), key)).findFirst();
     }
 
@@ -30,7 +33,11 @@ public static AppArtifactCoords toCoords(final AppArtifactKey k, final String ve
     }
 
     public static AppArtifactCoords toCoords(final Extension e) {
-        return toCoords(e.toDependency(false));
+        return new AppArtifactCoords(e.getArtifact().getGroupId(),
+                e.getArtifact().getArtifactId(),
+                e.getArtifact().getClassifier(),
+                e.getArtifact().getType(),
+                e.getArtifact().getVersion());
     }
 
     public static AppArtifactCoords toCoords(final Dependency d, final String overrideVersion) {
@@ -53,7 +60,7 @@ public static String toGA(AppArtifactKey c) {
     }
 
     public static String toGA(Extension e) {
-        return e.getGroupId() + ":" + e.getArtifactId();
+        return e.getArtifact().getGroupId() + ":" + e.getArtifact().getArtifactId();
     }
 
     public static AppArtifactCoords stripVersion(final AppArtifactCoords coords) {
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/ProjectPlatformDescriptorJsonUtil.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/ProjectPlatformDescriptorJsonUtil.java
new file mode 100644
index 00000000000000..2dafb527957a7c
--- /dev/null
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/ProjectPlatformDescriptorJsonUtil.java
@@ -0,0 +1,107 @@
+package io.quarkus.platform.descriptor;
+
+import io.quarkus.bootstrap.BootstrapConstants;
+import io.quarkus.bootstrap.model.AppArtifact;
+import io.quarkus.bootstrap.model.AppArtifactCoords;
+import io.quarkus.bootstrap.model.AppArtifactKey;
+import io.quarkus.bootstrap.resolver.AppModelResolver;
+import io.quarkus.bootstrap.resolver.AppModelResolverException;
+import io.quarkus.maven.ArtifactKey;
+import io.quarkus.registry.catalog.Category;
+import io.quarkus.registry.catalog.Extension;
+import io.quarkus.registry.catalog.ExtensionOrigin;
+import io.quarkus.registry.catalog.json.JsonCatalogMapperHelper;
+import io.quarkus.registry.catalog.json.JsonExtensionCatalog;
+import io.quarkus.registry.catalog.json.JsonExtensionOrigin;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class ProjectPlatformDescriptorJsonUtil {
+
+    public static JsonExtensionCatalog resolveCatalog(AppModelResolver resolver, List<AppArtifact> depConstraints)
+            throws AppModelResolverException {
+        final List<JsonExtensionCatalog> platforms = new ArrayList<>(2);
+        final Set<AppArtifactKey> processedPlatforms = new HashSet<>();
+        for (int i = depConstraints.size() - 1; i >= 0; --i) {
+            final AppArtifact artifact = depConstraints.get(i);
+            if (!artifact.getArtifactId().endsWith(BootstrapConstants.PLATFORM_DESCRIPTOR_ARTIFACT_ID_SUFFIX)
+                    && !artifact.getType().equals("json")) {
+                continue;
+            }
+            if (!processedPlatforms.add(artifact.getKey())) {
+                continue;
+            }
+            final Path json = resolver.resolve(artifact);
+            final JsonExtensionCatalog platform;
+            try {
+                platform = JsonCatalogMapperHelper.deserialize(json, JsonExtensionCatalog.class);
+            } catch (IOException e) {
+                throw new AppModelResolverException("Failed to deserialize a platform descriptor from " + json, e);
+            }
+            platform.getDerivedFrom().forEach(o -> processedPlatforms.add(AppArtifactCoords.fromString(o.getId()).getKey()));
+            platforms.add(platform);
+        }
+        if (platforms.isEmpty()) {
+            return null;
+        }
+
+        final List<ExtensionOrigin> derivedFrom = new ArrayList<>();
+        final List<Extension> extensions = new ArrayList<>();
+        final Set<ArtifactKey> extensionKeys = new HashSet<>();
+        final List<Category> categories = new ArrayList<>();
+        final Set<String> categoryIds = new HashSet<>();
+        final Map<String, Object> metadata = new HashMap<>();
+
+        final JsonExtensionCatalog catalog = new JsonExtensionCatalog();
+        catalog.setPlatform(false);
+        catalog.setDerivedFrom(derivedFrom);
+        catalog.setExtensions(extensions);
+        catalog.setCategories(categories);
+        catalog.setMetadata(metadata);
+
+        final JsonExtensionCatalog dominatingPlatform = platforms.get(0);
+        catalog.setQuarkusCoreVersion(dominatingPlatform.getQuarkusCoreVersion());
+        catalog.setUpstreamQuarkusCoreVersion(dominatingPlatform.getUpstreamQuarkusCoreVersion());
+
+        for (int i = platforms.size() - 1; i >= 0; --i) {
+            final JsonExtensionCatalog platform = platforms.get(i);
+            if (platform.getBom() != null) {
+                catalog.setBom(platform.getBom());
+            }
+            final JsonExtensionOrigin origin = new JsonExtensionOrigin();
+            origin.setId(platform.getId());
+            origin.setPlatform(platform.isPlatform());
+            origin.setBom(platform.getBom());
+            derivedFrom.add(origin);
+
+            for (Extension e : platform.getExtensions()) {
+                if (extensionKeys.add(e.getArtifact().getKey())) {
+                    extensions.add(e);
+                }
+            }
+
+            if (platform.getCategories().isEmpty()) {
+                for (Category c : platform.getCategories()) {
+                    if (categoryIds.add(c.getId())) {
+                        categories.add(c);
+                    }
+                }
+            }
+
+            if (!platform.getMetadata().isEmpty()) {
+                for (Map.Entry<String, Object> entry : platform.getMetadata().entrySet()) {
+                    if (!metadata.containsKey(entry.getKey())) {
+                        metadata.put(entry.getKey(), entry.getValue());
+                    }
+                }
+            }
+        }
+        return catalog;
+    }
+}
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/ArtifactResolver.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/ArtifactResolver.java
deleted file mode 100644
index 34a20cb09b028c..00000000000000
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/ArtifactResolver.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package io.quarkus.platform.descriptor.loader.json;
-
-import io.quarkus.bootstrap.resolver.AppModelResolverException;
-import java.nio.file.Path;
-import java.util.function.Function;
-
-public interface ArtifactResolver {
-
-    default <T> T process(String groupId, String artifactId, String version, Function<Path, T> processor)
-            throws AppModelResolverException {
-        return process(groupId, artifactId, null, "jar", version, processor);
-    }
-
-    <T> T process(String groupId, String artifactId, String classifier, String type, String version,
-            Function<Path, T> processor) throws AppModelResolverException;
-}
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/ClassPathResourceLoader.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/ClassPathResourceLoader.java
index 08e95c9f035202..7746ffc19c3362 100644
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/ClassPathResourceLoader.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/ClassPathResourceLoader.java
@@ -1,7 +1,5 @@
 package io.quarkus.platform.descriptor.loader.json;
 
-import io.quarkus.platform.descriptor.ResourceInputStreamConsumer;
-import io.quarkus.platform.descriptor.ResourcePathConsumer;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.UncheckedIOException;
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/DirectoryResourceLoader.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/DirectoryResourceLoader.java
index cbadf703b792be..e59700228f3f13 100644
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/DirectoryResourceLoader.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/DirectoryResourceLoader.java
@@ -1,6 +1,5 @@
 package io.quarkus.platform.descriptor.loader.json;
 
-import io.quarkus.platform.descriptor.ResourcePathConsumer;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/QuarkusJsonPlatformDescriptorLoader.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/QuarkusJsonPlatformDescriptorLoader.java
deleted file mode 100644
index 812f31dc1c185b..00000000000000
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/QuarkusJsonPlatformDescriptorLoader.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package io.quarkus.platform.descriptor.loader.json;
-
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
-import io.quarkus.platform.descriptor.loader.QuarkusPlatformDescriptorLoader;
-
-public interface QuarkusJsonPlatformDescriptorLoader<D extends QuarkusPlatformDescriptor>
-        extends QuarkusPlatformDescriptorLoader<D, QuarkusJsonPlatformDescriptorLoaderContext> {
-
-}
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/QuarkusJsonPlatformDescriptorLoaderContext.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/QuarkusJsonPlatformDescriptorLoaderContext.java
deleted file mode 100644
index 56e316c529bf33..00000000000000
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/QuarkusJsonPlatformDescriptorLoaderContext.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package io.quarkus.platform.descriptor.loader.json;
-
-import io.quarkus.devtools.messagewriter.MessageWriter;
-import io.quarkus.platform.descriptor.loader.QuarkusPlatformDescriptorLoaderContext;
-import java.io.InputStream;
-import java.util.function.Function;
-
-public abstract class QuarkusJsonPlatformDescriptorLoaderContext implements QuarkusPlatformDescriptorLoaderContext {
-
-    protected final MessageWriter log;
-    protected final ArtifactResolver artifactResolver;
-    protected final ResourceLoader resourceLoader;
-
-    public QuarkusJsonPlatformDescriptorLoaderContext(ArtifactResolver artifactResolver) {
-        this(artifactResolver, MessageWriter.info());
-    }
-
-    public QuarkusJsonPlatformDescriptorLoaderContext(ArtifactResolver artifactResolver, MessageWriter log) {
-        this(artifactResolver, new ClassPathResourceLoader(Thread.currentThread().getContextClassLoader()), log);
-    }
-
-    public QuarkusJsonPlatformDescriptorLoaderContext(ArtifactResolver artifactResolver, ResourceLoader resourceLoader,
-            MessageWriter log) {
-        this.log = log;
-        this.artifactResolver = artifactResolver;
-        this.resourceLoader = resourceLoader;
-    }
-
-    public abstract <T> T parseJson(Function<InputStream, T> parser);
-
-    @Override
-    public MessageWriter getMessageWriter() {
-        return log;
-    }
-
-    public ArtifactResolver getArtifactResolver() {
-        return artifactResolver;
-    }
-
-    public ResourceLoader getResourceLoader() {
-        return resourceLoader;
-    }
-}
diff --git a/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/platform/descriptor/ResourceInputStreamConsumer.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/ResourceInputStreamConsumer.java
similarity index 75%
rename from independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/platform/descriptor/ResourceInputStreamConsumer.java
rename to independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/ResourceInputStreamConsumer.java
index 4898146a6fa52f..5bd29649a6d58c 100644
--- a/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/platform/descriptor/ResourceInputStreamConsumer.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/ResourceInputStreamConsumer.java
@@ -1,4 +1,4 @@
-package io.quarkus.platform.descriptor;
+package io.quarkus.platform.descriptor.loader.json;
 
 import java.io.IOException;
 import java.io.InputStream;
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/ResourceLoader.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/ResourceLoader.java
index eca2fa1a54d4b3..910ae860ea8747 100644
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/ResourceLoader.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/ResourceLoader.java
@@ -1,7 +1,5 @@
 package io.quarkus.platform.descriptor.loader.json;
 
-import io.quarkus.platform.descriptor.ResourceInputStreamConsumer;
-import io.quarkus.platform.descriptor.ResourcePathConsumer;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.file.Files;
diff --git a/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/platform/descriptor/ResourcePathConsumer.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/ResourcePathConsumer.java
similarity index 73%
rename from independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/platform/descriptor/ResourcePathConsumer.java
rename to independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/ResourcePathConsumer.java
index 88ccd84c238583..cacb282ffbcc14 100644
--- a/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/platform/descriptor/ResourcePathConsumer.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/ResourcePathConsumer.java
@@ -1,4 +1,4 @@
-package io.quarkus.platform.descriptor;
+package io.quarkus.platform.descriptor.loader.json;
 
 import java.io.IOException;
 import java.nio.file.Path;
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/ZipResourceLoader.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/ZipResourceLoader.java
index af60f9d9226f7c..8c16537ada6483 100644
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/ZipResourceLoader.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/ZipResourceLoader.java
@@ -1,6 +1,5 @@
 package io.quarkus.platform.descriptor.loader.json;
 
-import io.quarkus.platform.descriptor.ResourcePathConsumer;
 import java.io.IOException;
 import java.nio.file.FileSystem;
 import java.nio.file.FileSystems;
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/tools/ToolsUtils.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/tools/ToolsUtils.java
index a5c3127293450a..82a8c02a45aee8 100644
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/tools/ToolsUtils.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/tools/ToolsUtils.java
@@ -1,8 +1,25 @@
 package io.quarkus.platform.tools;
 
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
+import io.quarkus.bootstrap.BootstrapConstants;
+import io.quarkus.bootstrap.model.AppArtifact;
+import io.quarkus.bootstrap.resolver.AppModelResolver;
+import io.quarkus.bootstrap.resolver.BootstrapAppModelResolver;
+import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver;
+import io.quarkus.devtools.messagewriter.MessageWriter;
+import io.quarkus.maven.ArtifactCoords;
+import io.quarkus.registry.catalog.ExtensionCatalog;
+import io.quarkus.registry.catalog.json.JsonCatalogMapperHelper;
+import io.quarkus.registry.catalog.json.JsonCatalogMerger;
+import io.quarkus.registry.catalog.json.JsonExtensionCatalog;
 import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
 import java.util.Properties;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.artifact.DefaultArtifact;
 
 public class ToolsUtils {
 
@@ -42,17 +59,76 @@ public static String dotJoin(String... parts) {
         return buf.toString();
     }
 
-    public static Properties readQuarkusProperties(QuarkusPlatformDescriptor platformDescr) {
-        final Properties properties;
+    public static ExtensionCatalog resolvePlatformDescriptorDirectly(String bomGroupId, String bomArtifactId, String bomVersion,
+            MavenArtifactResolver artifactResolver, MessageWriter log) {
+        // TODO remove this method once we have the registry service available
+        if (bomVersion == null) {
+            throw new IllegalArgumentException("BOM version was not provided");
+        }
+        Artifact catalogCoords = new DefaultArtifact(
+                bomGroupId == null ? "io.quarkus" : bomGroupId,
+                (bomArtifactId == null ? "quarkus-universe-bom" : bomArtifactId)
+                        + BootstrapConstants.PLATFORM_DESCRIPTOR_ARTIFACT_ID_SUFFIX,
+                bomVersion, "json", bomVersion);
+        Path platformJson = null;
+        try {
+            log.debug("Resolving platform descriptor %s", catalogCoords);
+            platformJson = artifactResolver.resolve(catalogCoords).getArtifact().getFile().toPath();
+        } catch (Exception e) {
+            if (bomArtifactId == null && catalogCoords.getArtifactId().startsWith("quarkus-universe-bom")) {
+                catalogCoords = new DefaultArtifact(
+                        catalogCoords.getGroupId(),
+                        "quarkus-bom" + BootstrapConstants.PLATFORM_DESCRIPTOR_ARTIFACT_ID_SUFFIX,
+                        catalogCoords.getClassifier(), catalogCoords.getExtension(), catalogCoords.getVersion());
+                try {
+                    log.debug("Resolving platform descriptor %s", catalogCoords);
+                    platformJson = artifactResolver.resolve(catalogCoords).getArtifact().getFile().toPath();
+                } catch (Exception e2) {
+                }
+            }
+            if (platformJson == null) {
+                throw new RuntimeException("Failed to resolve the default platform JSON descriptor", e);
+            }
+        }
         try {
-            properties = platformDescr.loadResource("quarkus.properties", is -> {
-                final Properties props = new Properties();
-                props.load(is);
-                return props;
-            });
+            return JsonCatalogMapperHelper.deserialize(platformJson, JsonExtensionCatalog.class);
         } catch (IOException e) {
-            throw new IllegalStateException("Failed to read quarkus.properties", e);
+            throw new RuntimeException("Failed to deserialize extension catalog " + platformJson, e);
+        }
+    }
+
+    public static ExtensionCatalog mergePlatforms(List<ArtifactCoords> platforms, MavenArtifactResolver artifactResolver) {
+        // TODO remove this method once we have the registry service available
+        return mergePlatforms(platforms, new BootstrapAppModelResolver(artifactResolver));
+    }
+
+    public static ExtensionCatalog mergePlatforms(List<ArtifactCoords> platforms, AppModelResolver artifactResolver) {
+        // TODO remove this method once we have the registry service available
+        List<ExtensionCatalog> catalogs = new ArrayList<>(platforms.size());
+        for (ArtifactCoords platform : platforms) {
+            final Path json;
+            try {
+                json = artifactResolver.resolve(new AppArtifact(platform.getGroupId(), platform.getArtifactId(),
+                        platform.getClassifier(), platform.getType(), platform.getVersion()));
+            } catch (Exception e) {
+                throw new RuntimeException("Failed to resolve platform descriptor " + platform, e);
+            }
+            try {
+                catalogs.add(JsonCatalogMapperHelper.deserialize(json, JsonExtensionCatalog.class));
+            } catch (IOException e) {
+                throw new RuntimeException("Failed to deserialize platform descriptor " + json, e);
+            }
         }
+        return JsonCatalogMerger.merge(catalogs);
+    }
+
+    @SuppressWarnings("unchecked")
+    public static Properties readQuarkusProperties(ExtensionCatalog catalog) {
+        Map<Object, Object> map = (Map<Object, Object>) catalog.getMetadata().getOrDefault("project", Collections.emptyMap());
+        map = (Map<Object, Object>) map.getOrDefault("properties", Collections.emptyMap());
+        final Properties properties = new Properties();
+        map.entrySet().forEach(
+                e -> properties.setProperty(e.getKey().toString(), e.getValue() == null ? null : e.getValue().toString()));
         return properties;
     }
 
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/tools/config/QuarkusPlatformConfig.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/tools/config/QuarkusPlatformConfig.java
deleted file mode 100644
index 30517d22ddc31b..00000000000000
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/tools/config/QuarkusPlatformConfig.java
+++ /dev/null
@@ -1,175 +0,0 @@
-package io.quarkus.platform.tools.config;
-
-import io.quarkus.devtools.messagewriter.MessageWriter;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
-import io.quarkus.platform.descriptor.loader.QuarkusPlatformDescriptorLoader;
-import io.quarkus.platform.descriptor.loader.QuarkusPlatformDescriptorLoaderContext;
-import java.util.Iterator;
-import java.util.ServiceLoader;
-import java.util.concurrent.atomic.AtomicReference;
-
-public class QuarkusPlatformConfig {
-
-    public static class Builder {
-
-        private int type = STANDALONE;
-        private MessageWriter log;
-        private QuarkusPlatformDescriptor platformDescr;
-
-        private Builder(int type) {
-            if (type == GLOBAL) {
-                assertNoGlobalConfig();
-            } else if (type == THREAD_LOCAL) {
-                assertNoThreadLocalConfig();
-            }
-            this.type = type;
-        }
-
-        public Builder setMessageWriter(MessageWriter msgWriter) {
-            this.log = msgWriter;
-            return this;
-        }
-
-        private MessageWriter getMessageWriter() {
-            return log == null ? log = MessageWriter.info() : log;
-        }
-
-        public Builder setPlatformDescriptor(QuarkusPlatformDescriptor platformDescr) {
-            this.platformDescr = platformDescr;
-            return this;
-        }
-
-        @SuppressWarnings({ "unchecked", "rawtypes" })
-        private QuarkusPlatformDescriptor getPlatformDescriptor() {
-            if (platformDescr != null) {
-                return platformDescr;
-            }
-
-            final Iterator<QuarkusPlatformDescriptorLoader> i = ServiceLoader.load(QuarkusPlatformDescriptorLoader.class)
-                    .iterator();
-            if (!i.hasNext()) {
-                throw new IllegalStateException("Failed to locate an implementation of "
-                        + QuarkusPlatformDescriptorLoader.class.getName() + " on the classpath");
-            }
-            final QuarkusPlatformDescriptorLoader<QuarkusPlatformDescriptor, QuarkusPlatformDescriptorLoaderContext> dl = i
-                    .next();
-            if (i.hasNext()) {
-                final StringBuilder buf = new StringBuilder();
-                buf.append("Found multiple implementations of ").append(QuarkusPlatformDescriptorLoader.class.getName())
-                        .append("on the classpath: ").append(dl.getClass().getName());
-                while (i.hasNext()) {
-                    buf.append(", ").append(i.next().getClass().getName());
-                }
-                throw new IllegalStateException(buf.toString());
-            }
-            return platformDescr = dl.load(new QuarkusPlatformDescriptorLoaderContext() {
-                @Override
-                public MessageWriter getMessageWriter() {
-                    return Builder.this.getMessageWriter();
-                }
-            });
-        }
-
-        public QuarkusPlatformConfig build() {
-            return new QuarkusPlatformConfig(this);
-        }
-    }
-
-    public static Builder builder() {
-        return new Builder(STANDALONE);
-    }
-
-    /**
-     * This hopefully will be a temporary way of providing global default config
-     * by creating a builder that will create a config instance which will serve
-     * as the global default config.
-     */
-    public static Builder defaultConfigBuilder() {
-        return new Builder(GLOBAL);
-    }
-
-    /**
-     * This hopefully will be a temporary way of providing global default config
-     * by creating a builder that will create a config instance which will serve
-     * as the global default config.
-     */
-    public static Builder threadLocalConfigBuilder() {
-        return new Builder(THREAD_LOCAL);
-    }
-
-    public static QuarkusPlatformConfig newInstance() {
-        return builder().build();
-    }
-
-    public static synchronized QuarkusPlatformConfig getGlobalDefault() {
-        final QuarkusPlatformConfig c = globalConfig.get();
-        if (c != null) {
-            return c;
-        }
-        return defaultConfigBuilder().build();
-    }
-
-    public static boolean hasGlobalDefault() {
-        return globalConfig.get() != null;
-    }
-
-    public static QuarkusPlatformConfig getThreadLocal() {
-        final QuarkusPlatformConfig c = threadLocalConfig.get();
-        if (c != null) {
-            return c;
-        }
-        return threadLocalConfigBuilder().build();
-    }
-
-    public static boolean hasThreadLocal() {
-        return threadLocalConfig.get() != null;
-    }
-
-    public static void clearThreadLocal() {
-        threadLocalConfig.remove();
-    }
-
-    private static void assertNoGlobalConfig() {
-        if (globalConfig.get() != null) {
-            throw new IllegalStateException(
-                    "The global instance of " + QuarkusPlatformConfig.class.getName() + " has already been initialized");
-        }
-    }
-
-    private static void assertNoThreadLocalConfig() {
-        if (threadLocalConfig.get() != null) {
-            throw new IllegalStateException(
-                    "The thread local instance of " + QuarkusPlatformConfig.class.getName() + " has already been initialized");
-        }
-    }
-
-    private static final int STANDALONE = 0;
-    private static final int GLOBAL = 1;
-    private static final int THREAD_LOCAL = 2;
-
-    private static final AtomicReference<QuarkusPlatformConfig> globalConfig = new AtomicReference<>();
-    private static final ThreadLocal<QuarkusPlatformConfig> threadLocalConfig = new ThreadLocal<>();
-
-    private final MessageWriter log;
-    private final QuarkusPlatformDescriptor platformDescr;
-
-    private QuarkusPlatformConfig(Builder builder) {
-        this.log = builder.getMessageWriter();
-        this.platformDescr = builder.getPlatformDescriptor();
-        if (builder.type == GLOBAL) {
-            assertNoGlobalConfig();
-            globalConfig.set(this);
-        } else if (builder.type == THREAD_LOCAL) {
-            assertNoThreadLocalConfig();
-            threadLocalConfig.set(this);
-        }
-    }
-
-    public MessageWriter getMessageWriter() {
-        return log;
-    }
-
-    public QuarkusPlatformDescriptor getPlatformDescriptor() {
-        return platformDescr;
-    }
-}
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/DefaultExtensionRegistry.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/DefaultExtensionRegistry.java
deleted file mode 100644
index 7b2aff278e8a7f..00000000000000
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/DefaultExtensionRegistry.java
+++ /dev/null
@@ -1,212 +0,0 @@
-package io.quarkus.registry;
-
-import io.quarkus.bootstrap.model.AppArtifactCoords;
-import io.quarkus.bootstrap.model.AppArtifactKey;
-import io.quarkus.dependencies.Extension;
-import io.quarkus.dependencies.ExtensionPredicate;
-import io.quarkus.devtools.project.extensions.ExtensionInstallPlan;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
-import io.quarkus.registry.builder.RegistryBuilder;
-import io.quarkus.registry.builder.URLRegistryBuilder;
-import io.quarkus.registry.model.ArtifactKey;
-import io.quarkus.registry.model.Extension.ExtensionRelease;
-import io.quarkus.registry.model.Registry;
-import java.io.IOException;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.stream.Collectors;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.maven.artifact.versioning.ComparableVersion;
-import org.immutables.value.Value;
-
-/**
- * This {@link ExtensionRegistry} implementation uses in-memory {@link Registry}
- * objects to query the data.
- */
-public class DefaultExtensionRegistry implements ExtensionRegistry {
-
-    private final Registry registry;
-
-    /**
-     * Create a {@link DefaultExtensionRegistry} out of a {@link Collection} of {@link URL}s
-     *
-     * @param urls urls used to lookup this registry
-     * @return a {@link DefaultExtensionRegistry} instance
-     * @throws IOException if any IO error occurs while reading each URL contents
-     */
-    public static DefaultExtensionRegistry fromURLs(Collection<URL> urls) throws IOException {
-        Registry registry = new URLRegistryBuilder()
-                .addURLs(urls)
-                .build();
-        return new DefaultExtensionRegistry(registry);
-    }
-
-    /**
-     * Create a {@link DefaultExtensionRegistry} from a single {@link QuarkusPlatformDescriptor}
-     *
-     * @param platform the single platform
-     * @return a {@link DefaultExtensionRegistry} instance
-     */
-    public static DefaultExtensionRegistry fromPlatform(QuarkusPlatformDescriptor platform) {
-        RegistryBuilder builder = new RegistryBuilder();
-        builder.visitPlatform(platform);
-        return new DefaultExtensionRegistry(builder.build());
-    }
-
-    public DefaultExtensionRegistry(Registry registry) {
-        this.registry = Objects.requireNonNull(registry, "Registry cannot be null");
-    }
-
-    @Override
-    public Set<String> getQuarkusCoreVersions() {
-        return registry.getCoreVersions().keySet().stream().map(ComparableVersion::toString).collect(
-                Collectors.toCollection(LinkedHashSet::new));
-    }
-
-    @Override
-    public Set<Extension> getExtensionsByCoreVersion(String version) {
-        return list(version, "");
-    }
-
-    @Override
-    public Set<Extension> list(String quarkusCore, String keyword) {
-        return listInternalExtensions(quarkusCore, keyword)
-                .stream()
-                .map(this::toQuarkusExtension)
-                .sorted(Comparator.comparing(Extension::getArtifactId))
-                .collect(Collectors.toCollection(LinkedHashSet::new));
-    }
-
-    @Override
-    public ExtensionInstallPlan planInstallation(String quarkusCore, Collection<String> keywords) {
-        ExtensionInstallPlan.Builder builder = ExtensionInstallPlan.builder();
-        boolean multipleKeywords = keywords.size() > 1;
-        for (String keyword : keywords) {
-            int countColons = StringUtils.countMatches(keyword, ":");
-            // Check if it's just groupId:artifactId
-            if (countColons == 1) {
-                AppArtifactKey artifactKey = AppArtifactKey.fromString(keyword);
-                builder.addManagedExtension(new AppArtifactCoords(artifactKey, null));
-                continue;
-            } else if (countColons > 1) {
-                // it's a gav
-                builder.addIndependentExtension(AppArtifactCoords.fromString(keyword));
-                continue;
-            }
-            List<ExtensionReleaseTuple> tuples = listInternalExtensions(quarkusCore, keyword);
-            if (tuples.size() != 1 && multipleKeywords) {
-                // No extension found for this keyword. Return empty immediately
-                return ExtensionInstallPlan.EMPTY;
-            }
-            // If it's a pattern allow multiple results
-            // See https://github.com/quarkusio/quarkus/issues/11086#issuecomment-666360783
-            else if (tuples.size() > 1 && !ExtensionPredicate.isPattern(keyword)) {
-                throw new MultipleExtensionsFoundException(keyword,
-                        tuples.stream().map(this::toQuarkusExtension).collect(Collectors.toList()));
-            }
-            for (ExtensionReleaseTuple tuple : tuples) {
-                ArtifactKey id = tuple.getExtension().getId();
-                String groupId = id.getGroupId();
-                String artifactId = id.getArtifactId();
-                String version = tuple.getRelease().getRelease().getVersion();
-                AppArtifactCoords extensionCoords = new AppArtifactCoords(groupId, artifactId, version);
-                List<AppArtifactCoords> platformCoords = tuple.getRelease().getPlatforms()
-                        .stream()
-                        .map(c -> new AppArtifactCoords(
-                                c.getCoords().getId().getGroupId(),
-                                c.getCoords().getId().getArtifactId(),
-                                "pom",
-                                c.getCoords().getVersion()))
-                        .collect(Collectors.toList());
-                if (platformCoords.isEmpty()) {
-                    builder.addIndependentExtension(extensionCoords);
-                } else {
-                    builder.addManagedExtension(extensionCoords);
-                    for (AppArtifactCoords platformCoord : platformCoords) {
-                        builder.addPlatform(platformCoord);
-                    }
-                }
-            }
-        }
-        return builder.build();
-    }
-
-    private List<ExtensionReleaseTuple> listInternalExtensions(String quarkusCore, String keyword) {
-        List<ExtensionReleaseTuple> result = new ArrayList<>();
-        ExtensionPredicate predicate = null;
-        if (keyword != null && !keyword.isEmpty()) {
-            predicate = new ExtensionPredicate(keyword);
-        }
-        for (io.quarkus.registry.model.Extension extension : registry.getExtensions()) {
-            for (ExtensionRelease extensionRelease : extension.getReleases()) {
-                if (quarkusCore.equals(extensionRelease.getRelease().getQuarkusCore())) {
-                    ExtensionReleaseTuple tuple = ExtensionReleaseTuple.builder().extension(extension)
-                            .release(extensionRelease).build();
-                    // If no filter is defined, just return the tuple
-                    if (predicate == null) {
-                        result.add(tuple);
-                    } else {
-                        Extension quarkusExtension = toQuarkusExtension(tuple);
-                        // If there is an exact match, return only this
-                        if (predicate.isExactMatch(quarkusExtension)) {
-                            return Collections.singletonList(tuple);
-                        } else if (predicate.test(quarkusExtension)) {
-                            result.add(tuple);
-                        }
-                    }
-                    break;
-                }
-            }
-        }
-        return result;
-    }
-
-    private Extension toQuarkusExtension(ExtensionReleaseTuple tuple) {
-        io.quarkus.registry.model.Extension extension = tuple.getExtension();
-        ExtensionRelease tupleRelease = tuple.getRelease();
-        // Platforms may have metadata overriding the extension metadata
-        Map<String, Object> metadata = new HashMap<>(extension.getMetadata());
-        tupleRelease.getPlatforms().stream()
-                .map(io.quarkus.registry.model.Extension.ExtensionPlatformRelease::getMetadata)
-                .findFirst()
-                .ifPresent(metadata::putAll);
-        ArtifactKey id = extension.getId();
-        return new Extension()
-                .setGroupId(id.getGroupId())
-                .setArtifactId(id.getArtifactId())
-                .setVersion(tupleRelease.getRelease().getVersion())
-                .setName(extension.getName())
-                .setDescription(extension.getDescription())
-                .setMetadata(metadata);
-    }
-
-    /**
-     * Used in tests
-     */
-    public Registry getRegistry() {
-        return registry;
-    }
-
-    /**
-     * This exists only because ExtensionRelease does not accept a back reference to Extension
-     */
-    @Value.Immutable
-    interface ExtensionReleaseTuple {
-        io.quarkus.registry.model.Extension getExtension();
-
-        ExtensionRelease getRelease();
-
-        static ImmutableExtensionReleaseTuple.Builder builder() {
-            return ImmutableExtensionReleaseTuple.builder();
-        }
-    }
-}
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/ExtensionRegistry.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/ExtensionRegistry.java
deleted file mode 100644
index a25dfb51729400..00000000000000
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/ExtensionRegistry.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package io.quarkus.registry;
-
-import io.quarkus.dependencies.Extension;
-import io.quarkus.devtools.project.extensions.ExtensionInstallPlan;
-import java.util.Collection;
-
-public interface ExtensionRegistry {
-
-    // Query methods
-    Collection<String> getQuarkusCoreVersions();
-
-    Collection<Extension> getExtensionsByCoreVersion(String version);
-
-    /**
-     * Return the set of extensions that matches a given keyword
-     *
-     * @param quarkusCore the quarkus core this extension supports
-     * @param keyword the keyword to search
-     * @return a set of {@link Extension} objects or an empty set if not found
-     */
-    Collection<Extension> list(String quarkusCore, String keyword);
-
-    /**
-     * What platforms and extensions do I need to add to my build descriptor?
-     * This method looks up the registry and provides a {@link ExtensionInstallPlan} containing this information.
-     *
-     * @param quarkusCore the quarkus core this extension supports
-     * @param keywords the keywords to lookup
-     * @return a {@link ExtensionInstallPlan} an object representing the necessary data to be added to a build descriptor
-     */
-    ExtensionInstallPlan planInstallation(String quarkusCore, Collection<String> keywords);
-
-}
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/RepositoryIndexer.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/RepositoryIndexer.java
deleted file mode 100644
index 000dd6dd86ab8c..00000000000000
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/RepositoryIndexer.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package io.quarkus.registry;
-
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
-import io.quarkus.registry.catalog.model.Extension;
-import io.quarkus.registry.catalog.model.Platform;
-import io.quarkus.registry.catalog.model.Repository;
-import io.quarkus.registry.catalog.spi.ArtifactResolver;
-import io.quarkus.registry.catalog.spi.IndexVisitor;
-import io.quarkus.registry.model.Release;
-import java.io.IOException;
-import java.util.Objects;
-
-/**
- * Indexes a repository
- */
-public class RepositoryIndexer {
-
-    private final ArtifactResolver artifactResolver;
-
-    public RepositoryIndexer(ArtifactResolver artifactResolver) {
-        this.artifactResolver = Objects.requireNonNull(artifactResolver, "Resolver cannot be null");
-    }
-
-    public void index(Repository repository, IndexVisitor visitor) throws IOException {
-        // Index Platforms
-        for (Platform platform : repository.getPlatforms()) {
-            for (Release release : platform.getReleases()) {
-                QuarkusPlatformDescriptor descriptor = artifactResolver.resolvePlatform(platform, release);
-                if (descriptor != null) {
-                    visitor.visitPlatform(descriptor);
-                }
-            }
-        }
-
-        // Index extensions
-        for (Extension extension : repository.getIndividualExtensions()) {
-            for (Release release : extension.getReleases()) {
-                io.quarkus.dependencies.Extension ext = artifactResolver.resolveExtension(extension, release);
-                if (ext != null) {
-                    visitor.visitExtension(ext, release.getQuarkusCore());
-                }
-            }
-        }
-    }
-}
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/builder/ExtensionRegistryBuilder.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/builder/ExtensionRegistryBuilder.java
deleted file mode 100644
index 8e1b62248fd408..00000000000000
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/builder/ExtensionRegistryBuilder.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package io.quarkus.registry.builder;
-
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
-import io.quarkus.registry.DefaultExtensionRegistry;
-import io.quarkus.registry.ExtensionRegistry;
-import io.quarkus.registry.catalog.model.Extension;
-import io.quarkus.registry.catalog.model.Platform;
-import io.quarkus.registry.catalog.spi.ArtifactResolver;
-import io.quarkus.registry.model.Registry;
-import io.quarkus.registry.model.Release;
-import java.io.IOException;
-
-/**
- * Builds an {@link ExtensionRegistry} given the platforms and extensions resolved by the
- * {@link ArtifactResolver}
- */
-public class ExtensionRegistryBuilder {
-
-    private final ArtifactResolver artifactResolver;
-    private final RegistryBuilder registryBuilder = new RegistryBuilder();
-
-    public ExtensionRegistryBuilder(ArtifactResolver artifactResolver) {
-        this.artifactResolver = artifactResolver;
-    }
-
-    public ExtensionRegistryBuilder addPlatform(String groupId, String artifactId, String version) throws IOException {
-        Platform platform = Platform.builder().groupId(groupId).artifactId(artifactId).build();
-        Release release = Release.builder().version(version).build();
-        QuarkusPlatformDescriptor descriptor = artifactResolver.resolvePlatform(platform, release);
-        registryBuilder.visitPlatform(descriptor);
-        return this;
-    }
-
-    public ExtensionRegistryBuilder addExtension(String groupId, String artifactId, String version, String quarkusCore)
-            throws IOException {
-        Extension extension = Extension.builder().groupId(groupId).artifactId(artifactId).build();
-        Release release = Release.builder().version(version).build();
-        io.quarkus.dependencies.Extension ext = artifactResolver.resolveExtension(extension, release);
-        registryBuilder.visitExtension(ext, quarkusCore);
-        return this;
-    }
-
-    public ExtensionRegistry build() {
-        Registry registry = registryBuilder.build();
-        return new DefaultExtensionRegistry(registry);
-    }
-
-}
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/builder/RegistryBuilder.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/builder/RegistryBuilder.java
deleted file mode 100644
index 4eaeb8524b3674..00000000000000
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/builder/RegistryBuilder.java
+++ /dev/null
@@ -1,122 +0,0 @@
-package io.quarkus.registry.builder;
-
-import io.quarkus.dependencies.Extension;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
-import io.quarkus.registry.catalog.spi.IndexVisitor;
-import io.quarkus.registry.model.ArtifactCoords;
-import io.quarkus.registry.model.ArtifactKey;
-import io.quarkus.registry.model.Extension.ExtensionPlatformRelease;
-import io.quarkus.registry.model.ImmutableArtifactCoords;
-import io.quarkus.registry.model.ImmutableArtifactKey;
-import io.quarkus.registry.model.ImmutableExtensionPlatformRelease;
-import io.quarkus.registry.model.ImmutableRegistry;
-import io.quarkus.registry.model.ModifiableExtension;
-import io.quarkus.registry.model.ModifiableExtensionRelease;
-import io.quarkus.registry.model.ModifiablePlatform;
-import io.quarkus.registry.model.Registry;
-import io.quarkus.registry.model.Release;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.Objects;
-import org.apache.maven.artifact.versioning.ComparableVersion;
-import org.immutables.value.Value;
-
-public class RegistryBuilder implements IndexVisitor {
-
-    private final Map<ArtifactKey, ModifiablePlatform> platforms = new LinkedHashMap<>();
-
-    private final Map<ArtifactKey, ModifiableExtension> extensions = new LinkedHashMap<>();
-
-    private final Map<ArtifactCoordsTuple, ModifiableExtensionRelease> releases = new LinkedHashMap<>();
-
-    private final ImmutableRegistry.Builder registryBuilder = Registry.builder();
-
-    @Override
-    public void visitPlatform(QuarkusPlatformDescriptor platform) {
-        registryBuilder.putCoreVersions(new ComparableVersion(platform.getQuarkusVersion()), new HashMap<>());
-        registryBuilder.addAllCategories(platform.getCategories());
-
-        ArtifactKey platformKey = ImmutableArtifactKey.of(platform.getBomGroupId(), platform.getBomArtifactId());
-        ModifiablePlatform platformBuilder = platforms.computeIfAbsent(platformKey,
-                key -> ModifiablePlatform.create().setId(key));
-
-        platformBuilder.addReleases(Release.builder().version(platform.getBomVersion())
-                .quarkusCore(platform.getQuarkusVersion())
-                .build());
-
-        ArtifactCoords platformCoords = ImmutableArtifactCoords.of(platformKey, platform.getBomVersion());
-        for (Extension extension : platform.getExtensions()) {
-            visitExtension(extension, platform.getQuarkusVersion(), platformCoords);
-        }
-    }
-
-    @Override
-    public void visitExtension(Extension extension, String quarkusCore) {
-        visitExtension(extension, quarkusCore, null);
-    }
-
-    private void visitExtension(Extension extension, String quarkusCore, ArtifactCoords platform) {
-        // Ignore unlisted extensions
-        if (extension.isUnlisted()) {
-            return;
-        }
-        ArtifactKey extensionKey = ImmutableArtifactKey.of(extension.getGroupId(), extension.getArtifactId());
-        ModifiableExtension extensionBuilder = extensions
-                .computeIfAbsent(extensionKey, key -> ModifiableExtension.create()
-                        .setId(extensionKey)
-                        .setName(Objects.toString(extension.getName(), extension.getArtifactId()))
-                        .setDescription(extension.getDescription())
-                        .setMetadata(extension.getMetadata()));
-        ArtifactCoords coords = ImmutableArtifactCoords.of(extensionKey, extension.getVersion());
-        ArtifactCoordsTuple key = ImmutableArtifactCoordsTuple.builder().coords(coords)
-                .quarkusVersion(quarkusCore)
-                .build();
-        ModifiableExtensionRelease releaseBuilder = releases.computeIfAbsent(key,
-                appArtifactCoords -> ModifiableExtensionRelease.create()
-                        .setRelease(Release.builder()
-                                .version(appArtifactCoords.getCoords().getVersion())
-                                .quarkusCore(appArtifactCoords.getQuarkusVersion())
-                                .build()));
-        if (platform != null) {
-            Map<String, Object> metadata = diff(extensionBuilder.getMetadata(), extension.getMetadata());
-            ExtensionPlatformRelease platformRelease = ImmutableExtensionPlatformRelease
-                    .builder().coords(platform).metadata(metadata).build();
-            releaseBuilder.addPlatforms(platformRelease);
-        }
-    }
-
-    public Registry build() {
-        for (Map.Entry<ArtifactCoordsTuple, ModifiableExtensionRelease> entry : releases.entrySet()) {
-            ArtifactCoordsTuple tuple = entry.getKey();
-            ModifiableExtensionRelease extensionReleaseBuilder = entry.getValue();
-            ArtifactKey key = tuple.getCoords().getId();
-            ModifiableExtension extensionBuilder = extensions.get(key);
-            extensionBuilder.addReleases(extensionReleaseBuilder.toImmutable());
-        }
-        extensions.values().stream().map(ModifiableExtension::toImmutable).forEach(registryBuilder::addExtensions);
-        platforms.values().stream().map(ModifiablePlatform::toImmutable).forEach(registryBuilder::addPlatforms);
-        return registryBuilder.build();
-    }
-
-    static Map<String, Object> diff(Map<String, Object> left, Map<String, Object> right) {
-        Map<String, Object> result = new HashMap<>();
-        for (Map.Entry<String, Object> entry : right.entrySet()) {
-            Object value = left.get(entry.getKey());
-            if (!entry.getValue().equals(value)) {
-                result.put(entry.getKey(), entry.getValue());
-            }
-        }
-        return result;
-    }
-
-    @Value.Immutable
-    public interface ArtifactCoordsTuple {
-        @Value.Parameter
-        ArtifactCoords getCoords();
-
-        @Value.Parameter
-        String getQuarkusVersion();
-    }
-
-}
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/builder/URLRegistryBuilder.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/builder/URLRegistryBuilder.java
deleted file mode 100644
index 493c08e9afabd0..00000000000000
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/builder/URLRegistryBuilder.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package io.quarkus.registry.builder;
-
-import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.PropertyNamingStrategies;
-import io.quarkus.registry.model.ImmutableRegistry;
-import io.quarkus.registry.model.Registry;
-import java.io.IOException;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-public class URLRegistryBuilder {
-
-    private final List<URL> urls = new ArrayList<>();
-
-    public URLRegistryBuilder addURL(URL url) {
-        urls.add(url);
-        return this;
-    }
-
-    public URLRegistryBuilder addURLs(Collection<URL> urls) {
-        this.urls.addAll(urls);
-        return this;
-    }
-
-    public Registry build() throws IOException {
-        if (urls.isEmpty()) {
-            throw new IllegalStateException("At least one URL must be specified");
-        }
-        ObjectMapper mapper = new ObjectMapper()
-                .setSerializationInclusion(JsonInclude.Include.NON_NULL)
-                .setPropertyNamingStrategy(PropertyNamingStrategies.KEBAB_CASE);
-        if (urls.size() == 1) {
-            // Just one
-            return mapper.readValue(urls.get(0), Registry.class);
-        } else {
-            ImmutableRegistry.Builder builder = Registry.builder();
-            for (URL url : urls) {
-                Registry aRegistry = mapper.readValue(url, Registry.class);
-                builder.addAllCategories(aRegistry.getCategories())
-                        .addAllExtensions(aRegistry.getExtensions())
-                        .addAllPlatforms(aRegistry.getPlatforms())
-                        .putAllCoreVersions(aRegistry.getCoreVersions());
-            }
-            return builder.build();
-        }
-
-    }
-}
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/catalog/model/Extension.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/catalog/model/Extension.java
deleted file mode 100644
index bf7c365c59bd4e..00000000000000
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/catalog/model/Extension.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package io.quarkus.registry.catalog.model;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-import io.quarkus.registry.model.Release;
-import java.util.List;
-import org.immutables.value.Value;
-
-/**
- * An extension is a Maven dependency that can be added to a Quarkus project
- */
-@Value.Immutable
-@JsonDeserialize(as = ImmutableExtension.class)
-public interface Extension {
-
-    @JsonProperty("group-id")
-    String getGroupId();
-
-    @JsonProperty("artifact-id")
-    String getArtifactId();
-
-    @Value.Auxiliary
-    List<Release> getReleases();
-
-    static ImmutableExtension.Builder builder() {
-        return ImmutableExtension.builder();
-    }
-}
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/catalog/model/Platform.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/catalog/model/Platform.java
deleted file mode 100644
index fd6e96a2542229..00000000000000
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/catalog/model/Platform.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package io.quarkus.registry.catalog.model;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-import io.quarkus.registry.model.Release;
-import java.util.List;
-import org.immutables.value.Value;
-
-/**
- * A {@link Platform} holds a set of extensions
- */
-@Value.Immutable
-@JsonDeserialize(as = ImmutablePlatform.class)
-public interface Platform {
-
-    @JsonProperty("group-id")
-    String getGroupId();
-
-    @Value.Default
-    @JsonProperty("group-id-json")
-    @Value.Auxiliary
-    default String getGroupIdJson() {
-        return getGroupId();
-    }
-
-    @JsonProperty("artifact-id")
-    String getArtifactId();
-
-    @Value.Default
-    @JsonProperty("artifact-id-json")
-    @Value.Auxiliary
-    default String getArtifactIdJson() {
-        return getArtifactId();
-    }
-
-    @Value.Auxiliary
-    List<Release> getReleases();
-
-    static ImmutablePlatform.Builder builder() {
-        return ImmutablePlatform.builder();
-    }
-}
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/catalog/model/Repository.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/catalog/model/Repository.java
deleted file mode 100644
index e51884b657529e..00000000000000
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/catalog/model/Repository.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package io.quarkus.registry.catalog.model;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.ObjectReader;
-import com.fasterxml.jackson.databind.PropertyNamingStrategies;
-import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-import com.fasterxml.jackson.databind.type.CollectionType;
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import org.immutables.value.Value.Immutable;
-
-@Immutable
-@JsonDeserialize(as = ImmutableRepository.class)
-public abstract class Repository {
-
-    public abstract List<Extension> getIndividualExtensions();
-
-    public abstract List<Platform> getPlatforms();
-
-    /**
-     * - Match all files ending with '.json' inside an extensions directory
-     * - Match platforms.json
-     */
-    public static Repository parse(Path rootPath, ObjectMapper mapper) {
-        ObjectReader reader = mapper.reader()
-                .with(mapper.getDeserializationConfig().with(PropertyNamingStrategies.KEBAB_CASE));
-        return ImmutableRepository.builder()
-                .addAllPlatforms(parse(rootPath.resolve("platforms.json"), Platform.class, reader))
-                .addAllIndividualExtensions(parse(rootPath.resolve("extensions"), Extension.class, reader))
-                .build();
-    }
-
-    private static <T> Set<T> parse(Path root, Class<? extends T> type, ObjectReader reader) {
-        final Set<T> result = new HashSet<>();
-        if (Files.isDirectory(root)) {
-            try (DirectoryStream<Path> stream = Files.newDirectoryStream(root, "*.json")) {
-                for (Path path : stream) {
-                    result.add(reader.forType(type).readValue(path.toFile()));
-                }
-            } catch (IOException e) {
-                throw new UncheckedIOException(e);
-            }
-        } else {
-            CollectionType collectionType = reader.getTypeFactory().constructCollectionType(List.class, type);
-            try {
-                result.addAll(reader.forType(collectionType).readValue(root.toFile()));
-            } catch (IOException e) {
-                e.printStackTrace();
-            }
-        }
-        return result;
-    }
-
-}
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/catalog/spi/ArtifactResolver.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/catalog/spi/ArtifactResolver.java
deleted file mode 100644
index a2ed5907785674..00000000000000
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/catalog/spi/ArtifactResolver.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package io.quarkus.registry.catalog.spi;
-
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
-import io.quarkus.registry.catalog.model.Extension;
-import io.quarkus.registry.catalog.model.Platform;
-import io.quarkus.registry.model.Release;
-import java.io.IOException;
-
-/**
- * Resolves artifacts from the underlying artifact repositories
- */
-public interface ArtifactResolver {
-    /**
-     * Resolve this specific platform
-     */
-    QuarkusPlatformDescriptor resolvePlatform(Platform platform, Release release) throws IOException;
-
-    /**
-     * Resolve this specific extension
-     */
-    io.quarkus.dependencies.Extension resolveExtension(Extension extension, Release release) throws IOException;
-}
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/catalog/spi/IndexVisitor.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/catalog/spi/IndexVisitor.java
deleted file mode 100644
index d2e9c5f32af5f0..00000000000000
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/catalog/spi/IndexVisitor.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package io.quarkus.registry.catalog.spi;
-
-import io.quarkus.dependencies.Extension;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
-
-public interface IndexVisitor {
-
-    void visitPlatform(QuarkusPlatformDescriptor platform);
-
-    void visitExtension(Extension extension, String quarkusCore);
-}
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/model/ArtifactCoords.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/model/ArtifactCoords.java
deleted file mode 100644
index dcb211448ddae7..00000000000000
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/model/ArtifactCoords.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package io.quarkus.registry.model;
-
-import com.fasterxml.jackson.annotation.JsonUnwrapped;
-import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-import org.immutables.value.Value;
-
-@Value.Immutable
-@JsonDeserialize(as = ImmutableArtifactCoords.class)
-public interface ArtifactCoords {
-    @Value.Parameter
-    @JsonUnwrapped
-    ArtifactKey getId();
-
-    @Value.Parameter
-    String getVersion();
-}
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/model/ArtifactKey.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/model/ArtifactKey.java
deleted file mode 100644
index c38c3ef82b57a1..00000000000000
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/model/ArtifactKey.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package io.quarkus.registry.model;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-import org.immutables.value.Value;
-
-@Value.Immutable
-@JsonDeserialize(as = ImmutableArtifactKey.class)
-public interface ArtifactKey {
-    @Value.Parameter
-    @JsonProperty("group-id")
-    String getGroupId();
-
-    @Value.Parameter
-    @JsonProperty("artifact-id")
-    String getArtifactId();
-}
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/model/Extension.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/model/Extension.java
deleted file mode 100644
index 6e20e935ea8db4..00000000000000
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/model/Extension.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package io.quarkus.registry.model;
-
-import com.fasterxml.jackson.annotation.JsonUnwrapped;
-import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-import java.util.Map;
-import java.util.Set;
-import java.util.SortedSet;
-import org.immutables.value.Value;
-
-@Value.Immutable
-@Value.Modifiable
-@JsonDeserialize(as = ImmutableExtension.class)
-public interface Extension {
-
-    @JsonUnwrapped
-    ArtifactKey getId();
-
-    @Value.Auxiliary
-    String getName();
-
-    @Value.Auxiliary
-    @Nullable
-    String getDescription();
-
-    @Value.Auxiliary
-    Map<String, Object> getMetadata();
-
-    @Value.Auxiliary
-    @Value.ReverseOrder
-    SortedSet<ExtensionRelease> getReleases();
-
-    @Value.Immutable
-    @Value.Modifiable
-    @JsonDeserialize(as = ImmutableExtensionRelease.class)
-    interface ExtensionRelease extends Comparable<ExtensionRelease> {
-
-        @JsonUnwrapped
-        Release getRelease();
-
-        @Value.Auxiliary
-        Set<ExtensionPlatformRelease> getPlatforms();
-
-        @Override
-        default int compareTo(ExtensionRelease o) {
-            return getRelease().compareTo(o.getRelease());
-        }
-    }
-
-    @Value.Immutable
-    @JsonDeserialize(as = ImmutableExtensionPlatformRelease.class)
-    interface ExtensionPlatformRelease {
-        @JsonUnwrapped
-        ArtifactCoords getCoords();
-
-        @Value.Auxiliary
-        Map<String, Object> getMetadata();
-    }
-}
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/model/Nullable.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/model/Nullable.java
deleted file mode 100644
index 223b4cb0688419..00000000000000
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/model/Nullable.java
+++ /dev/null
@@ -1,4 +0,0 @@
-package io.quarkus.registry.model;
-
-@interface Nullable {
-}
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/model/Platform.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/model/Platform.java
deleted file mode 100644
index 1c721254a74ee1..00000000000000
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/model/Platform.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package io.quarkus.registry.model;
-
-import com.fasterxml.jackson.annotation.JsonUnwrapped;
-import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-import java.util.Set;
-import org.immutables.value.Value;
-
-@Value.Immutable
-@Value.Modifiable
-@JsonDeserialize(as = ImmutablePlatform.class)
-public interface Platform {
-
-    @JsonUnwrapped
-    ArtifactKey getId();
-
-    @Value.Auxiliary
-    Set<Release> getReleases();
-}
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/model/Registry.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/model/Registry.java
deleted file mode 100644
index 7250c5af056920..00000000000000
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/model/Registry.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package io.quarkus.registry.model;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-import io.quarkus.dependencies.Category;
-import java.util.Map;
-import java.util.Set;
-import java.util.SortedMap;
-import org.apache.maven.artifact.versioning.ComparableVersion;
-import org.immutables.value.Value;
-
-@Value.Immutable
-@JsonDeserialize(as = ImmutableRegistry.class)
-public interface Registry {
-
-    @JsonProperty("core-versions")
-    @Value.ReverseOrder
-    SortedMap<ComparableVersion, Map<String, String>> getCoreVersions();
-
-    Set<Extension> getExtensions();
-
-    Set<Platform> getPlatforms();
-
-    Set<Category> getCategories();
-
-    static ImmutableRegistry.Builder builder() {
-        return ImmutableRegistry.builder();
-    }
-}
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/model/Release.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/model/Release.java
deleted file mode 100644
index 84e7650b56350f..00000000000000
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/model/Release.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package io.quarkus.registry.model;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-import org.apache.maven.artifact.versioning.ComparableVersion;
-import org.immutables.value.Value;
-
-@Value.Immutable
-@JsonDeserialize(as = ImmutableRelease.class)
-public interface Release extends Comparable<Release> {
-
-    String getVersion();
-
-    @Nullable
-    @JsonProperty("quarkus-core")
-    String getQuarkusCore();
-
-    @Nullable
-    @JsonProperty("repository-url")
-    @Value.Auxiliary
-    String getRepositoryURL();
-
-    static ImmutableRelease.Builder builder() {
-        return ImmutableRelease.builder();
-    }
-
-    @Override
-    default int compareTo(Release o) {
-        int compare = new ComparableVersion(getVersion())
-                .compareTo(new ComparableVersion(o.getVersion()));
-        if (compare == 0 && (getQuarkusCore() != null && o.getQuarkusCore() != null)) {
-            compare = new ComparableVersion(getQuarkusCore())
-                    .compareTo(new ComparableVersion(o.getQuarkusCore()));
-        }
-        return compare;
-    }
-}
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/package-info.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/package-info.java
deleted file mode 100644
index 0c6504e3ca885f..00000000000000
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/registry/package-info.java
+++ /dev/null
@@ -1,6 +0,0 @@
-@Style(jdkOnly = true, visibility = PUBLIC, allowedClasspathAnnotations = { Override.class })
-package io.quarkus.registry;
-
-import static org.immutables.value.Value.Style.ImplementationVisibility.PUBLIC;
-
-import org.immutables.value.Value.Style;
\ No newline at end of file
diff --git a/independent-projects/tools/devtools-common/src/main/resources/META-INF/services/io.quarkus.devtools.project.codegen.ProjectGenerator b/independent-projects/tools/devtools-common/src/main/resources/META-INF/services/io.quarkus.devtools.project.codegen.ProjectGenerator
deleted file mode 100644
index 41ba05fc1a3e40..00000000000000
--- a/independent-projects/tools/devtools-common/src/main/resources/META-INF/services/io.quarkus.devtools.project.codegen.ProjectGenerator
+++ /dev/null
@@ -1 +0,0 @@
-io.quarkus.devtools.project.codegen.rest.BasicRestProjectGenerator
diff --git a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/project/buildfile/MavenBuildFileTest.java b/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/project/buildfile/MavenBuildFileTest.java
index 88f10e174789bf..e0104ed2fe59d1 100644
--- a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/project/buildfile/MavenBuildFileTest.java
+++ b/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/project/buildfile/MavenBuildFileTest.java
@@ -4,7 +4,7 @@
 
 import io.quarkus.bootstrap.model.AppArtifactCoords;
 import io.quarkus.maven.utilities.MojoUtils;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
+import io.quarkus.registry.catalog.ExtensionCatalog;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.Properties;
@@ -39,7 +39,7 @@ void setUp(@TempDir Path projectDirPath) throws IOException {
         model.setDependencyManagement(depMan);
         Path pomPath = projectDirPath.resolve("pom.xml");
         MojoUtils.write(model, pomPath.toFile());
-        QuarkusPlatformDescriptor mock = Mockito.mock(QuarkusPlatformDescriptor.class);
+        ExtensionCatalog mock = Mockito.mock(ExtensionCatalog.class);
         mavenBuildFile = new MavenBuildFile(projectDirPath, mock);
     }
 
diff --git a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/registry/DefaultExtensionRegistryTest.java b/independent-projects/tools/devtools-common/src/test/java/io/quarkus/registry/DefaultExtensionRegistryTest.java
deleted file mode 100644
index fcf1fa3b1f480d..00000000000000
--- a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/registry/DefaultExtensionRegistryTest.java
+++ /dev/null
@@ -1,82 +0,0 @@
-package io.quarkus.registry;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.PropertyNamingStrategies;
-import io.quarkus.bootstrap.model.AppArtifactCoords;
-import io.quarkus.devtools.project.extensions.ExtensionInstallPlan;
-import io.quarkus.registry.builder.RegistryBuilder;
-import io.quarkus.registry.catalog.model.Repository;
-import io.quarkus.registry.model.Registry;
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Arrays;
-import java.util.Collections;
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.io.TempDir;
-
-class DefaultExtensionRegistryTest {
-
-    static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
-            .setPropertyNamingStrategy(PropertyNamingStrategies.KEBAB_CASE)
-            .setSerializationInclusion(JsonInclude.Include.NON_NULL);
-    static DefaultExtensionRegistry extensionRegistry;
-
-    @BeforeAll
-    static void setUp() throws IOException {
-        Repository repository = Repository.parse(Paths.get("src/test/resources/registry/repository"), OBJECT_MAPPER);
-        RegistryBuilder registryBuilder = new RegistryBuilder();
-        RepositoryIndexer indexer = new RepositoryIndexer(new TestArtifactResolver());
-        indexer.index(repository, registryBuilder);
-        Registry registry = registryBuilder.build();
-        extensionRegistry = new DefaultExtensionRegistry(registry);
-    }
-
-    @Test
-    void serializationShouldKeepValues(@TempDir Path tmpDir) throws IOException {
-        File tmpFile = tmpDir.resolve("registry.json").toFile();
-        OBJECT_MAPPER.writeValue(tmpFile, extensionRegistry.getRegistry());
-        Registry newRegistry = OBJECT_MAPPER.readValue(tmpFile, Registry.class);
-        assertThat(newRegistry).isEqualToComparingFieldByField(extensionRegistry.getRegistry());
-    }
-
-    @Test
-    void shouldReturnFourQuarkusCoreVersions() {
-        assertThat(extensionRegistry.getQuarkusCoreVersions()).containsExactly(
-                "1.6.0.Final",
-                "1.5.2.Final",
-                "1.3.2.Final");
-    }
-
-    @Test
-    void shouldLookupPlatformForDependentExtensionInQuarkusFinal() {
-        ExtensionInstallPlan result = extensionRegistry.planInstallation("1.3.2.Final",
-                Arrays.asList("quarkus-arc", "quarkus-vertx"));
-        assertThat(result).isNotNull();
-        assertThat(result.getPlatforms()).hasSize(2);
-        assertThat(result.getPlatforms())
-                .extracting(AppArtifactCoords::getArtifactId)
-                .contains("quarkus-universe-bom", "quarkus-bom");
-        assertThat(result.getManagedExtensions()).hasSize(2);
-        assertThat(result.getIndependentExtensions()).isEmpty();
-    }
-
-    @Test
-    void shouldLookupNoPlatformForIndependentExtension() {
-        ExtensionInstallPlan result = extensionRegistry.planInstallation("1.3.1.Final",
-                Collections.singletonList("myfaces-quarkus-runtime"));
-        assertThat(result).isNotNull();
-        assertThat(result.getPlatforms()).isEmpty();
-        assertThat(result.getManagedExtensions()).isEmpty();
-        assertThat(result.getIndependentExtensions()).hasSize(1);
-        assertThat(result.getIndependentExtensions().iterator().next())
-                .hasFieldOrPropertyWithValue("groupId", "org.apache.myfaces.core.extensions.quarkus")
-                .hasFieldOrPropertyWithValue("artifactId", "myfaces-quarkus-runtime")
-                .hasFieldOrPropertyWithValue("version", "2.3-next-M2");
-    }
-}
diff --git a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/registry/RepositoryIndexerTest.java b/independent-projects/tools/devtools-common/src/test/java/io/quarkus/registry/RepositoryIndexerTest.java
deleted file mode 100644
index 282e311b0d7e38..00000000000000
--- a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/registry/RepositoryIndexerTest.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package io.quarkus.registry;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.atLeast;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import io.quarkus.dependencies.Extension;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
-import io.quarkus.registry.catalog.model.Repository;
-import io.quarkus.registry.catalog.spi.IndexVisitor;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import org.junit.jupiter.api.Test;
-
-class RepositoryIndexerTest {
-    @Test
-    void shouldVisitParsedElements() throws Exception {
-        Path rootPath = Paths.get("src/test/resources/registry/repository");
-        assertThat(rootPath).exists();
-        ObjectMapper mapper = new ObjectMapper();
-        Repository repository = Repository.parse(rootPath, mapper);
-        RepositoryIndexer indexer = new RepositoryIndexer(new TestArtifactResolver());
-        IndexVisitor mock = mock(IndexVisitor.class);
-        indexer.index(repository, mock);
-        verify(mock, atLeast(4)).visitPlatform(any(QuarkusPlatformDescriptor.class));
-        verify(mock, atLeast(2)).visitExtension(any(Extension.class), anyString());
-    }
-}
diff --git a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/registry/TestArtifactResolver.java b/independent-projects/tools/devtools-common/src/test/java/io/quarkus/registry/TestArtifactResolver.java
deleted file mode 100644
index 9e2581f99eb645..00000000000000
--- a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/registry/TestArtifactResolver.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package io.quarkus.registry;
-
-import io.quarkus.dependencies.Extension;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
-import io.quarkus.registry.catalog.model.Platform;
-import io.quarkus.registry.catalog.spi.ArtifactResolver;
-import io.quarkus.registry.model.Release;
-import io.quarkus.test.platform.descriptor.loader.QuarkusTestPlatformDescriptorLoader;
-import java.io.IOException;
-
-public class TestArtifactResolver implements ArtifactResolver {
-
-    @Override
-    public QuarkusPlatformDescriptor resolvePlatform(Platform platform, Release release) {
-        final QuarkusTestPlatformDescriptorLoader loader = new QuarkusTestPlatformDescriptorLoader();
-        loader.setGroupId(platform.getGroupId());
-        loader.setArtifactId(platform.getArtifactId());
-        loader.setVersion(release.getVersion());
-        return loader.load(null);
-    }
-
-    @Override
-    public Extension resolveExtension(io.quarkus.registry.catalog.model.Extension extension, Release release)
-            throws IOException {
-        return new Extension(extension.getGroupId(), extension.getArtifactId(), release.getVersion());
-    }
-}
diff --git a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/registry/builder/ExtensionRegistryBuilderTest.java b/independent-projects/tools/devtools-common/src/test/java/io/quarkus/registry/builder/ExtensionRegistryBuilderTest.java
deleted file mode 100644
index 7f6ead47774a9c..00000000000000
--- a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/registry/builder/ExtensionRegistryBuilderTest.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package io.quarkus.registry.builder;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.atMostOnce;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import io.quarkus.registry.ExtensionRegistry;
-import io.quarkus.registry.catalog.model.Extension;
-import io.quarkus.registry.catalog.spi.ArtifactResolver;
-import io.quarkus.registry.model.Release;
-import java.io.IOException;
-import org.junit.jupiter.api.Test;
-
-class ExtensionRegistryBuilderTest {
-
-    @Test
-    void shouldResolveCamelDependencies() throws IOException {
-        String groupId = "org.apache.camel";
-        String artifactId = "camel-quarkus";
-        String version = "1.6.0.Final";
-
-        ArtifactResolver artifactResolver = mock(ArtifactResolver.class);
-        Release release = Release.builder().version(version).build();
-        Extension extension = Extension.builder().groupId(groupId).artifactId(artifactId)
-                .addReleases(release).build();
-        when(artifactResolver.resolveExtension(extension, release))
-                .thenReturn(new io.quarkus.dependencies.Extension(groupId, artifactId, version));
-
-        ExtensionRegistry registry = new ExtensionRegistryBuilder(artifactResolver)
-                .addExtension(groupId, artifactId, version, version)
-                .build();
-        verify(artifactResolver, atMostOnce()).resolveExtension(extension, release);
-        assertThat(registry.list(version, "camel")).isNotEmpty();
-    }
-
-}
diff --git a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/registry/builder/RegistryBuilderTest.java b/independent-projects/tools/devtools-common/src/test/java/io/quarkus/registry/builder/RegistryBuilderTest.java
deleted file mode 100644
index a6a69ed15bd1c0..00000000000000
--- a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/registry/builder/RegistryBuilderTest.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package io.quarkus.registry.builder;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.entry;
-
-import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.PropertyNamingStrategies;
-import io.quarkus.registry.RepositoryIndexer;
-import io.quarkus.registry.TestArtifactResolver;
-import io.quarkus.registry.catalog.model.Repository;
-import io.quarkus.registry.model.Registry;
-import java.io.IOException;
-import java.nio.file.Paths;
-import java.util.HashMap;
-import java.util.Map;
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.Test;
-
-class RegistryBuilderTest {
-
-    static Registry registry;
-
-    @BeforeAll
-    static void setUp() throws IOException {
-        ObjectMapper mapper = new ObjectMapper();
-        Repository repository = Repository.parse(Paths.get("src/test/resources/registry/repository"), mapper);
-        RepositoryIndexer indexer = new RepositoryIndexer(new TestArtifactResolver());
-        RegistryBuilder builder = new RegistryBuilder();
-        indexer.index(repository, builder);
-        registry = builder.build();
-    }
-
-    @Test
-    void build() throws Exception {
-        assertThat(registry.getExtensions()).isNotEmpty();
-        assertThat(registry.getPlatforms()).isNotEmpty();
-        if (Boolean.getBoolean("generateTmpRegistry")) {
-            ObjectMapper mapper = new ObjectMapper()
-                    .setSerializationInclusion(JsonInclude.Include.NON_NULL)
-                    .setPropertyNamingStrategy(PropertyNamingStrategies.KEBAB_CASE);
-            mapper.writeValue(new java.io.File("/tmp/registry.json"), registry);
-        }
-    }
-
-    @Test
-    void diffMap() {
-        Map<String, Object> left = new HashMap<>();
-        left.put("A", "1");
-        left.put("B", "2");
-        Map<String, Object> right = new HashMap<>();
-        right.put("A", "1");
-        right.put("B", "2");
-        right.put("C", "3");
-        Map<String, Object> result = RegistryBuilder.diff(left, right);
-        assertThat(result).doesNotContainKeys("A", "B").containsOnly(entry("C", "3"));
-    }
-
-}
diff --git a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/registry/builder/URLRegistryBuilderTest.java b/independent-projects/tools/devtools-common/src/test/java/io/quarkus/registry/builder/URLRegistryBuilderTest.java
deleted file mode 100644
index 5cad5d6368c3a9..00000000000000
--- a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/registry/builder/URLRegistryBuilderTest.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package io.quarkus.registry.builder;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-
-import io.quarkus.registry.model.Registry;
-import java.io.IOException;
-import java.net.URL;
-import org.junit.jupiter.api.Test;
-
-class URLRegistryBuilderTest {
-
-    @Test
-    void shouldFailOnEmptyURLS() {
-        assertThrows(IllegalStateException.class, new URLRegistryBuilder()::build);
-    }
-
-    @Test
-    void shouldReadRegistry() throws IOException {
-        URL url = getClass().getClassLoader().getResource("registry/registry.json");
-        Registry registry = new URLRegistryBuilder().addURL(url).build();
-        assertThat(registry.getCoreVersions()).isNotEmpty();
-    }
-}
diff --git a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/registry/model/RepositoryTest.java b/independent-projects/tools/devtools-common/src/test/java/io/quarkus/registry/model/RepositoryTest.java
deleted file mode 100644
index 35cd832fa3945f..00000000000000
--- a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/registry/model/RepositoryTest.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package io.quarkus.registry.model;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import io.quarkus.registry.catalog.model.Repository;
-import java.nio.file.Paths;
-import org.junit.jupiter.api.Test;
-
-class RepositoryTest {
-
-    @Test
-    void shouldParseRepository() throws Exception {
-        Repository repository = Repository.parse(Paths.get("src/test/resources/registry/repository"), new ObjectMapper());
-        assertThat(repository).isNotNull();
-        assertThat(repository.getPlatforms()).isNotEmpty();
-        assertThat(repository.getIndividualExtensions()).isNotEmpty();
-    }
-}
diff --git a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/test/platform/descriptor/CombinedQuarkusPlatformDescriptorTest.java b/independent-projects/tools/devtools-common/src/test/java/io/quarkus/test/platform/descriptor/CombinedQuarkusPlatformDescriptorTest.java
deleted file mode 100644
index f7f72424e5b174..00000000000000
--- a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/test/platform/descriptor/CombinedQuarkusPlatformDescriptorTest.java
+++ /dev/null
@@ -1,95 +0,0 @@
-package io.quarkus.test.platform.descriptor;
-
-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 io.quarkus.dependencies.Category;
-import io.quarkus.dependencies.Extension;
-import io.quarkus.platform.descriptor.CombinedQuarkusPlatformDescriptor;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-public class CombinedQuarkusPlatformDescriptorTest extends PlatformAwareTestBase {
-
-    private static final String DOMINATING_VERSION = "dominating-version";
-
-    QuarkusPlatformDescriptor dominatingPlatform;
-    QuarkusPlatformDescriptor defaultPlatform;
-
-    @BeforeEach
-    public void setup() {
-        dominatingPlatform = new TestDominatingQuarkusPlatformDescriptor();
-        defaultPlatform = getPlatformDescriptor();
-    }
-
-    @Test
-    public void testDominance() throws Exception {
-
-        final QuarkusPlatformDescriptor combined = CombinedQuarkusPlatformDescriptor.builder()
-                .addPlatform(dominatingPlatform)
-                .addPlatform(defaultPlatform)
-                .build();
-
-        final Map<String, Extension> expectedExtensions = toMap(defaultPlatform.getExtensions());
-        expectedExtensions.putAll(toMap(dominatingPlatform.getExtensions()));
-
-        assertBom(combined);
-
-        assertEquals(dominatingPlatform.getQuarkusVersion(), combined.getQuarkusVersion());
-
-        assertCategories(combined);
-
-        assertExtensions(expectedExtensions, combined);
-
-        assertEquals("dominating pom.xml template", combined.getTemplate("dir/some-other-file.template"));
-        assertEquals(defaultPlatform.getTemplate("dir/some-file.template"),
-                combined.getTemplate("dir/some-file.template"));
-    }
-
-    private void assertBom(QuarkusPlatformDescriptor descriptor) {
-        assertEquals(dominatingPlatform.getBomGroupId(), descriptor.getBomGroupId());
-        assertEquals(dominatingPlatform.getBomArtifactId(), descriptor.getBomArtifactId());
-        assertEquals(dominatingPlatform.getBomVersion(), descriptor.getBomVersion());
-    }
-
-    private void assertCategories(QuarkusPlatformDescriptor descriptor) {
-        final List<Category> categories = descriptor.getCategories();
-        assertFalse(categories.isEmpty());
-        final Map<String, Category> map = categories.stream().collect(Collectors.toMap(Category::getId, c -> c));
-        assertEquals("Dominating Web", map.get("web").getName());
-        assertEquals("Data", map.get("data").getName());
-        assertEquals("Other category", map.get("other").getName());
-    }
-
-    private void assertExtensions(Map<String, Extension> expectedExtensions, QuarkusPlatformDescriptor descriptor) {
-        final List<Extension> extensions = descriptor.getExtensions();
-        assertFalse(extensions.isEmpty());
-        for (Extension actual : extensions) {
-            final Extension expected = expectedExtensions.get(getGa(actual));
-            assertNotNull(expected);
-            assertEquals(expected.getVersion(), actual.getVersion());
-        }
-
-        final Map<String, Extension> actualMap = toMap(descriptor.getExtensions());
-        Extension ext = actualMap.get("io.quarkus:quarkus-jdbc-h2");
-        assertNotNull(ext);
-        assertEquals(defaultPlatform.getQuarkusVersion(), ext.getVersion());
-
-        ext = actualMap.get("io.quarkus:quarkus-resteasy");
-        assertNotNull(ext);
-        assertEquals(DOMINATING_VERSION, ext.getVersion());
-    }
-
-    private static Map<String, Extension> toMap(final List<Extension> extensions) {
-        return extensions.stream().collect(Collectors.toMap(e -> getGa(e), e -> e));
-    }
-
-    private static String getGa(Extension e) {
-        return e.getGroupId() + ":" + e.getArtifactId();
-    }
-}
diff --git a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/test/platform/descriptor/PlatformAwareTestBase.java b/independent-projects/tools/devtools-common/src/test/java/io/quarkus/test/platform/descriptor/PlatformAwareTestBase.java
deleted file mode 100644
index 0cf687fef93049..00000000000000
--- a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/test/platform/descriptor/PlatformAwareTestBase.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package io.quarkus.test.platform.descriptor;
-
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
-import io.quarkus.platform.tools.config.QuarkusPlatformConfig;
-
-public class PlatformAwareTestBase {
-
-    private QuarkusPlatformDescriptor platformDescr;
-
-    protected QuarkusPlatformDescriptor getPlatformDescriptor() {
-        return platformDescr == null ? platformDescr = QuarkusPlatformConfig.builder().build().getPlatformDescriptor()
-                : platformDescr;
-    }
-
-    protected String getBomGroupId() {
-        return getPlatformDescriptor().getBomGroupId();
-    }
-
-    protected String getBomArtifactId() {
-        return getPlatformDescriptor().getBomArtifactId();
-    }
-
-    protected String getBomVersion() {
-        return getPlatformDescriptor().getBomVersion();
-    }
-}
diff --git a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/test/platform/descriptor/TestDominatingQuarkusPlatformDescriptor.java b/independent-projects/tools/devtools-common/src/test/java/io/quarkus/test/platform/descriptor/TestDominatingQuarkusPlatformDescriptor.java
deleted file mode 100644
index b8e77157072b38..00000000000000
--- a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/test/platform/descriptor/TestDominatingQuarkusPlatformDescriptor.java
+++ /dev/null
@@ -1,77 +0,0 @@
-package io.quarkus.test.platform.descriptor;
-
-import static io.quarkus.test.platform.descriptor.loader.QuarkusTestPlatformDescriptorLoader.addCategory;
-import static io.quarkus.test.platform.descriptor.loader.QuarkusTestPlatformDescriptorLoader.addExtension;
-
-import io.quarkus.bootstrap.model.AppArtifactCoords;
-import io.quarkus.dependencies.Category;
-import io.quarkus.dependencies.Extension;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
-import io.quarkus.platform.descriptor.ResourceInputStreamConsumer;
-import io.quarkus.platform.descriptor.ResourcePathConsumer;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-public class TestDominatingQuarkusPlatformDescriptor implements QuarkusPlatformDescriptor {
-
-    private final List<Category> categories = new ArrayList<>();
-    private final List<Extension> extensions = new ArrayList<>();
-
-    public TestDominatingQuarkusPlatformDescriptor() {
-
-        addCategory("other", "Other category", categories);
-        addCategory("web", "Dominating Web", categories);
-
-        addExtension(new AppArtifactCoords("io.quarkus", "quarkus-resteasy", "dominating-version"), "Dominating RESTEasy",
-                "dominating/guide", "reasteasy", extensions);
-    }
-
-    @Override
-    public String getBomGroupId() {
-        return "dominating.bom.group.id";
-    }
-
-    @Override
-    public String getBomArtifactId() {
-        return "dominating.bom.artifact.id";
-    }
-
-    @Override
-    public String getBomVersion() {
-        return "dominating.bom.version";
-    }
-
-    @Override
-    public String getQuarkusVersion() {
-        return "dominating.quarkus.version";
-    }
-
-    @Override
-    public List<Extension> getExtensions() {
-        return extensions;
-    }
-
-    @Override
-    public List<Category> getCategories() {
-        return categories;
-    }
-
-    @Override
-    public String getTemplate(String name) {
-        if ("dir/some-other-file.template".equals(name)) {
-            return "dominating pom.xml template";
-        }
-        return null;
-    }
-
-    @Override
-    public <T> T loadResource(String name, ResourceInputStreamConsumer<T> consumer) throws IOException {
-        return null;
-    }
-
-    @Override
-    public <T> T loadResourceAsPath(String name, ResourcePathConsumer<T> consumer) throws IOException {
-        return null;
-    }
-}
diff --git a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/test/platform/descriptor/loader/QuarkusTestPlatformDescriptorLoader.java b/independent-projects/tools/devtools-common/src/test/java/io/quarkus/test/platform/descriptor/loader/QuarkusTestPlatformDescriptorLoader.java
deleted file mode 100644
index a3e4ca798e8203..00000000000000
--- a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/test/platform/descriptor/loader/QuarkusTestPlatformDescriptorLoader.java
+++ /dev/null
@@ -1,235 +0,0 @@
-package io.quarkus.test.platform.descriptor.loader;
-
-import io.quarkus.bootstrap.model.AppArtifactCoords;
-import io.quarkus.dependencies.Category;
-import io.quarkus.dependencies.Extension;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
-import io.quarkus.platform.descriptor.ResourceInputStreamConsumer;
-import io.quarkus.platform.descriptor.ResourcePathConsumer;
-import io.quarkus.platform.descriptor.loader.QuarkusPlatformDescriptorLoader;
-import io.quarkus.platform.descriptor.loader.QuarkusPlatformDescriptorLoaderContext;
-import io.quarkus.platform.descriptor.loader.json.ResourceLoaders;
-import io.quarkus.platform.tools.ToolsConstants;
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.StringWriter;
-import java.net.URL;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-import java.util.Properties;
-
-public class QuarkusTestPlatformDescriptorLoader
-        implements QuarkusPlatformDescriptorLoader<QuarkusPlatformDescriptor, QuarkusPlatformDescriptorLoaderContext> {
-
-    private static final List<Extension> extensions = new ArrayList<>();
-    private static final Properties quarkusProps;
-
-    private static final String quarkusVersion;
-    private static final List<Category> categories = new ArrayList<>();
-
-    private String groupId = "io.quarkus";
-    private String artifactId = "quarkus-bom";
-    private String version;
-
-    private static void addCategories() {
-        addCategory("web", "Web");
-        addCategory("data", "Data");
-        addCategory("messaging", "Messaging");
-        addCategory("core", "Core");
-        addCategory("reactive", "Reactive");
-        addCategory("cloud", "Cloud");
-    }
-
-    private static void addExtensions() {
-        addExtension("quarkus-agroal", "Agroal");
-        addExtension("quarkus-arc", "Arc");
-        addExtension("quarkus-kotlin", "Kotlin", "url://", "kotlin");
-        addExtension("quarkus-scala", "Scala", "url://", "scala");
-        addExtension("quarkus-config-yaml", "Config Yaml", "url://", "config-yaml");
-        addExtension("quarkus-hibernate-orm-panache", "Hibernate ORM Panache");
-        addExtension("quarkus-hibernate-search-orm-elasticsearch", "Elasticsearch");
-        addExtension("quarkus-hibernate-validator", "Hibernate Validator");
-        addExtension("quarkus-jdbc-postgresql", "JDBC PostreSQL");
-        addExtension("quarkus-jdbc-h2", "JDBC H2");
-        addExtension("quarkus-resteasy", "RESTEasy", "https://quarkus.io/guides/rest-json", "resteasy");
-
-        addExtension("quarkus-smallrye-reactive-messaging", "SmallRye Reactive Messaging");
-        addExtension("quarkus-smallrye-reactive-streams-operators", "SmallRye Reactive Streams Operators");
-        addExtension("quarkus-smallrye-opentracing", "SmallRye Opentracing");
-        addExtension("quarkus-smallrye-metrics", "SmallRye Metrics");
-        addExtension("quarkus-smallrye-reactive-messaging-kafka", "SmallRye Reactive Messaging Kafka");
-        addExtension("quarkus-smallrye-health", "SmallRye Health");
-        addExtension("quarkus-smallrye-openapi", "SmallRye Open API");
-        addExtension("quarkus-smallrye-jwt", "SmallRye JWT");
-        addExtension("quarkus-smallrye-context-propagation", "SmallRye Context Propagation");
-        addExtension("quarkus-smallrye-reactive-type-converters", "SmallRye Reactive Type Converters");
-        addExtension("quarkus-smallrye-reactive-messaging-amqp", "SmallRye Reactive Messaging AMQP");
-        addExtension("quarkus-smallrye-fault-tolerance", "SmallRye Fault Tolerance");
-
-        addExtension("quarkus-vertx", "Vert.X");
-    }
-
-    private static void addExtension(String artifactId, String name) {
-        addExtension(artifactId, name, "url://" + name);
-    }
-
-    private static void addExtension(String artifactId, String name, String guide) {
-        addExtension(artifactId, name, guide, null);
-    }
-
-    private static void addExtension(String artifactId, String name, String guide, String codestart) {
-        addExtension(new AppArtifactCoords("io.quarkus", artifactId, quarkusVersion), name, guide, codestart);
-    }
-
-    private static void addExtension(AppArtifactCoords coords, String name, String guide, String codestart) {
-        addExtension(coords, name, guide, codestart, extensions);
-    }
-
-    public static void addExtension(AppArtifactCoords coords, String name, String guide, String codestart,
-            List<Extension> extensions) {
-        final Extension e = new Extension(coords.getGroupId(), coords.getArtifactId(), coords.getVersion())
-                .setName(name)
-                .setGuide(guide);
-        if (codestart != null) {
-            e.setCodestart(codestart);
-        }
-        extensions.add(e);
-    }
-
-    private static void addCategory(String id, String name) {
-        addCategory(id, name, categories);
-    }
-
-    public static void addCategory(String id, String name, List<Category> categories) {
-        Category cat = new Category();
-        cat.setId(id);
-        cat.setName(name);
-        categories.add(cat);
-    }
-
-    public void setGroupId(String groupId) {
-        this.groupId = groupId;
-    }
-
-    public void setArtifactId(String artifactId) {
-        this.artifactId = artifactId;
-    }
-
-    public void setVersion(String version) {
-        this.version = version;
-    }
-
-    static {
-        try {
-            quarkusProps = loadStaticResource("quarkus.properties", is -> {
-                final Properties props = new Properties();
-                props.load(is);
-                return props;
-            });
-        } catch (IOException e) {
-            throw new IllegalStateException("Failed to load quarkus.properties", e);
-        }
-        quarkusVersion = quarkusProps.getProperty(ToolsConstants.PROP_QUARKUS_CORE_VERSION);
-        if (quarkusVersion == null) {
-            throw new IllegalStateException(
-                    ToolsConstants.PROP_QUARKUS_CORE_VERSION + " property is missing from quarkus.properties");
-        }
-
-        addCategories();
-        addExtensions();
-    }
-
-    @Override
-    public QuarkusPlatformDescriptor load(QuarkusPlatformDescriptorLoaderContext context) {
-        return new QuarkusPlatformDescriptor() {
-
-            @Override
-            public String getBomGroupId() {
-                return groupId;
-            }
-
-            @Override
-            public String getBomArtifactId() {
-                return artifactId;
-            }
-
-            @Override
-            public String getBomVersion() {
-                return Objects.toString(version, quarkusVersion);
-            }
-
-            @Override
-            public String getQuarkusVersion() {
-                return Objects.toString(version, quarkusVersion);
-            }
-
-            @Override
-            public List<Extension> getExtensions() {
-                return extensions;
-            }
-
-            @Override
-            public String getTemplate(String name) {
-                try {
-                    return loadResource(name, is -> {
-                        final StringWriter writer = new StringWriter();
-                        try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
-                                BufferedWriter bw = new BufferedWriter(writer)) {
-                            String line;
-                            while ((line = reader.readLine()) != null) {
-                                bw.write(line);
-                                bw.newLine();
-                            }
-                        }
-                        return writer.getBuffer().toString();
-                    });
-                } catch (IOException e) {
-                    throw new IllegalStateException("Failed to load resource " + name, e);
-                }
-            }
-
-            @Override
-            public <T> T loadResource(String name, ResourceInputStreamConsumer<T> consumer) throws IOException {
-                return loadStaticResource(name, consumer);
-            }
-
-            @Override
-            public <T> T loadResourceAsPath(String name, ResourcePathConsumer<T> consumer) throws IOException {
-                return loadStaticResourcePath(name, consumer);
-            }
-
-            @Override
-            public List<Category> getCategories() {
-                return categories;
-            }
-        };
-    }
-
-    private static <T> T loadStaticResource(String name, ResourceInputStreamConsumer<T> consumer) throws IOException {
-        final InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(name);
-        if (is == null) {
-            throw new IOException("Failed to locate resource " + name + " on the classpath");
-        }
-        try {
-            return consumer.consume(is);
-        } finally {
-            is.close();
-        }
-    }
-
-    private static <T> T loadStaticResourcePath(String name, ResourcePathConsumer<T> consumer) throws IOException {
-        final URL url = Thread.currentThread().getContextClassLoader().getResource(name);
-        final File file = ResourceLoaders.getResourceFile(url, name);
-        if (!Files.exists(file.toPath())) {
-            throw new IOException("Failed to locate resource " + name + " on the classpath");
-        }
-        return consumer.consume(file.toPath());
-    }
-}
diff --git a/independent-projects/tools/devtools-common/src/test/resources/META-INF/services/io.quarkus.platform.descriptor.loader.QuarkusPlatformDescriptorLoader b/independent-projects/tools/devtools-common/src/test/resources/META-INF/services/io.quarkus.platform.descriptor.loader.QuarkusPlatformDescriptorLoader
deleted file mode 100644
index d891464c6f8e9a..00000000000000
--- a/independent-projects/tools/devtools-common/src/test/resources/META-INF/services/io.quarkus.platform.descriptor.loader.QuarkusPlatformDescriptorLoader
+++ /dev/null
@@ -1 +0,0 @@
-io.quarkus.test.platform.descriptor.loader.QuarkusTestPlatformDescriptorLoader
\ No newline at end of file
diff --git a/independent-projects/tools/devtools-common/src/test/resources/registry/registry.json b/independent-projects/tools/devtools-common/src/test/resources/registry/registry.json
deleted file mode 100644
index 0ce8d67c77a054..00000000000000
--- a/independent-projects/tools/devtools-common/src/test/resources/registry/registry.json
+++ /dev/null
@@ -1,202 +0,0 @@
-{
-  "core-versions": {
-    "1.5.1.Final": {},
-    "1.4.2.Final": {},
-    "1.3.4.Final": {}
-  },
-  "extensions" : [
-    {
-      "group-id":"io.quarkus",
-      "artifact-id":"quarkus-resteasy",
-      "name":"RESTEasy JAX-RS",
-      "description":"REST endpoint framework implementing JAX-RS and more",
-      "metadata":{
-        "short-name":"jax-rs",
-        "keywords":[
-          "resteasy",
-          "jaxrs",
-          "web",
-          "rest"
-        ],
-        "guide":"https://quarkus.io/guides/rest-json",
-        "categories":[
-          "web"
-        ],
-        "status":"stable"
-      },
-      "releases":[
-        {
-          "platforms":[
-            {
-              "group-id":"io.quarkus",
-              "artifact-id":"quarkus-universe-bom",
-              "version":"1.5.1.Final"
-            },
-            {
-              "group-id":"io.quarkus",
-              "artifact-id":"quarkus-bom",
-              "version":"1.5.1.Final"
-            }
-          ],
-          "version":"1.5.1.Final",
-          "quarkus-core":"1.5.1.Final"
-        },
-        {
-          "platforms":[
-            {
-              "group-id":"io.quarkus",
-              "artifact-id":"quarkus-universe-bom",
-              "version":"1.4.2.Final"
-            },
-            {
-              "group-id":"io.quarkus",
-              "artifact-id":"quarkus-bom",
-              "version":"1.4.2.Final"
-            }
-          ],
-          "version":"1.4.2.Final",
-          "quarkus-core":"1.4.2.Final"
-        },
-        {
-          "platforms":[
-            {
-              "group-id":"io.quarkus",
-              "artifact-id":"quarkus-universe-bom",
-              "version":"1.3.4.Final"
-            },
-            {
-              "group-id":"io.quarkus",
-              "artifact-id":"quarkus-bom",
-              "version":"1.3.4.Final"
-            }
-          ],
-          "version":"1.3.4.Final",
-          "quarkus-core":"1.3.4.Final"
-        }
-      ]
-    },
-    {
-      "group-id":"io.quarkus",
-      "artifact-id":"quarkus-resteasy-jackson",
-      "name":"RESTEasy Jackson",
-      "description":"Jackson serialization support for RESTEasy",
-      "metadata":{
-        "keywords":[
-          "resteasy-jackson",
-          "jaxrs-json",
-          "resteasy-json",
-          "resteasy",
-          "jaxrs",
-          "json",
-          "jackson"
-        ],
-        "categories":[
-          "web",
-          "serialization"
-        ],
-        "status":"stable"
-      },
-      "releases":[
-        {
-          "platforms":[
-            {
-              "group-id":"io.quarkus",
-              "artifact-id":"quarkus-universe-bom",
-              "version":"1.5.1.Final"
-            },
-            {
-              "group-id":"io.quarkus",
-              "artifact-id":"quarkus-bom",
-              "version":"1.5.1.Final"
-            }
-          ],
-          "version":"1.5.1.Final",
-          "quarkus-core":"1.5.1.Final"
-        },
-        {
-          "platforms":[
-            {
-              "group-id":"io.quarkus",
-              "artifact-id":"quarkus-universe-bom",
-              "version":"1.4.2.Final"
-            },
-            {
-              "group-id":"io.quarkus",
-              "artifact-id":"quarkus-bom",
-              "version":"1.4.2.Final"
-            }
-          ],
-          "version":"1.4.2.Final",
-          "quarkus-core":"1.4.2.Final"
-        },
-        {
-          "platforms":[
-            {
-              "group-id":"io.quarkus",
-              "artifact-id":"quarkus-universe-bom",
-              "version":"1.3.4.Final"
-            },
-            {
-              "group-id":"io.quarkus",
-              "artifact-id":"quarkus-bom",
-              "version":"1.3.4.Final"
-            }
-          ],
-          "version":"1.3.4.Final",
-          "quarkus-core":"1.3.4.Final"
-        }
-      ]
-    }
-  ],
-  "platforms":[
-    {
-      "group-id":"io.quarkus",
-      "artifact-id":"quarkus-universe-bom",
-      "releases":[
-        {
-          "version":"1.5.1.Final",
-          "quarkus-core":"1.5.1.Final"
-        },
-        {
-          "version":"1.4.2.Final",
-          "quarkus-core":"1.4.2.Final"
-        },
-        {
-          "version":"1.3.4.Final",
-          "quarkus-core":"1.3.4.Final"
-        }
-      ]
-    },
-    {
-      "group-id":"io.quarkus",
-      "artifact-id":"quarkus-bom",
-      "releases":[
-        {
-          "version":"1.5.1.Final",
-          "quarkus-core":"1.5.1.Final"
-        },
-        {
-          "version":"1.4.2.Final",
-          "quarkus-core":"1.4.2.Final"
-        },
-        {
-          "version":"1.3.4.Final",
-          "quarkus-core":"1.3.4.Final"
-        }
-      ]
-    }
-  ],
-  "categories":[
-    {
-      "id":"web",
-      "name":"Web",
-      "description":"Everything you need for REST endpoints, HTTP and web formats like JSON",
-      "metadata":{
-        "pinned":[
-          "io.quarkus:quarkus-resteasy",
-          "io.quarkus:quarkus-resteasy-jackson"
-        ]
-      }
-    }
-  ]
-}
\ No newline at end of file
diff --git a/independent-projects/tools/devtools-common/src/test/resources/registry/repository/extensions/jsf.json b/independent-projects/tools/devtools-common/src/test/resources/registry/repository/extensions/jsf.json
deleted file mode 100644
index 7dedaad1cee999..00000000000000
--- a/independent-projects/tools/devtools-common/src/test/resources/registry/repository/extensions/jsf.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
-  "group-id": "org.apache.myfaces.core.extensions.quarkus",
-  "artifact-id": "myfaces-quarkus-runtime",
-  "releases": [
-    {
-      "version": "2.3-next-M2",
-      "quarkus-core":  "1.3.1.Final"
-    },
-    {
-      "version": "2.3-next-M1",
-      "quarkus-core": "1.1.0.CR1"
-    }
-  ]
-}
\ No newline at end of file
diff --git a/independent-projects/tools/devtools-common/src/test/resources/registry/repository/platforms.json b/independent-projects/tools/devtools-common/src/test/resources/registry/repository/platforms.json
deleted file mode 100644
index 7d16491842aa3c..00000000000000
--- a/independent-projects/tools/devtools-common/src/test/resources/registry/repository/platforms.json
+++ /dev/null
@@ -1,30 +0,0 @@
-[
-  {
-    "group-id": "io.quarkus",
-    "artifact-id": "quarkus-universe-bom",
-    "releases": [
-      {
-        "version": "1.5.2.Final"
-      },
-      {
-        "version": "1.3.2.Final"
-      }
-    ]
-  },
-  {
-    "group-id": "io.quarkus",
-    "artifact-id": "quarkus-bom",
-    "artifact-id-json": "quarkus-bom-descriptor-json",
-    "releases": [
-      {
-        "version": "1.6.0.Final"
-      },
-      {
-        "version": "1.5.2.Final"
-      },
-      {
-        "version": "1.3.2.Final"
-      }
-    ]
-  }
-]
diff --git a/independent-projects/tools/message-writer/pom.xml b/independent-projects/tools/message-writer/pom.xml
index 3ef13025f1d0d3..4d33f90355d1c8 100644
--- a/independent-projects/tools/message-writer/pom.xml
+++ b/independent-projects/tools/message-writer/pom.xml
@@ -19,5 +19,4 @@
     <artifactId>quarkus-devtools-message-writer</artifactId>
     <name>Quarkus - Dev tools - Message Writer</name>
 
-    
 </project>
\ No newline at end of file
diff --git a/independent-projects/tools/platform-descriptor-api/pom.xml b/independent-projects/tools/platform-descriptor-api/pom.xml
deleted file mode 100644
index 7d4d978905f95f..00000000000000
--- a/independent-projects/tools/platform-descriptor-api/pom.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <modelVersion>4.0.0</modelVersion>
-
-    <parent>
-        <groupId>io.quarkus</groupId>
-        <artifactId>quarkus-tools-parent</artifactId>
-        <version>999-SNAPSHOT</version>
-    </parent>
-
-    <artifactId>quarkus-platform-descriptor-api</artifactId>
-    <name>Quarkus - Dev tools - Platform Descriptor API</name>
-
-    <dependencies>
-        <dependency>
-            <groupId>io.quarkus</groupId>
-            <artifactId>quarkus-devtools-message-writer</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.maven</groupId>
-            <artifactId>maven-model</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.junit.jupiter</groupId>
-            <artifactId>junit-jupiter</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.assertj</groupId>
-            <artifactId>assertj-core</artifactId>
-            <scope>test</scope>
-        </dependency>
-    </dependencies>
-</project>
diff --git a/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/dependencies/Extension.java b/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/dependencies/Extension.java
deleted file mode 100644
index f867b080c8561a..00000000000000
--- a/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/dependencies/Extension.java
+++ /dev/null
@@ -1,305 +0,0 @@
-package io.quarkus.dependencies;
-
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-import org.apache.maven.model.Dependency;
-
-/**
- * @author <a href="http://escoffier.me">Clement Escoffier</a>
- * @author <a href="http://kenfinnigan.me">Ken Finnigan</a>
- */
-public class Extension implements Serializable {
-
-    public static final String GROUP_ID = "group-id";
-
-    public static final String ARTIFACT_ID = "artifact-id";
-
-    public static final String VERSION = "version";
-
-    public static final String MD_SHORT_NAME = "short-name";
-
-    public static final String MD_CODESTART = "codestart";
-
-    public static final String MD_GUIDE = "guide";
-
-    /** Key used for keywords in metadata **/
-    public static String MD_KEYWORDS = "keywords";
-
-    public static final String MD_UNLISTED = "unlisted";
-
-    public static final String MD_STATUS = "status";
-
-    private String artifactId;
-    private String groupId;
-    private String scope;
-    private String version;
-
-    private String type;
-    private String classifier;
-
-    private String name;
-    private String description;
-
-    private String simplifiedArtifactId;
-    private static final transient Pattern QUARKUS_PREFIX = Pattern.compile("^quarkus-");
-
-    private Map<String, Object> metadata = new HashMap<String, Object>(3);
-
-    public Extension() {
-        // Use by mapper.
-    }
-
-    public Extension(String groupId, String artifactId, String version) {
-        this.groupId = groupId;
-        this.setArtifactId(artifactId);
-        this.version = version;
-    }
-
-    public String getArtifactId() {
-        return artifactId;
-    }
-
-    public Extension setArtifactId(String artifactId) {
-        this.artifactId = artifactId;
-        this.simplifiedArtifactId = QUARKUS_PREFIX.matcher(artifactId).replaceFirst("");
-        return this;
-    }
-
-    /** Group Id for the extension artifact */
-    public String getGroupId() {
-        return groupId;
-    }
-
-    public Extension setGroupId(String groupId) {
-        this.groupId = groupId;
-        return this;
-    }
-
-    public String getScope() {
-        return scope;
-    }
-
-    public Extension setScope(String scope) {
-        this.scope = scope;
-        return this;
-    }
-
-    public String getVersion() {
-        return version;
-    }
-
-    public Extension setVersion(String version) {
-        this.version = version;
-        return this;
-    }
-
-    public String getType() {
-        return type;
-    }
-
-    public Extension setType(String type) {
-        this.type = type;
-        return this;
-    }
-
-    public String getClassifier() {
-        return classifier;
-    }
-
-    public Extension setClassifier(String classifier) {
-        this.classifier = classifier;
-        return this;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public Extension setName(String name) {
-        this.name = name;
-        return this;
-    }
-
-    public String getDescription() {
-        return description;
-    }
-
-    public Extension setDescription(String description) {
-        this.description = description;
-        return this;
-    }
-
-    /**
-     * Semi-Unstructured metadata used to provide metadata to tools and other
-     * frontends.
-     * 
-     */
-    public Map<String, Object> getMetadata() {
-        return metadata;
-    }
-
-    public Extension setMetadata(Map<String, Object> metadata) {
-        this.metadata = metadata;
-        return this;
-    }
-
-    public List<String> getKeywords() {
-        List<String> kw = (List<String>) getMetadata().get(MD_KEYWORDS);
-        return kw == null ? Collections.emptyList() : kw;
-    }
-
-    public Extension setKeywords(String[] keywords) {
-        getMetadata().put(MD_KEYWORDS, Arrays.asList(keywords));
-        return this;
-    }
-
-    /**
-     * List of strings to use for matching.
-     * 
-     * Returns keywords + artifactid all in lowercase.
-     * 
-     * @return list of labels to use for matching.
-     */
-    public List<String> labelsForMatching() {
-        List<String> list = new ArrayList<>();
-        List<String> keywords = getKeywords();
-        if (keywords != null) {
-            list.addAll(keywords.stream().map(String::toLowerCase).collect(Collectors.toList()));
-        }
-        list.add(artifactId.toLowerCase());
-        return list;
-    }
-
-    /**
-     * Convert this Extension into a dependency
-     * 
-     * @param stripVersion if provided version will not be set on the Dependency
-     * @return a Maven {@link Dependency} object
-     */
-    public Dependency toDependency(boolean stripVersion) {
-        Dependency dependency = new Dependency();
-        dependency.setGroupId(groupId);
-        dependency.setArtifactId(artifactId);
-        if (scope != null && !scope.isEmpty()) {
-            dependency.setScope(scope);
-        }
-        if (classifier != null && !classifier.isEmpty()) {
-            dependency.setClassifier(classifier);
-        }
-        if (version != null && !version.isEmpty() && !stripVersion) {
-            dependency.setVersion(version);
-        }
-        if (type != null && !type.isEmpty()) {
-            dependency.setType(type);
-        }
-        return dependency;
-    }
-
-    public String managementKey() {
-        return getGroupId() + ":" + getArtifactId();
-    }
-
-    public String gav() {
-        return managementKey() + ":" + version;
-    }
-
-    public String getSimplifiedArtifactId() {
-        return simplifiedArtifactId;
-    }
-
-    @Override
-    public String toString() {
-        return gav();
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-        Extension extension = (Extension) o;
-        return Objects.equals(artifactId, extension.artifactId) &&
-                Objects.equals(groupId, extension.groupId) &&
-                Objects.equals(version, extension.version);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(artifactId, groupId, version);
-    }
-
-    public Extension setGuide(String guide) {
-        getMetadata().put(MD_GUIDE, guide);
-        return this;
-    }
-
-    /**
-     * 
-     * @return string representing the location of primary guide for this extension.
-     */
-    public String getGuide() {
-        return (String) getMetadata().get(MD_GUIDE);
-    }
-
-    public String getShortName() {
-        String shortName = (String) getMetadata().get(MD_SHORT_NAME);
-        if (shortName == null) {
-            return name;
-        } else {
-            return shortName;
-        }
-    }
-
-    public Extension setShortName(String shortName) {
-        getMetadata().put(MD_SHORT_NAME, shortName);
-        return this;
-    }
-
-    public String getCodestart() {
-        return (String) getMetadata().get(MD_CODESTART);
-    }
-
-    public Extension setCodestart(String codestart) {
-        getMetadata().put(MD_CODESTART, codestart);
-        return this;
-    }
-
-    public boolean isUnlisted() {
-        Object val = getMetadata().get(MD_UNLISTED);
-        if (val == null) {
-            return false;
-        } else if (val instanceof Boolean) {
-            return (Boolean) val;
-        } else if (val instanceof String) {
-            return Boolean.parseBoolean((String) val);
-        }
-
-        return false;
-    }
-
-    public void setUnlisted(boolean unlisted) {
-        getMetadata().put(MD_UNLISTED, unlisted);
-    }
-
-    public Extension addMetadata(String key, Object value) {
-        getMetadata().put(key, value);
-        return this;
-
-    }
-
-    public Extension removeMetadata(String key) {
-        getMetadata().remove(key);
-        return this;
-    }
-}
diff --git a/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/platform/descriptor/CombinedQuarkusPlatformDescriptor.java b/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/platform/descriptor/CombinedQuarkusPlatformDescriptor.java
deleted file mode 100644
index 53d3f359f2271c..00000000000000
--- a/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/platform/descriptor/CombinedQuarkusPlatformDescriptor.java
+++ /dev/null
@@ -1,210 +0,0 @@
-package io.quarkus.platform.descriptor;
-
-import io.quarkus.dependencies.Category;
-import io.quarkus.dependencies.Extension;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import org.apache.maven.model.Dependency;
-
-/**
- * Platform descriptor that is composed of multiple platform descriptors.
- * The order in which descriptors are added is significant. Platform descriptor
- * added earlier dominate over those added later.
- */
-public class CombinedQuarkusPlatformDescriptor implements QuarkusPlatformDescriptor {
-
-    public static class Builder {
-
-        private final List<QuarkusPlatformDescriptor> platforms = new ArrayList<>();
-
-        private Builder() {
-        }
-
-        /**
-         * Adds a platform descriptor.
-         * The order in which descriptors are added is significant. Platform descriptor
-         * added earlier dominate over those added later.
-         *
-         * @param platform platform descriptor to add
-         * @return this builder instance
-         */
-        public Builder addPlatform(QuarkusPlatformDescriptor platform) {
-            platforms.add(platform);
-            return this;
-        }
-
-        public QuarkusPlatformDescriptor build() {
-            if (platforms.size() == 1) {
-                return platforms.get(0);
-            }
-            return new CombinedQuarkusPlatformDescriptor(this);
-        }
-    }
-
-    public static Builder builder() {
-        return new Builder();
-    }
-
-    private final QuarkusPlatformDescriptor master;
-    private final List<QuarkusPlatformDescriptor> platforms;
-    private List<Extension> extensions;
-    private List<Category> categories;
-    private Map<String, Object> metadata;
-
-    private CombinedQuarkusPlatformDescriptor(Builder builder) {
-        if (builder.platforms.isEmpty()) {
-            throw new IllegalArgumentException("No platforms to combine");
-        }
-        master = builder.platforms.get(0);
-        platforms = new ArrayList<>(builder.platforms);
-    }
-
-    @Override
-    public String getBomGroupId() {
-        return master.getBomGroupId();
-    }
-
-    @Override
-    public String getBomArtifactId() {
-        return master.getBomArtifactId();
-    }
-
-    @Override
-    public String getBomVersion() {
-        return master.getBomVersion();
-    }
-
-    @Override
-    public String getQuarkusVersion() {
-        return master.getQuarkusVersion();
-    }
-
-    @Override
-    public Map<String, Object> getMetadata() {
-        if (this.metadata != null) {
-            return this.metadata;
-        }
-        Map<String, Object> metadata = new LinkedHashMap<>();
-        for (int i = platforms.size() - 1; i >= 0; i--) {
-            metadata.putAll(platforms.get(i).getMetadata());
-        }
-        return this.metadata = metadata;
-    }
-
-    @Override
-    public List<Extension> getExtensions() {
-        if (extensions != null) {
-            return extensions;
-        }
-        final List<Extension> list = new ArrayList<>();
-        final Set<DepKey> depKeys = new HashSet<>();
-        for (QuarkusPlatformDescriptor platform : platforms) {
-            for (Extension ext : platform.getExtensions()) {
-                if (depKeys.add(new DepKey(ext.getGroupId(), ext.getArtifactId()))) {
-                    list.add(ext);
-                }
-            }
-        }
-        return extensions = list;
-    }
-
-    @Override
-    public List<Category> getCategories() {
-        if (categories != null) {
-            return categories;
-        }
-        final List<Category> list = new ArrayList<>();
-        final Set<String> ids = new HashSet<>();
-        for (QuarkusPlatformDescriptor platform : platforms) {
-            for (Category cat : platform.getCategories()) {
-                if (ids.add(cat.getId())) {
-                    list.add(cat);
-                }
-            }
-        }
-        return categories = list;
-    }
-
-    @Override
-    public String getTemplate(String name) {
-        for (QuarkusPlatformDescriptor platform : platforms) {
-            final String template = platform.getTemplate(name);
-            if (template != null) {
-                return template;
-            }
-        }
-        return null;
-    }
-
-    @Override
-    public <T> T loadResource(String name, ResourceInputStreamConsumer<T> consumer) throws IOException {
-        for (QuarkusPlatformDescriptor platform : platforms) {
-            try {
-                return platform.loadResource(name, consumer);
-            } catch (IOException e) {
-                // ignore
-            }
-        }
-        throw new IOException("Failed to locate resource " + name);
-    }
-
-    @Override
-    public <T> T loadResourceAsPath(String name, ResourcePathConsumer<T> consumer) throws IOException {
-        for (QuarkusPlatformDescriptor platform : platforms) {
-            try {
-                return platform.loadResourceAsPath(name, consumer);
-            } catch (IOException e) {
-                // ignore
-            }
-        }
-        throw new IOException("Failed to locate resource " + name);
-    }
-
-    private static class DepKey {
-        final String groupId;
-        final String artifactId;
-        final String classifier;
-        final String type;
-
-        DepKey(Dependency dep) {
-            this(dep.getGroupId(), dep.getArtifactId(), dep.getClassifier(), dep.getType());
-        }
-
-        DepKey(String groupId, String artifactId) {
-            this(groupId, artifactId, null, null);
-        }
-
-        DepKey(String groupId, String artifactId, String classifier, String type) {
-            this.groupId = groupId;
-            this.artifactId = artifactId;
-            this.classifier = classifier;
-            this.type = type;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) {
-                return true;
-            }
-            if (!(o instanceof DepKey)) {
-                return false;
-            }
-            DepKey depKey = (DepKey) o;
-            return Objects.equals(groupId, depKey.groupId) &&
-                    Objects.equals(artifactId, depKey.artifactId) &&
-                    Objects.equals(classifier, depKey.classifier) &&
-                    Objects.equals(type, depKey.type);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(groupId, artifactId, classifier, type);
-        }
-    }
-}
diff --git a/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/platform/descriptor/QuarkusPlatformDescriptor.java b/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/platform/descriptor/QuarkusPlatformDescriptor.java
deleted file mode 100644
index 811557e98d9ee6..00000000000000
--- a/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/platform/descriptor/QuarkusPlatformDescriptor.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package io.quarkus.platform.descriptor;
-
-import io.quarkus.dependencies.Category;
-import io.quarkus.dependencies.Extension;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import org.apache.maven.model.Dependency;
-
-public interface QuarkusPlatformDescriptor {
-
-    String getBomGroupId();
-
-    String getBomArtifactId();
-
-    String getBomVersion();
-
-    String getQuarkusVersion();
-
-    /**
-     *
-     * @return platform's dependencyManagement
-     */
-    default List<Dependency> getManagedDependencies() {
-        throw new UnsupportedOperationException();
-    }
-
-    List<Extension> getExtensions();
-
-    List<Category> getCategories();
-
-    default Map<String, Object> getMetadata() {
-        return Collections.emptyMap();
-    }
-
-    String getTemplate(String name);
-
-    <T> T loadResource(String name, ResourceInputStreamConsumer<T> consumer) throws IOException;
-
-    <T> T loadResourceAsPath(String name, ResourcePathConsumer<T> consumer) throws IOException;
-
-    default String gav() {
-        return String.format("%s:%s:%s", getBomGroupId(), getBomArtifactId(), getBomVersion());
-    }
-
-}
diff --git a/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/platform/descriptor/loader/QuarkusPlatformDescriptorLoader.java b/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/platform/descriptor/loader/QuarkusPlatformDescriptorLoader.java
deleted file mode 100644
index 371085940126f1..00000000000000
--- a/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/platform/descriptor/loader/QuarkusPlatformDescriptorLoader.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package io.quarkus.platform.descriptor.loader;
-
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
-
-public interface QuarkusPlatformDescriptorLoader<D extends QuarkusPlatformDescriptor, C extends QuarkusPlatformDescriptorLoaderContext> {
-
-    D load(C context);
-}
diff --git a/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/platform/descriptor/loader/QuarkusPlatformDescriptorLoaderContext.java b/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/platform/descriptor/loader/QuarkusPlatformDescriptorLoaderContext.java
deleted file mode 100644
index 2486051b955bc1..00000000000000
--- a/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/platform/descriptor/loader/QuarkusPlatformDescriptorLoaderContext.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package io.quarkus.platform.descriptor.loader;
-
-import io.quarkus.devtools.messagewriter.MessageWriter;
-
-public interface QuarkusPlatformDescriptorLoaderContext {
-
-    MessageWriter getMessageWriter();
-}
diff --git a/independent-projects/tools/platform-descriptor-api/src/test/java/io/quarkus/dependencies/ExtensionPredicateTest.java b/independent-projects/tools/platform-descriptor-api/src/test/java/io/quarkus/dependencies/ExtensionPredicateTest.java
deleted file mode 100644
index 7f9e27f90d6c07..00000000000000
--- a/independent-projects/tools/platform-descriptor-api/src/test/java/io/quarkus/dependencies/ExtensionPredicateTest.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package io.quarkus.dependencies;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-import org.junit.jupiter.api.Test;
-
-class ExtensionPredicateTest {
-
-    @Test
-    void rejectUnlisted() {
-        ExtensionPredicate predicate = new ExtensionPredicate("foo");
-        Extension extension = new Extension("g", "a", "v");
-        extension.setUnlisted(true);
-        assertThat(predicate).rejects(extension);
-    }
-
-    @Test
-    void acceptKeywordInArtifactId() {
-        ExtensionPredicate predicate = new ExtensionPredicate("foo");
-        Extension extension = new Extension("g", "foo-bar", "1.0");
-        assertThat(predicate).accepts(extension);
-    }
-
-    @Test
-    void acceptKeywordInLabel() {
-        ExtensionPredicate predicate = new ExtensionPredicate("foo");
-        Extension extension = new Extension("g", "a", "1.0");
-        extension.setKeywords(new String[] { "foo", "bar" });
-        assertThat(predicate).accepts(extension);
-    }
-
-}
diff --git a/independent-projects/tools/platform-descriptor-resolver-json/src/main/java/io/quarkus/platform/descriptor/resolver/json/ChainedJsonDescriptorResolver.java b/independent-projects/tools/platform-descriptor-resolver-json/src/main/java/io/quarkus/platform/descriptor/resolver/json/ChainedJsonDescriptorResolver.java
deleted file mode 100644
index b8aee2e838158c..00000000000000
--- a/independent-projects/tools/platform-descriptor-resolver-json/src/main/java/io/quarkus/platform/descriptor/resolver/json/ChainedJsonDescriptorResolver.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package io.quarkus.platform.descriptor.resolver.json;
-
-import io.quarkus.devtools.messagewriter.MessageWriter;
-import java.nio.file.Path;
-import java.util.Arrays;
-import java.util.List;
-
-public class ChainedJsonDescriptorResolver implements JsonDescriptorResolver {
-
-    private final List<JsonDescriptorResolver> chain;
-
-    public ChainedJsonDescriptorResolver(JsonDescriptorResolver... descriptorResolvers) {
-        chain = Arrays.asList(descriptorResolvers);
-    }
-
-    @Override
-    public Path jsonForBom(String bomGroupId, String bomArtifactId, String bomVersion,
-            JsonMavenArtifactResolver artifactResolver, MessageWriter log)
-            throws Exception {
-        for (JsonDescriptorResolver resolver : chain) {
-            try {
-                final Path p = resolver.jsonForBom(bomGroupId, bomArtifactId, bomVersion, artifactResolver, log);
-                if (p != null) {
-                    return p;
-                }
-            } catch (Exception e) {
-                if (log != null) {
-                    log.debug("Failed to resolve Quarkus platform descriptor for BOM %s:%s:%s: %s", bomGroupId, bomArtifactId,
-                            bomVersion, e.getLocalizedMessage());
-                }
-            }
-        }
-        throw new Exception(
-                "Failed to resolve the JSON descriptor for BOM " + bomGroupId + ":" + bomArtifactId + ":" + bomVersion);
-    }
-}
diff --git a/independent-projects/tools/platform-descriptor-resolver-json/src/main/java/io/quarkus/platform/descriptor/resolver/json/JsonDescriptorResolver.java b/independent-projects/tools/platform-descriptor-resolver-json/src/main/java/io/quarkus/platform/descriptor/resolver/json/JsonDescriptorResolver.java
deleted file mode 100644
index c7797b674f5c66..00000000000000
--- a/independent-projects/tools/platform-descriptor-resolver-json/src/main/java/io/quarkus/platform/descriptor/resolver/json/JsonDescriptorResolver.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package io.quarkus.platform.descriptor.resolver.json;
-
-import io.quarkus.devtools.messagewriter.MessageWriter;
-import java.nio.file.Path;
-
-public interface JsonDescriptorResolver {
-
-    Path jsonForBom(String bomGroupId, String bomArtifactId, String bomVersion, JsonMavenArtifactResolver jsonResolver,
-            MessageWriter log) throws Exception;
-}
diff --git a/independent-projects/tools/platform-descriptor-resolver-json/src/main/java/io/quarkus/platform/descriptor/resolver/json/JsonMavenArtifactResolver.java b/independent-projects/tools/platform-descriptor-resolver-json/src/main/java/io/quarkus/platform/descriptor/resolver/json/JsonMavenArtifactResolver.java
deleted file mode 100644
index 25b7a6518505bc..00000000000000
--- a/independent-projects/tools/platform-descriptor-resolver-json/src/main/java/io/quarkus/platform/descriptor/resolver/json/JsonMavenArtifactResolver.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package io.quarkus.platform.descriptor.resolver.json;
-
-import io.quarkus.devtools.messagewriter.MessageWriter;
-import java.nio.file.Path;
-
-public interface JsonMavenArtifactResolver {
-
-    Path resolveArtifact(String groupId, String artifactId, String classifier, String type, String version, MessageWriter log)
-            throws Exception;
-}
diff --git a/independent-projects/tools/platform-descriptor-resolver-json/src/main/java/io/quarkus/platform/descriptor/resolver/json/PlatformDescriptorLoadingException.java b/independent-projects/tools/platform-descriptor-resolver-json/src/main/java/io/quarkus/platform/descriptor/resolver/json/PlatformDescriptorLoadingException.java
deleted file mode 100644
index f661ccdca9e182..00000000000000
--- a/independent-projects/tools/platform-descriptor-resolver-json/src/main/java/io/quarkus/platform/descriptor/resolver/json/PlatformDescriptorLoadingException.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package io.quarkus.platform.descriptor.resolver.json;
-
-import java.lang.Exception;
-
-public class PlatformDescriptorLoadingException extends Exception {
-
-    private static final long serialVersionUID = 1L;
-
-    public PlatformDescriptorLoadingException(String message, Throwable cause) {
-        super(message, cause);
-    }
-
-    public PlatformDescriptorLoadingException(String message) {
-        super(message);
-    }
-}
diff --git a/independent-projects/tools/platform-descriptor-resolver-json/src/main/java/io/quarkus/platform/descriptor/resolver/json/QuarkusJsonPlatformDescriptorResolver.java b/independent-projects/tools/platform-descriptor-resolver-json/src/main/java/io/quarkus/platform/descriptor/resolver/json/QuarkusJsonPlatformDescriptorResolver.java
deleted file mode 100644
index 406b018e54fb17..00000000000000
--- a/independent-projects/tools/platform-descriptor-resolver-json/src/main/java/io/quarkus/platform/descriptor/resolver/json/QuarkusJsonPlatformDescriptorResolver.java
+++ /dev/null
@@ -1,738 +0,0 @@
-package io.quarkus.platform.descriptor.resolver.json;
-
-import static io.quarkus.platform.tools.ToolsUtils.getProperty;
-
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import io.quarkus.bootstrap.BootstrapConstants;
-import io.quarkus.bootstrap.model.AppArtifact;
-import io.quarkus.bootstrap.resolver.AppModelResolver;
-import io.quarkus.bootstrap.resolver.AppModelResolverException;
-import io.quarkus.bootstrap.resolver.BootstrapAppModelResolver;
-import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver;
-import io.quarkus.devtools.messagewriter.MessageWriter;
-import io.quarkus.maven.utilities.MojoUtils;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
-import io.quarkus.platform.descriptor.loader.json.ArtifactResolver;
-import io.quarkus.platform.descriptor.loader.json.ClassPathResourceLoader;
-import io.quarkus.platform.descriptor.loader.json.DirectoryResourceLoader;
-import io.quarkus.platform.descriptor.loader.json.QuarkusJsonPlatformDescriptorLoader;
-import io.quarkus.platform.descriptor.loader.json.QuarkusJsonPlatformDescriptorLoaderContext;
-import io.quarkus.platform.descriptor.loader.json.ResourceLoader;
-import io.quarkus.platform.descriptor.loader.json.ZipResourceLoader;
-import io.quarkus.platform.tools.ToolsConstants;
-import io.quarkus.platform.tools.ToolsUtils;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.Iterator;
-import java.util.Properties;
-import java.util.ServiceLoader;
-import java.util.function.Function;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.maven.model.Model;
-import org.apache.maven.model.Parent;
-
-/**
- * Helps resolve the specific or the latest available version of a JSON platform descriptor.
- */
-public class QuarkusJsonPlatformDescriptorResolver {
-
-    private static final String BUNDLED_QUARKUS_BOM_PATH = "quarkus-bom/pom.xml";
-    private static final String BUNDLED_QUARKUS_PROPERTIES_PATH = "quarkus.properties";
-    private static final String BUNDLED_EXTENSIONS_JSON_PATH = "quarkus-bom-descriptor/extensions.json";
-
-    private static final String QUARKUS_PLATFORM_DESCRIPTOR_JSON = "quarkus-platform-descriptor-json";
-
-    private static final String DEFAULT_QUARKUS_PLATFORM_VERSION_RANGE = "[1.0.0.CR2,2)";
-    private static final String DEFAULT_NON_QUARKUS_VERSION_RANGE = "[0,)";
-
-    public static final String PROP_PLATFORM_JSON_GROUP_ID = "quarkus.platform.json.groupId";
-    public static final String PROP_PLATFORM_JSON_ARTIFACT_ID = "quarkus.platform.json.artifactId";
-    public static final String PROP_PLATFORM_JSON_VERSION = "quarkus.platform.json.version";
-    public static final String PROP_PLATFORM_JSON_VERSION_RANGE = "quarkus.platform.json.version-range";
-
-    private static final JsonDescriptorResolver jsonDescriptorResolver;
-    static {
-        jsonDescriptorResolver = new ChainedJsonDescriptorResolver(
-                ((bomGroupId, bomArtifactId, bomVersion, jsonResolver, log) -> jsonResolver.resolveArtifact(bomGroupId,
-                        bomArtifactId + BootstrapConstants.PLATFORM_DESCRIPTOR_ARTIFACT_ID_SUFFIX, bomVersion, "json",
-                        bomVersion, log)),
-                ((bomGroupId, bomArtifactId, bomVersion, jsonResolver, log) -> jsonResolver.resolveArtifact(bomGroupId,
-                        bomArtifactId, null, "json", bomVersion, log)),
-                ((bomGroupId, bomArtifactId, bomVersion, jsonResolver, log) -> jsonResolver.resolveArtifact(bomGroupId,
-                        bomArtifactId + "-descriptor-json", null, "json", bomVersion, log)));
-    }
-
-    public static JsonDescriptorResolver jsonDescriptorResolver() {
-        return jsonDescriptorResolver;
-    }
-
-    public static QuarkusJsonPlatformDescriptorResolver newInstance() {
-        return new QuarkusJsonPlatformDescriptorResolver();
-    }
-
-    private static String getDefaultVersionRange(String groupId, String artifactId) {
-        return ToolsConstants.IO_QUARKUS.equals(groupId)
-                && (isDefaultArtifactId(artifactId, "quarkus-bom")
-                        || isDefaultArtifactId(artifactId, "quarkus-universe-bom")
-                        || "quarkus-bom-descriptor".equals(artifactId))
-                                ? DEFAULT_QUARKUS_PLATFORM_VERSION_RANGE
-                                : DEFAULT_NON_QUARKUS_VERSION_RANGE;
-    }
-
-    private static boolean isDefaultArtifactId(String artifactId, String defaultPrefix) {
-        return artifactId.equals(defaultPrefix + BootstrapConstants.PLATFORM_DESCRIPTOR_ARTIFACT_ID_SUFFIX)
-                || artifactId.equals(defaultPrefix);
-    }
-
-    private String jsonGroupId;
-    private String jsonArtifactId;
-    private String jsonClassifier;
-    private String jsonVersion;
-    private String jsonVersionRange;
-
-    private Path jsonDescriptor;
-
-    private String bomGroupId;
-    private String bomArtifactId;
-    private String bomVersion;
-    private String bomVersionRange;
-
-    private AppModelResolver artifactResolver;
-    private MessageWriter log;
-
-    public QuarkusJsonPlatformDescriptorResolver() {
-    }
-
-    public QuarkusPlatformDescriptor resolveFromJson(String groupId, String artifactId, String version) {
-        // for backward compatibility the classifier here is an empty string
-        return resolveFromJson(groupId, artifactId, "", version);
-    }
-
-    public QuarkusPlatformDescriptor resolveFromJson(String groupId, String artifactId, String classifier, String version) {
-        this.jsonGroupId = groupId;
-        this.jsonArtifactId = artifactId;
-        this.jsonClassifier = classifier;
-        this.jsonVersion = version;
-        return resolve();
-    }
-
-    public QuarkusPlatformDescriptor resolveLatestFromJson(String groupId, String artifactId, String versionRange) {
-        // for backward compatibility the classifier here is an empty string
-        return resolveLatestFromJson(groupId, artifactId, "", versionRange);
-    }
-
-    public QuarkusPlatformDescriptor resolveLatestFromJson(String groupId, String artifactId, String classifier,
-            String versionRange) {
-        this.jsonGroupId = groupId;
-        this.jsonArtifactId = artifactId;
-        this.jsonClassifier = classifier;
-        this.jsonVersionRange = versionRange;
-        return resolve();
-    }
-
-    public QuarkusPlatformDescriptor resolveFromJsonArtifactId(String artifactId) {
-        this.jsonArtifactId = artifactId;
-        return resolve();
-    }
-
-    public QuarkusPlatformDescriptor resolveFromJson(Path jsonDescriptor) {
-        this.jsonDescriptor = jsonDescriptor;
-        return resolve();
-    }
-
-    public QuarkusPlatformDescriptor resolveFromBom(String groupId, String artifactId, String version) {
-        this.bomGroupId = groupId;
-        this.bomArtifactId = artifactId;
-        this.bomVersion = version;
-        return resolve();
-    }
-
-    public QuarkusPlatformDescriptor resolveLatestFromBom(String groupId, String artifactId, String versionRange) {
-        this.bomGroupId = groupId;
-        this.bomArtifactId = artifactId;
-        this.bomVersionRange = versionRange;
-        return resolve();
-    }
-
-    public QuarkusJsonPlatformDescriptorResolver setArtifactResolver(AppModelResolver artifactResolver) {
-        this.artifactResolver = artifactResolver;
-        return this;
-    }
-
-    public QuarkusJsonPlatformDescriptorResolver setMessageWriter(MessageWriter msgWriter) {
-        this.log = msgWriter;
-        return this;
-    }
-
-    public QuarkusPlatformDescriptor resolve() {
-
-        ensureLoggerInitialized();
-
-        AppModelResolver artifactResolver = this.artifactResolver;
-        if (artifactResolver == null) {
-            try {
-                artifactResolver = new BootstrapAppModelResolver(MavenArtifactResolver.builder().build());
-            } catch (Exception e) {
-                throw new IllegalStateException("Failed to initialize the Maven artifact resolver", e);
-            }
-        }
-
-        try {
-            if (jsonDescriptor != null) {
-                return loadFromFile(artifactResolver, jsonDescriptor);
-            }
-            return resolveJsonDescriptor(artifactResolver);
-        } catch (VersionNotAvailableException | PlatformDescriptorLoadingException e) {
-            throw new IllegalStateException("Failed to load Quarkus platform descriptor", e);
-        }
-    }
-
-    public QuarkusPlatformDescriptor resolveBundled() {
-        ensureLoggerInitialized();
-        final Model bundledBom = loadBundledPom();
-        if (bundledBom == null) {
-            throw new IllegalStateException("Failed to locate bundled Quarkus platform BOM on the classpath");
-        }
-        try {
-            return loadFromBomCoords(null, getGroupId(bundledBom), getArtifactId(bundledBom), getVersion(bundledBom),
-                    bundledBom);
-        } catch (Exception e) {
-            throw new IllegalStateException("Failed to load bundled Quarkus platform", e);
-        }
-    }
-
-    private void ensureLoggerInitialized() {
-        if (log == null) {
-            log = MessageWriter.info();
-        }
-    }
-
-    private QuarkusPlatformDescriptor loadFromFile(AppModelResolver artifactResolver, Path jsonFile)
-            throws PlatformDescriptorLoadingException, VersionNotAvailableException {
-        log.debug("Loading Quarkus platform descriptor from %s", jsonFile);
-        if (!Files.exists(jsonFile)) {
-            throw new IllegalArgumentException("Failed to locate extensions JSON file at " + jsonFile);
-        }
-
-        // Resolve the Quarkus version used by the platform
-        final String quarkusCoreVersion;
-        try (BufferedReader reader = Files.newBufferedReader(jsonFile)) {
-            JsonNode node = new ObjectMapper().readTree(reader);
-            quarkusCoreVersion = node.get("quarkus-core-version").asText(null);
-            if (quarkusCoreVersion == null) {
-                throw new IllegalStateException("Failed to determine the Quarkus Core version for " + jsonFile);
-            }
-        } catch (RuntimeException | IOException e) {
-            throw new PlatformDescriptorLoadingException("Failed to parse extensions JSON file " + jsonFile, e);
-        }
-        log.debug("Loaded Quarkus platform is based on Quarkus %s", quarkusCoreVersion);
-
-        try (InputStream is = Files.newInputStream(jsonFile)) {
-            return loadPlatformDescriptor(toLoaderResolver(artifactResolver), is, quarkusCoreVersion);
-        } catch (VersionNotAvailableException e) {
-            throw e;
-        } catch (Exception e) {
-            throw new PlatformDescriptorLoadingException("Failed to load Quarkus platform descriptor from " + jsonFile, e);
-        }
-    }
-
-    private QuarkusPlatformDescriptor resolveJsonDescriptor(AppModelResolver artifactResolver)
-            throws PlatformDescriptorLoadingException {
-        if (!ToolsUtils.isNullOrEmpty(bomGroupId) || !ToolsUtils.isNullOrEmpty(bomArtifactId)
-                || !ToolsUtils.isNullOrEmpty(bomVersion) || !ToolsUtils.isNullOrEmpty(bomVersionRange)) {
-            if (log.isDebugEnabled()) {
-                final StringBuilder buf = new StringBuilder();
-                buf.append("Resolving Quarkus platform descriptor from the provided BOM coordinates ");
-                appendArg(buf, bomGroupId);
-                buf.append(":");
-                appendArg(buf, bomArtifactId);
-                buf.append(":");
-                if (!ToolsUtils.isNullOrEmpty(bomVersion)) {
-                    appendArg(buf, bomVersion);
-                } else if (!ToolsUtils.isNullOrEmpty(bomVersionRange)) {
-                    appendArg(buf, bomVersionRange);
-                } else {
-                    appendArg(buf, bomVersion);
-                }
-                log.debug(buf.toString());
-            }
-            return resolveJsonArtifactFromBom(artifactResolver);
-        }
-        try {
-            return resolveJsonArtifactFromArgs(artifactResolver);
-        } catch (VersionNotAvailableException e) {
-            final QuarkusPlatformDescriptor platform = resolveJsonArtifactFromBom(artifactResolver);
-            log.warn(e.getLocalizedMessage() + ", falling back to the bundled platform based on " + platform.getBomGroupId()
-                    + ":" + platform.getBomArtifactId() + "::pom:" + platform.getBomVersion() + " and Quarkus version "
-                    + platform.getQuarkusVersion());
-            return platform;
-        }
-    }
-
-    private static void appendArg(StringBuilder buf, String arg) {
-        buf.append(ToolsUtils.isNullOrEmpty(arg) ? "<not-provided>" : arg);
-    }
-
-    private QuarkusPlatformDescriptor resolveJsonArtifactFromArgs(AppModelResolver artifactResolver)
-            throws VersionNotAvailableException {
-        String jsonGroupId = this.jsonGroupId;
-        String jsonArtifactId = this.jsonArtifactId;
-        String jsonClassifier = this.jsonClassifier;
-        String jsonVersion = this.jsonVersion;
-        // If some of the coordinates are missing, we are trying the default ones
-        int defaultCoords = 0;
-        if (jsonGroupId == null) {
-            jsonGroupId = getProperty(PROP_PLATFORM_JSON_GROUP_ID);
-            if (jsonGroupId == null) {
-                jsonGroupId = ToolsConstants.DEFAULT_PLATFORM_BOM_GROUP_ID;
-                ++defaultCoords;
-            }
-        }
-        boolean artifactIdProvided = jsonArtifactId != null;
-        if (!artifactIdProvided) {
-            jsonArtifactId = getProperty(PROP_PLATFORM_JSON_ARTIFACT_ID);
-            artifactIdProvided = jsonArtifactId != null;
-            if (!artifactIdProvided) {
-                jsonArtifactId = ToolsConstants.DEFAULT_PLATFORM_BOM_ARTIFACT_ID;
-                ++defaultCoords;
-            }
-        }
-        boolean versionProvided = jsonVersion != null;
-        if (!versionProvided) {
-            if (jsonVersionRange != null) {
-                // if the range was set using the api, it overrides a possibly set version system property
-                // depending on how this evolves this may or may not be reasonable
-                try {
-                    jsonVersion = resolveLatestJsonVersion(artifactResolver, jsonGroupId, jsonArtifactId, jsonVersionRange);
-                } catch (VersionNotAvailableException e) {
-                    throw new IllegalStateException("Failed to resolve the latest version of " + jsonGroupId + ":"
-                            + jsonArtifactId + " from the requested range " + jsonVersionRange, e);
-                }
-                versionProvided = true;
-            } else {
-                jsonVersion = getProperty(PROP_PLATFORM_JSON_VERSION);
-                versionProvided = jsonVersion != null;
-                if (!versionProvided) {
-                    jsonVersion = resolveLatestJsonVersion(artifactResolver, jsonGroupId, jsonArtifactId, null);
-                    ++defaultCoords;
-                }
-            }
-        }
-        if (jsonClassifier == null) {
-            jsonClassifier = jsonVersion;
-        }
-        final AppArtifact jsonArtifact = new AppArtifact(jsonGroupId, jsonArtifactId, jsonClassifier, "json", jsonVersion);
-        if (artifactIdProvided) {
-            try {
-                return loadFromFile(artifactResolver, artifactResolver.resolve(jsonArtifact));
-            } catch (PlatformDescriptorLoadingException e) {
-                // the artifact was successfully resolved but processing of it has failed
-                throw new IllegalStateException("Failed to load Quarkus platform descriptor " + jsonArtifact, e);
-            } catch (Exception e) {
-                if (!versionProvided && e instanceof VersionNotAvailableException) {
-                    throw (VersionNotAvailableException) e;
-                }
-                throw new IllegalStateException("Failed to resolve Quarkus platform descriptor " + jsonArtifact, e);
-            }
-        }
-        try {
-            return loadDescriptorForBom(artifactResolver, jsonArtifact);
-        } catch (VersionNotAvailableException e) {
-            if (defaultCoords == 3) {
-                // complete coords were the default ones, so we can re-throw and try the bundled platform
-                throw e;
-            }
-            throw new IllegalStateException("Failed to resolve the JSON artifact with the requested coordinates", e);
-        }
-    }
-
-    private QuarkusPlatformDescriptor resolveJsonArtifactFromBom(AppModelResolver artifactResolver)
-            throws PlatformDescriptorLoadingException {
-
-        // If some of the coordinates are missing, we are trying the default ones
-        boolean tryingDefaultCoords = false;
-        String bomGroupId = this.bomGroupId;
-        if (bomGroupId == null) {
-            bomGroupId = ToolsConstants.DEFAULT_PLATFORM_BOM_GROUP_ID;
-            tryingDefaultCoords = true;
-        }
-        String bomArtifactId = this.bomArtifactId;
-        if (bomArtifactId == null) {
-            bomArtifactId = ToolsConstants.DEFAULT_PLATFORM_BOM_ARTIFACT_ID;
-            tryingDefaultCoords = true;
-        }
-        String bomVersion = this.bomVersion;
-        try {
-            return loadFromBomCoords(artifactResolver, bomGroupId, bomArtifactId, bomVersion, null);
-        } catch (VersionNotAvailableException e) {
-            if (!tryingDefaultCoords) {
-                throw new IllegalStateException("Failed to resolve the platform BOM using the provided coordinates", e);
-            }
-            log.debug(
-                    "Failed to resolve Quarkus platform BOM using the default coordinates %s:%s:%s, falling back to the bundled Quarkus platform artifacts",
-                    bomGroupId, bomArtifactId, bomVersion);
-        }
-
-        Model bundledBom = loadBundledPom();
-        bomGroupId = this.bomGroupId;
-        if (bomGroupId != null) {
-            if (!bomGroupId.equals(getGroupId(bundledBom))) {
-                throw new IllegalStateException(
-                        "Failed to resolve Quarkus platform using the requested BOM groupId " + bomGroupId);
-            }
-        } else {
-            bomGroupId = getGroupId(bundledBom);
-        }
-        if (bomGroupId == null) {
-            failedDetermineDefaultPlatformCoords();
-        }
-
-        bomArtifactId = this.bomArtifactId;
-        if (bomArtifactId != null) {
-            if (!bomArtifactId.equals(getArtifactId(bundledBom))) {
-                throw new IllegalStateException(
-                        "Failed to resolve Quarkus platform using the requested BOM artifactId " + bomArtifactId);
-            }
-        } else {
-            bomArtifactId = getArtifactId(bundledBom);
-        }
-        if (bomArtifactId == null) {
-            failedDetermineDefaultPlatformCoords();
-        }
-
-        bomVersion = this.bomVersion;
-        if (bomVersion != null) {
-            if (!bomVersion.equals(getVersion(bundledBom))) {
-                throw new IllegalStateException(
-                        "Failed to resolve Quarkus platform using the requested BOM version " + bomVersion);
-            }
-        } else if (this.bomVersionRange == null) {
-            bomVersion = getVersion(bundledBom);
-        }
-        try {
-            return loadFromBomCoords(artifactResolver, bomGroupId, bomArtifactId, bomVersion, bundledBom);
-        } catch (VersionNotAvailableException e) {
-            // this should never happen
-            throw new IllegalStateException("Failed to load the bundled platform artifacts", e);
-        }
-    }
-
-    private QuarkusPlatformDescriptor loadFromBomCoords(AppModelResolver artifactResolver, String bomGroupId,
-            String bomArtifactId, String bomVersion, Model bundledBom)
-            throws PlatformDescriptorLoadingException, VersionNotAvailableException {
-        if (bomVersion == null) {
-            String bomVersionRange = this.bomVersionRange;
-            if (bomVersionRange == null) {
-                bomVersionRange = getDefaultVersionRange(bomGroupId, bomArtifactId);
-            }
-            final AppArtifact bomArtifact = new AppArtifact(bomGroupId, bomArtifactId, null, "pom", bomVersionRange);
-            log.debug("Resolving the latest version of %s", bomArtifact);
-            try {
-                bomVersion = artifactResolver.getLatestVersionFromRange(bomArtifact, bomVersionRange);
-            } catch (AppModelResolverException e) {
-                throw new VersionNotAvailableException("Failed to resolve the latest version of " + bomArtifact, e);
-            }
-            if (bomVersion == null) {
-                throw new VersionNotAvailableException("Failed to resolve the latest version of " + bomArtifact);
-            }
-        }
-        log.debug("Resolving Quarkus platform BOM %s:%s::pom:%s", bomGroupId, bomArtifactId, bomVersion);
-
-        final Model theBundledBom = loadBundledPomIfNull(bundledBom);
-        // Check whether the BOM on the classpath is matching the requested one
-        if (theBundledBom != null
-                && bomArtifactId.equals(getArtifactId(theBundledBom))
-                && bomVersion.equals(getVersion(theBundledBom))
-                && bomGroupId.equals(getGroupId(theBundledBom))) {
-            log.debug("The requested Quarkus platform BOM version is available on the classpath");
-            // If the BOM matches, there should also be the JSON file
-            final InputStream jsonStream = getCpResourceAsStream(BUNDLED_EXTENSIONS_JSON_PATH);
-            if (jsonStream != null) {
-                // The JSON is available, now there also should be quarkus.properties
-                final String quarkusVersion = getBundledPlatformQuarkusVersionOrNull();
-                if (quarkusVersion != null) {
-                    return loadPlatformDescriptor(getBundledResolver(theBundledBom), jsonStream, quarkusVersion);
-                } else {
-                    log.debug("Failed to locate quarkus.properties on the classpath");
-                }
-            } else {
-                log.debug("Failed to locate Quarkus platform descriptor on the classpath");
-            }
-        }
-
-        return loadDescriptorForBom(artifactResolver, new AppArtifact(bomGroupId, bomArtifactId, null, "json", bomVersion));
-    }
-
-    private void failedDetermineDefaultPlatformCoords() {
-        throw new IllegalStateException("Failed to determine the Maven coordinates of the default Quarkus platform");
-    }
-
-    private QuarkusPlatformDescriptor loadDescriptorForBom(AppModelResolver artifactResolver, AppArtifact jsonArtifact)
-            throws VersionNotAvailableException {
-        final Path jsonFile;
-        try {
-            jsonFile = jsonDescriptorResolver.jsonForBom(jsonArtifact.getGroupId(), jsonArtifact.getArtifactId(),
-                    jsonArtifact.getVersion(),
-                    (groupId, artifactId, classifier, type, version, log) -> {
-                        log.debug("Attempting to resolve Quarkus platform descriptor %s:%s:%s:%s:%s", groupId,
-                                artifactId, classifier == null ? "" : classifier, type, version);
-                        return artifactResolver
-                                .resolve(new AppArtifact(groupId, artifactId, classifier, type, version));
-                    },
-                    log);
-        } catch (Exception e) {
-            throw new VersionNotAvailableException("Failed to resolve Quarkus platform descriptor for BOM " + jsonArtifact, e);
-        }
-        try {
-            return loadFromFile(artifactResolver, jsonFile);
-        } catch (PlatformDescriptorLoadingException e) {
-            throw new IllegalStateException("Failed to load Quarkus platform descriptor " + jsonFile, e);
-        }
-    }
-
-    @SuppressWarnings("rawtypes")
-    private QuarkusPlatformDescriptor loadPlatformDescriptor(ArtifactResolver mvn, final InputStream jsonStream,
-            String quarkusCoreVersion) throws PlatformDescriptorLoadingException, VersionNotAvailableException {
-
-        ClassLoader jsonDescrLoaderCl = null;
-
-        // check whether the quarkus-platform-descriptor-json used in the platform is already on the classpath
-        final String pomPropsPath = "META-INF/maven/" + ToolsConstants.IO_QUARKUS + "/" + QUARKUS_PLATFORM_DESCRIPTOR_JSON
-                + "/pom.properties";
-        final InputStream is = getCpResourceAsStream(pomPropsPath);
-        if (is != null) {
-            final Properties props = new Properties();
-            try {
-                props.load(is);
-            } catch (IOException e) {
-                throw new PlatformDescriptorLoadingException("Failed to load " + pomPropsPath + " from the classpath", e);
-            }
-            final String version = props.getProperty("version");
-            if (quarkusCoreVersion.equals(version)) {
-                jsonDescrLoaderCl = Thread.currentThread().getContextClassLoader();
-            } else {
-                log.debug("Version of the Quarkus JSON platform descriptor loader on the classpath is %s", version);
-            }
-        }
-
-        // platform resource loader
-        ResourceLoader resourceLoader = null;
-
-        boolean externalLoader = false;
-        if (jsonDescrLoaderCl == null) {
-            final AppArtifact jsonDescrArtifact = new AppArtifact(ToolsConstants.IO_QUARKUS, QUARKUS_PLATFORM_DESCRIPTOR_JSON,
-                    null, "jar", quarkusCoreVersion);
-            log.debug("Resolving Quarkus JSON platform descriptor loader %s", jsonDescrArtifact);
-            final URL jsonDescrUrl;
-            try {
-                final Path path = mvn.process(jsonDescrArtifact.getGroupId(), jsonDescrArtifact.getArtifactId(),
-                        jsonDescrArtifact.getClassifier(), jsonDescrArtifact.getType(), jsonDescrArtifact.getVersion(), p -> {
-                            return p;
-                        });
-                resourceLoader = Files.isDirectory(path) ? new DirectoryResourceLoader(path) : new ZipResourceLoader(path);
-                log.debug("Quarkus platform resources will be loaded from %s", path);
-                jsonDescrUrl = path.toUri().toURL();
-            } catch (AppModelResolverException e) {
-                throw new VersionNotAvailableException("Failed to resolve " + jsonDescrArtifact, e);
-            } catch (Exception e) {
-                throw new PlatformDescriptorLoadingException("Failed to resolve " + jsonDescrArtifact, e);
-            }
-            jsonDescrLoaderCl = new URLClassLoader(new URL[] { jsonDescrUrl }, Thread.currentThread().getContextClassLoader());
-            externalLoader = true;
-        }
-
-        try {
-            final Iterator<QuarkusJsonPlatformDescriptorLoader> i = ServiceLoader
-                    .load(QuarkusJsonPlatformDescriptorLoader.class, jsonDescrLoaderCl).iterator();
-            if (!i.hasNext()) {
-                throw new PlatformDescriptorLoadingException(
-                        "Failed to locate an implementation of " + QuarkusJsonPlatformDescriptorLoader.class.getName());
-            }
-            final QuarkusJsonPlatformDescriptorLoader<?> jsonDescrLoader = i.next();
-            if (i.hasNext()) {
-                throw new PlatformDescriptorLoadingException(
-                        "Located more than one implementation of " + QuarkusJsonPlatformDescriptorLoader.class.getName());
-            }
-
-            try {
-                return jsonDescrLoader.load(
-                        new QuarkusJsonPlatformDescriptorLoaderContext(
-                                mvn,
-                                resourceLoader == null ? new ClassPathResourceLoader() : resourceLoader,
-                                log) {
-                            @Override
-                            public <T> T parseJson(Function<InputStream, T> parser) {
-                                return parser.apply(jsonStream);
-                            }
-                        });
-            } catch (Exception e) {
-                throw new PlatformDescriptorLoadingException("Failed to load Quarkus platform descriptor", e);
-            }
-        } finally {
-            if (externalLoader) {
-                try {
-                    ((URLClassLoader) jsonDescrLoaderCl).close();
-                } catch (IOException e) {
-                }
-            }
-        }
-    }
-
-    private String resolveLatestJsonVersion(AppModelResolver artifactResolver, String groupId, String artifactId,
-            String versionRange) throws VersionNotAvailableException {
-        if (versionRange == null) {
-            versionRange = getProperty(PROP_PLATFORM_JSON_VERSION_RANGE);
-            if (versionRange == null) {
-                versionRange = getDefaultVersionRange(groupId, artifactId);
-            }
-        }
-        try {
-            return resolveLatestFromVersionRange(artifactResolver, groupId, artifactId, null, "json", versionRange);
-        } catch (AppModelResolverException e) {
-            throw new VersionNotAvailableException("Failed to resolve the latest JSON platform version of " + groupId + ":"
-                    + artifactId + "::json:" + versionRange);
-        }
-    }
-
-    private String resolveLatestFromVersionRange(AppModelResolver mvn, String groupId, String artifactId, String classifier,
-            String type, final String versionRange)
-            throws AppModelResolverException, VersionNotAvailableException {
-        final AppArtifact appArtifact = new AppArtifact(groupId, artifactId, classifier, type, versionRange);
-        log.debug("Resolving the latest version of %s", appArtifact);
-        final String latestVersion = mvn.getLatestVersionFromRange(appArtifact, versionRange);
-        if (latestVersion == null) {
-            throw new VersionNotAvailableException("Failed to resolve the latest version of " + appArtifact);
-        }
-        return latestVersion;
-    }
-
-    private Model loadBundledPomIfNull(Model model) {
-        return model == null ? loadBundledPom() : model;
-    }
-
-    private Model loadBundledPom() {
-        final InputStream bomIs = getCpResourceAsStream(BUNDLED_QUARKUS_BOM_PATH);
-        if (bomIs == null) {
-            log.debug("Failed to locate quarkus-bom/pom.xml on the classpath");
-            return null;
-        }
-        try {
-            return MojoUtils.readPom(bomIs);
-        } catch (IOException e) {
-            throw new IllegalStateException("Failed to load POM model from the classpath for quarkus-bom/pom.xml", e);
-        } finally {
-            try {
-                bomIs.close();
-            } catch (IOException e) {
-            }
-        }
-    }
-
-    private static String getGroupId(Model model) {
-        if (model == null) {
-            return null;
-        }
-        String groupId = model.getGroupId();
-        if (groupId != null) {
-            return groupId;
-        }
-        final Parent parent = model.getParent();
-        if (parent != null) {
-            groupId = parent.getGroupId();
-            if (groupId != null) {
-                return groupId;
-            }
-        }
-        throw new IllegalStateException("Failed to determine the groupId for the POM of " + model.getArtifactId());
-    }
-
-    private static String getArtifactId(Model model) {
-        if (model == null) {
-            return null;
-        }
-        return model.getArtifactId();
-    }
-
-    private static String getVersion(Model model) {
-        if (model == null) {
-            return null;
-        }
-        String version = model.getVersion();
-        if (version != null) {
-            return version;
-        }
-        final Parent parent = model.getParent();
-        if (parent != null) {
-            version = parent.getVersion();
-            if (version != null) {
-                return version;
-            }
-        }
-        throw new IllegalStateException("Failed to determine the version for the POM of " + model.getArtifactId());
-    }
-
-    private ArtifactResolver toLoaderResolver(AppModelResolver mvn) {
-        return new ArtifactResolver() {
-
-            @Override
-            public <T> T process(String groupId, String artifactId, String classifier, String type, String version,
-                    Function<Path, T> processor) throws AppModelResolverException {
-                final AppArtifact artifact = new AppArtifact(groupId, artifactId, classifier, type, version);
-                return processor.apply(mvn.resolve(artifact));
-            }
-        };
-    }
-
-    private ArtifactResolver getBundledResolver(final Model model) {
-
-        final Path platformResources;
-        try {
-            platformResources = MojoUtils.getResourceOrigin(Thread.currentThread().getContextClassLoader(),
-                    BUNDLED_QUARKUS_BOM_PATH);
-        } catch (IOException e) {
-            throw new IllegalStateException("Failed to locate the bundled Quarkus platform resources on the classpath");
-        }
-
-        final ArtifactResolver bundledResolver = new ArtifactResolver() {
-
-            @Override
-            public <T> T process(String groupId, String artifactId, String classifier, String type, String version,
-                    Function<Path, T> processor) {
-                if (QUARKUS_PLATFORM_DESCRIPTOR_JSON.equals(artifactId)
-                        && ToolsConstants.IO_QUARKUS.equals(groupId)
-                        && "jar".equals(type)
-                        && StringUtils.isEmpty(classifier)
-                        && version.equals(getBundledPlatformQuarkusVersionOrNull())) {
-                    return processor.apply(platformResources);
-                }
-                throw new IllegalArgumentException("Unexpected artifact coordinates " + groupId + ":" + artifactId + ":"
-                        + classifier + ":" + type + ":" + version);
-            }
-        };
-        return bundledResolver;
-    }
-
-    private static String getBundledPlatformQuarkusVersionOrNull() {
-        final InputStream quarkusPropsStream = getCpResourceAsStream(BUNDLED_QUARKUS_PROPERTIES_PATH);
-        if (quarkusPropsStream == null) {
-            return null;
-        }
-        final Properties props = new Properties();
-        try {
-            props.load(quarkusPropsStream);
-        } catch (IOException e) {
-            throw new IllegalStateException("Failed to load quarkus.properties from the classpath", e);
-        }
-        return ToolsUtils.requireQuarkusCoreVersion(props);
-    }
-
-    private static InputStream getCpResourceAsStream(String name) {
-        return Thread.currentThread().getContextClassLoader().getResourceAsStream(name);
-    }
-}
diff --git a/independent-projects/tools/platform-descriptor-resolver-json/src/main/java/io/quarkus/platform/descriptor/resolver/json/VersionNotAvailableException.java b/independent-projects/tools/platform-descriptor-resolver-json/src/main/java/io/quarkus/platform/descriptor/resolver/json/VersionNotAvailableException.java
deleted file mode 100644
index cea8276f639917..00000000000000
--- a/independent-projects/tools/platform-descriptor-resolver-json/src/main/java/io/quarkus/platform/descriptor/resolver/json/VersionNotAvailableException.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package io.quarkus.platform.descriptor.resolver.json;
-
-public class VersionNotAvailableException extends Exception {
-
-    private static final long serialVersionUID = 1L;
-
-    public VersionNotAvailableException(String message, Throwable cause) {
-        super(message, cause);
-    }
-
-    public VersionNotAvailableException(String message) {
-        super(message);
-    }
-}
diff --git a/independent-projects/tools/platform-descriptor-resolver-json/src/test/java/io/quarkus/platform/descriptor/resolver/json/test/QuarkusJsonPlatformDescriptorResolverTest.java b/independent-projects/tools/platform-descriptor-resolver-json/src/test/java/io/quarkus/platform/descriptor/resolver/json/test/QuarkusJsonPlatformDescriptorResolverTest.java
deleted file mode 100644
index d077b1e4668815..00000000000000
--- a/independent-projects/tools/platform-descriptor-resolver-json/src/test/java/io/quarkus/platform/descriptor/resolver/json/test/QuarkusJsonPlatformDescriptorResolverTest.java
+++ /dev/null
@@ -1,208 +0,0 @@
-package io.quarkus.platform.descriptor.resolver.json.test;
-
-import static io.quarkus.platform.tools.ToolsConstants.DEFAULT_PLATFORM_BOM_ARTIFACT_ID;
-import static io.quarkus.platform.tools.ToolsConstants.DEFAULT_PLATFORM_BOM_GROUP_ID;
-import static io.quarkus.platform.tools.ToolsConstants.IO_QUARKUS;
-import static io.quarkus.platform.tools.ToolsConstants.QUARKUS_CORE_ARTIFACT_ID;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-
-import io.quarkus.bootstrap.BootstrapConstants;
-import io.quarkus.bootstrap.model.AppArtifact;
-import io.quarkus.bootstrap.resolver.ResolverSetupCleanup;
-import io.quarkus.bootstrap.resolver.TsArtifact;
-import io.quarkus.bootstrap.resolver.TsDependency;
-import io.quarkus.bootstrap.util.IoUtils;
-import io.quarkus.devtools.messagewriter.MessageWriter;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
-import io.quarkus.platform.descriptor.resolver.json.QuarkusJsonPlatformDescriptorResolver;
-import io.quarkus.platform.tools.ToolsConstants;
-import java.nio.file.Path;
-import org.junit.jupiter.api.AfterAll;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-public class QuarkusJsonPlatformDescriptorResolverTest extends ResolverSetupCleanup {
-
-    private static Path testDir;
-
-    private MessageWriter log = MessageWriter.info();
-
-    @BeforeEach
-    @Override
-    public void setup() throws Exception {
-        if (workDir != null) {
-            return;
-        }
-        super.setup();
-        testDir = workDir;
-        doSetup();
-    }
-
-    @Override
-    protected boolean cleanWorkDir() {
-        return false;
-    }
-
-    @AfterAll
-    public static void afterAll() throws Exception {
-        if (testDir != null) {
-            IoUtils.recursiveDelete(testDir);
-        }
-    }
-
-    protected void doSetup() throws Exception {
-
-        final TsArtifact quarkusCore = new TsArtifact(IO_QUARKUS, QUARKUS_CORE_ARTIFACT_ID, null, "jar", "1.0.0.CR50");
-        install(quarkusCore, newJar().getPath(workDir));
-
-        final TsArtifact quarkusPlatformDescriptorJson = new TsArtifact(IO_QUARKUS, "quarkus-platform-descriptor-json", null,
-                "jar", "1.0.0.CR50");
-        install(quarkusPlatformDescriptorJson, newJar().getPath(workDir));
-
-        // install a few universe versions with the default GA
-        installDefaultUniverseLegacyArtifactId(quarkusCore, "1.0.0.CR50");
-        installDefaultUniverseLegacyArtifactId(quarkusCore, "1.0.0.CR60");
-        installDefaultUniverseLegacyArtifactId(quarkusCore, "1.0.0.CR70");
-
-        installDefaultUniverse(quarkusCore, "1.0.0.CR80");
-
-        // install a universe with a custom GA and JSON descriptor with `-descriptor-json` suffix
-        TsArtifact universeBom = new TsArtifact(DEFAULT_PLATFORM_BOM_GROUP_ID, "other-universe", null, "pom", "1.0.0.CR80")
-                .addManagedDependency(new TsDependency(quarkusCore));
-        install(universeBom);
-        final TsArtifact universeJson = new TsArtifact(DEFAULT_PLATFORM_BOM_GROUP_ID, "other-universe" + "-descriptor-json",
-                null, "json", "1.0.0.CR80")
-                        .setContent(new TestPlatformJsonDescriptorProvider(universeBom));
-        install(universeJson);
-    }
-
-    @Test
-    public void testResolveBundled() throws Exception {
-        final QuarkusPlatformDescriptor platform = newResolver().resolveBundled();
-        assertBundledPlatform(platform, "1.0.0.CR90");
-        assertEquals("1.0.0.CR90", platform.getQuarkusVersion());
-    }
-
-    @Test
-    public void testResolve() throws Exception {
-        final QuarkusPlatformDescriptor platform = newResolver().resolve();
-        assertDefaultPlatform(platform, "1.0.0.CR80");
-        assertEquals("1.0.0.CR50", platform.getQuarkusVersion());
-    }
-
-    @Test
-    public void testResolveFromJsonVersion() throws Exception {
-        final QuarkusPlatformDescriptor platform = newResolver().resolveFromJson(DEFAULT_PLATFORM_BOM_GROUP_ID,
-                DEFAULT_PLATFORM_BOM_ARTIFACT_ID, "1.0.0.CR60");
-        assertDefaultPlatform(platform, "1.0.0.CR60");
-        assertEquals("1.0.0.CR50", platform.getQuarkusVersion());
-    }
-
-    @Test
-    public void testResolveFromJsonFile() throws Exception {
-        final Path jsonPath = resolver.resolve(
-                new AppArtifact(DEFAULT_PLATFORM_BOM_GROUP_ID, DEFAULT_PLATFORM_BOM_ARTIFACT_ID, null, "json", "1.0.0.CR50"));
-        final QuarkusPlatformDescriptor platform = newResolver().resolveFromJson(jsonPath);
-        assertDefaultPlatform(platform, "1.0.0.CR50");
-        assertEquals("1.0.0.CR50", platform.getQuarkusVersion());
-    }
-
-    @Test
-    public void testResolveFromBomWithDescriptorJsonPrefix() throws Exception {
-        final QuarkusPlatformDescriptor platform = newResolver().resolveFromBom(DEFAULT_PLATFORM_BOM_GROUP_ID, "other-universe",
-                "1.0.0.CR80");
-        assertNotNull(platform);
-        assertEquals(ToolsConstants.IO_QUARKUS, platform.getBomGroupId());
-        assertEquals("other-universe", platform.getBomArtifactId());
-        assertEquals("1.0.0.CR80", platform.getBomVersion());
-        assertEquals("1.0.0.CR50", platform.getQuarkusVersion());
-    }
-
-    @Test
-    public void testResolveFromJsonWithDescriptorJsonPrefix() throws Exception {
-        final QuarkusPlatformDescriptor platform = newResolver().resolveLatestFromJson(DEFAULT_PLATFORM_BOM_GROUP_ID,
-                "other-universe" + "-descriptor-json", null);
-        assertNotNull(platform);
-        assertEquals(ToolsConstants.IO_QUARKUS, platform.getBomGroupId());
-        assertEquals("other-universe", platform.getBomArtifactId());
-        assertEquals("1.0.0.CR80", platform.getBomVersion());
-        assertEquals("1.0.0.CR50", platform.getQuarkusVersion());
-    }
-
-    @Test
-    public void testResolveFromLatestJson() throws Exception {
-        final QuarkusPlatformDescriptor platform = newResolver().resolveLatestFromJson(DEFAULT_PLATFORM_BOM_GROUP_ID,
-                DEFAULT_PLATFORM_BOM_ARTIFACT_ID + BootstrapConstants.PLATFORM_DESCRIPTOR_ARTIFACT_ID_SUFFIX, null, null);
-        assertDefaultPlatform(platform, "1.0.0.CR80");
-        assertEquals("1.0.0.CR50", platform.getQuarkusVersion());
-    }
-
-    @Test
-    public void testResolveFromLatestJsonWithRange() throws Exception {
-        final QuarkusPlatformDescriptor platform = newResolver().resolveLatestFromJson(DEFAULT_PLATFORM_BOM_GROUP_ID,
-                DEFAULT_PLATFORM_BOM_ARTIFACT_ID, "[0,1.0.0.CR60]");
-        assertDefaultPlatform(platform, "1.0.0.CR60");
-        assertEquals("1.0.0.CR50", platform.getQuarkusVersion());
-    }
-
-    @Test
-    public void testResolveFromLatestBom() throws Exception {
-        final QuarkusPlatformDescriptor platform = newResolver().resolveLatestFromBom(DEFAULT_PLATFORM_BOM_GROUP_ID,
-                DEFAULT_PLATFORM_BOM_ARTIFACT_ID, null);
-        assertDefaultPlatform(platform, "1.0.0.CR80");
-        assertEquals("1.0.0.CR50", platform.getQuarkusVersion());
-    }
-
-    @Test
-    public void testResolveFromLatestBomWithRange() throws Exception {
-        final QuarkusPlatformDescriptor platform = newResolver().resolveLatestFromJson(DEFAULT_PLATFORM_BOM_GROUP_ID,
-                DEFAULT_PLATFORM_BOM_ARTIFACT_ID, "[0,1.0.0.CR60]");
-        assertDefaultPlatform(platform, "1.0.0.CR60");
-        assertEquals("1.0.0.CR50", platform.getQuarkusVersion());
-    }
-
-    private void installDefaultUniverseLegacyArtifactId(final TsArtifact quarkusCore, String platformVersion) {
-        final TsArtifact universeBom = new TsArtifact(DEFAULT_PLATFORM_BOM_GROUP_ID, DEFAULT_PLATFORM_BOM_ARTIFACT_ID, null,
-                "pom", platformVersion)
-                        .addManagedDependency(new TsDependency(quarkusCore));
-        install(universeBom);
-        final TsArtifact universeJson = new TsArtifact(DEFAULT_PLATFORM_BOM_GROUP_ID, DEFAULT_PLATFORM_BOM_ARTIFACT_ID, null,
-                "json", platformVersion)
-                        .setContent(new TestPlatformJsonDescriptorProvider(universeBom));
-        install(universeJson);
-    }
-
-    private void installDefaultUniverse(final TsArtifact quarkusCore, String platformVersion) {
-        final TsArtifact universeBom = new TsArtifact(DEFAULT_PLATFORM_BOM_GROUP_ID, DEFAULT_PLATFORM_BOM_ARTIFACT_ID, null,
-                "pom", platformVersion)
-                        .addManagedDependency(new TsDependency(quarkusCore));
-        install(universeBom);
-        final TsArtifact universeJson = new TsArtifact(DEFAULT_PLATFORM_BOM_GROUP_ID,
-                DEFAULT_PLATFORM_BOM_ARTIFACT_ID + BootstrapConstants.PLATFORM_DESCRIPTOR_ARTIFACT_ID_SUFFIX,
-                platformVersion,
-                "json", platformVersion)
-                        .setContent(new TestPlatformJsonDescriptorProvider(universeBom));
-        install(universeJson);
-    }
-
-    private QuarkusJsonPlatformDescriptorResolver newResolver() {
-        return QuarkusJsonPlatformDescriptorResolver.newInstance()
-                .setMessageWriter(log)
-                .setArtifactResolver(resolver);
-    }
-
-    private static void assertDefaultPlatform(QuarkusPlatformDescriptor platform, String version) {
-        assertNotNull(platform);
-        assertEquals(ToolsConstants.IO_QUARKUS, platform.getBomGroupId());
-        assertEquals(ToolsConstants.DEFAULT_PLATFORM_BOM_ARTIFACT_ID, platform.getBomArtifactId());
-        assertEquals(version, platform.getBomVersion());
-    }
-
-    private static void assertBundledPlatform(QuarkusPlatformDescriptor platform, String version) {
-        assertNotNull(platform);
-        assertEquals(ToolsConstants.IO_QUARKUS, platform.getBomGroupId());
-        assertEquals("quarkus-bom", platform.getBomArtifactId());
-        assertEquals(version, platform.getBomVersion());
-    }
-}
diff --git a/independent-projects/tools/platform-descriptor-resolver-json/src/test/java/io/quarkus/platform/descriptor/resolver/json/test/TestJsonPlatformDescriptorLoader.java b/independent-projects/tools/platform-descriptor-resolver-json/src/test/java/io/quarkus/platform/descriptor/resolver/json/test/TestJsonPlatformDescriptorLoader.java
deleted file mode 100644
index 8ccd665066d5fd..00000000000000
--- a/independent-projects/tools/platform-descriptor-resolver-json/src/test/java/io/quarkus/platform/descriptor/resolver/json/test/TestJsonPlatformDescriptorLoader.java
+++ /dev/null
@@ -1,81 +0,0 @@
-package io.quarkus.platform.descriptor.resolver.json.test;
-
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import io.quarkus.dependencies.Category;
-import io.quarkus.dependencies.Extension;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
-import io.quarkus.platform.descriptor.ResourceInputStreamConsumer;
-import io.quarkus.platform.descriptor.ResourcePathConsumer;
-import io.quarkus.platform.descriptor.loader.json.QuarkusJsonPlatformDescriptorLoader;
-import io.quarkus.platform.descriptor.loader.json.QuarkusJsonPlatformDescriptorLoaderContext;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.nio.charset.StandardCharsets;
-import java.util.Collections;
-import java.util.List;
-
-public class TestJsonPlatformDescriptorLoader implements QuarkusJsonPlatformDescriptorLoader<QuarkusPlatformDescriptor> {
-
-    @Override
-    public QuarkusPlatformDescriptor load(QuarkusJsonPlatformDescriptorLoaderContext context) {
-        final JsonNode json = context.parseJson(s -> {
-            try (InputStreamReader reader = new InputStreamReader(s, StandardCharsets.UTF_8)) {
-                return new ObjectMapper().readTree(reader);
-            } catch (IOException e) {
-                throw new IllegalStateException("Failed to parse JSON descriptor", e);
-            }
-        });
-
-        final JsonNode bom = json.required("bom");
-        final String quarkusVersion = json.required("quarkus-core-version").asText();
-
-        return new QuarkusPlatformDescriptor() {
-
-            @Override
-            public String getBomGroupId() {
-                return bom.get("groupId").asText(null);
-            }
-
-            @Override
-            public String getBomArtifactId() {
-                return bom.get("artifactId").asText(null);
-            }
-
-            @Override
-            public String getBomVersion() {
-                return bom.get("version").asText(null);
-            }
-
-            @Override
-            public String getQuarkusVersion() {
-                return quarkusVersion;
-            }
-
-            @Override
-            public List<Extension> getExtensions() {
-                return Collections.emptyList();
-            }
-
-            @Override
-            public List<Category> getCategories() {
-                return Collections.emptyList();
-            }
-
-            @Override
-            public String getTemplate(String name) {
-                return null;
-            }
-
-            @Override
-            public <T> T loadResource(String name, ResourceInputStreamConsumer<T> consumer) throws IOException {
-                return null;
-            }
-
-            @Override
-            public <T> T loadResourceAsPath(String name, ResourcePathConsumer<T> consumer) throws IOException {
-                return null;
-            }
-        };
-    }
-}
diff --git a/independent-projects/tools/platform-descriptor-resolver-json/src/test/java/io/quarkus/platform/descriptor/resolver/json/test/TestPlatformJsonDescriptorProvider.java b/independent-projects/tools/platform-descriptor-resolver-json/src/test/java/io/quarkus/platform/descriptor/resolver/json/test/TestPlatformJsonDescriptorProvider.java
deleted file mode 100644
index 77673dd52c427e..00000000000000
--- a/independent-projects/tools/platform-descriptor-resolver-json/src/test/java/io/quarkus/platform/descriptor/resolver/json/test/TestPlatformJsonDescriptorProvider.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package io.quarkus.platform.descriptor.resolver.json.test;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.node.ObjectNode;
-import io.quarkus.bootstrap.resolver.TsArtifact;
-import io.quarkus.platform.tools.ToolsConstants;
-import java.io.BufferedWriter;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import org.apache.maven.model.Dependency;
-import org.apache.maven.model.Model;
-
-public class TestPlatformJsonDescriptorProvider implements TsArtifact.ContentProvider {
-
-    private final TsArtifact bomArtifact;
-
-    public TestPlatformJsonDescriptorProvider(TsArtifact bomArtifact) {
-        this.bomArtifact = bomArtifact;
-    }
-
-    @Override
-    public Path getPath(Path workDir) throws IOException {
-        final Model model = bomArtifact.getPomModel();
-
-        ObjectMapper mapper = new ObjectMapper();
-        ObjectNode json = mapper.createObjectNode();
-        json.set("bom",
-                mapper.createObjectNode().put("groupId", model.getGroupId())
-                        .put("artifactId", model.getArtifactId())
-                        .put("version", model.getVersion()));
-
-        String coreVersion = null;
-        for (Dependency dep : model.getDependencyManagement().getDependencies()) {
-            if (dep.getArtifactId().equals(ToolsConstants.QUARKUS_CORE_ARTIFACT_ID)
-                    && dep.getGroupId().equals(ToolsConstants.QUARKUS_CORE_GROUP_ID)) {
-                coreVersion = dep.getVersion();
-            }
-        }
-        if (coreVersion == null) {
-            throw new IllegalStateException("Failed to locate " + ToolsConstants.QUARKUS_CORE_GROUP_ID + ":"
-                    + ToolsConstants.QUARKUS_CORE_ARTIFACT_ID + " among the managed dependencies");
-        }
-        json.put("quarkus-core-version", coreVersion);
-
-        final Path jsonPath = workDir.resolve(bomArtifact.getArtifactFileName());
-        try (BufferedWriter writer = Files.newBufferedWriter(jsonPath)) {
-            mapper.writeValue(writer, json);
-        }
-        return jsonPath;
-    }
-}
diff --git a/independent-projects/tools/platform-descriptor-resolver-json/src/test/resources/META-INF/services/io.quarkus.platform.descriptor.loader.json.QuarkusJsonPlatformDescriptorLoader b/independent-projects/tools/platform-descriptor-resolver-json/src/test/resources/META-INF/services/io.quarkus.platform.descriptor.loader.json.QuarkusJsonPlatformDescriptorLoader
deleted file mode 100644
index 3698a45f9f4bdd..00000000000000
--- a/independent-projects/tools/platform-descriptor-resolver-json/src/test/resources/META-INF/services/io.quarkus.platform.descriptor.loader.json.QuarkusJsonPlatformDescriptorLoader
+++ /dev/null
@@ -1 +0,0 @@
-io.quarkus.platform.descriptor.resolver.json.test.TestJsonPlatformDescriptorLoader
\ No newline at end of file
diff --git a/independent-projects/tools/platform-descriptor-resolver-json/src/test/resources/quarkus-bom-descriptor/extensions.json b/independent-projects/tools/platform-descriptor-resolver-json/src/test/resources/quarkus-bom-descriptor/extensions.json
deleted file mode 100644
index 309c411e5344bb..00000000000000
--- a/independent-projects/tools/platform-descriptor-resolver-json/src/test/resources/quarkus-bom-descriptor/extensions.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-    "bom": {
-        "groupId": "io.quarkus",
-        "artifactId": "quarkus-bom",
-        "version": "1.0.0.CR90"
-    },
-    "quarkus-core-version": "1.0.0.CR90",
-    "extensions": []
-}
\ No newline at end of file
diff --git a/independent-projects/tools/platform-descriptor-resolver-json/src/test/resources/quarkus-bom/pom.xml b/independent-projects/tools/platform-descriptor-resolver-json/src/test/resources/quarkus-bom/pom.xml
deleted file mode 100644
index caa7967f6ca081..00000000000000
--- a/independent-projects/tools/platform-descriptor-resolver-json/src/test/resources/quarkus-bom/pom.xml
+++ /dev/null
@@ -1,470 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <modelVersion>4.0.0</modelVersion>
-    <groupId>io.quarkus</groupId>
-    <artifactId>quarkus-bom</artifactId>
-    <version>1.0.0.CR90</version>
-    <name>Quarkus - BOM</name>
-    <packaging>pom</packaging>
-
-    <dependencyManagement>
-        <dependencies>
-            <!-- External BOMs -->
-
-            <!-- Quarkus core -->
-
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-core</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <!--
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-arc</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-caffeine</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-jaxb</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-jackson</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-jsonb</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-jsonp</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-netty</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-agroal</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-artemis-core</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-artemis-jms</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-elasticsearch-rest-client</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-security</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-elytron-security</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-elytron-security-properties-file</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-elytron-security-oauth2</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-oidc</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-keycloak-authorization</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-flyway</artifactId>
-                <version>${project.version}</version>
-             </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-hibernate-orm</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-panache-common</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-panacheql</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-hibernate-orm-panache</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-mongodb-panache</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-hibernate-search-orm-elasticsearch</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-hibernate-validator</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-infinispan-client</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-jaeger</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-jdbc-postgresql</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-jdbc-h2</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-jdbc-mariadb</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-jdbc-mssql</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-jdbc-mysql</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-jdbc-derby</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-kafka-client</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-kafka-streams</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-smallrye-health</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-smallrye-jwt</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-smallrye-context-propagation</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-smallrye-reactive-streams-operators</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-smallrye-reactive-type-converters</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-smallrye-reactive-messaging</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-smallrye-reactive-messaging-kafka</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-smallrye-reactive-messaging-amqp</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-smallrye-reactive-messaging-mqtt</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-smallrye-metrics</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-smallrye-openapi</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-smallrye-opentracing</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-rest-client</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-resteasy-common</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-resteasy</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-resteasy-jackson</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-resteasy-jsonb</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-resteasy-jaxb</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-resteasy-server-common</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-narayana-jta</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-undertow</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-smallrye-fault-tolerance</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-vertx-core</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-vertx</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-vertx-http</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-vertx-web</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-reactive-pg-client</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-reactive-mysql-client</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-mailer</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-mongodb-client</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-websockets</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-scheduler</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-quartz</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-spring-di</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-spring-web</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-spring-data-jpa</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-swagger-ui</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-kotlin</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-amazon-lambda</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-amazon-lambda-http</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-amazon-dynamodb</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-azure-functions-http</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-kubernetes</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-kubernetes-client</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-kogito</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-tika</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-neo4j</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-scala</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-jgit</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-narayana-stm</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-elytron-security-jdbc</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-vault</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-credentials</artifactId>
-                <version>${project.version}</version>
-            </dependency>
--->
-        </dependencies>
-    </dependencyManagement>
-</project>
diff --git a/independent-projects/tools/platform-descriptor-resolver-json/src/test/resources/quarkus.properties b/independent-projects/tools/platform-descriptor-resolver-json/src/test/resources/quarkus.properties
deleted file mode 100644
index 9cb4e05a1c5bd2..00000000000000
--- a/independent-projects/tools/platform-descriptor-resolver-json/src/test/resources/quarkus.properties
+++ /dev/null
@@ -1 +0,0 @@
-quarkus-core-version = 1.0.0.CR90
\ No newline at end of file
diff --git a/independent-projects/tools/pom.xml b/independent-projects/tools/pom.xml
index c4f6ce3bb7b1a9..e080899b0aa83b 100644
--- a/independent-projects/tools/pom.xml
+++ b/independent-projects/tools/pom.xml
@@ -56,16 +56,26 @@
         <smallrye-commons.version>1.5.0</smallrye-commons.version>
     </properties>
     <modules>
-        <module>platform-descriptor-api</module>
+        <module>artifact-api</module>
+        <module>registry-client</module>
         <module>message-writer</module>
         <module>devtools-testing</module>
         <module>codestarts</module>
         <module>devtools-common</module>
         <module>utilities</module>
-        <module>platform-descriptor-resolver-json</module>
     </modules>
     <dependencyManagement>
         <dependencies>
+            <dependency>
+                <groupId>io.quarkus</groupId>
+                <artifactId>quarkus-devtools-artifact-api</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>io.quarkus</groupId>
+                <artifactId>quarkus-devtools-registry-client</artifactId>
+                <version>${project.version}</version>
+            </dependency>
             <dependency>
                 <groupId>org.apache.commons</groupId>
                 <artifactId>commons-compress</artifactId>
@@ -106,16 +116,6 @@
                 <artifactId>quarkus-devtools-common</artifactId>
                 <version>${project.version}</version>
             </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-platform-descriptor-api</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>io.quarkus</groupId>
-                <artifactId>quarkus-platform-descriptor-resolver-json</artifactId>
-                <version>${project.version}</version>
-            </dependency>
             <dependency>
                 <groupId>io.quarkus.http</groupId>
                 <artifactId>quarkus-http-websockets-jsr</artifactId>
diff --git a/independent-projects/tools/platform-descriptor-resolver-json/pom.xml b/independent-projects/tools/registry-client/pom.xml
similarity index 61%
rename from independent-projects/tools/platform-descriptor-resolver-json/pom.xml
rename to independent-projects/tools/registry-client/pom.xml
index b6362dcd8fb26a..dc14ba7c9fc0c2 100644
--- a/independent-projects/tools/platform-descriptor-resolver-json/pom.xml
+++ b/independent-projects/tools/registry-client/pom.xml
@@ -2,29 +2,37 @@
 <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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
     <parent>
-        <artifactId>quarkus-tools-parent</artifactId>
         <groupId>io.quarkus</groupId>
+        <artifactId>quarkus-tools-parent</artifactId>
         <version>999-SNAPSHOT</version>
-        <relativePath>../pom.xml</relativePath>
     </parent>
-    <modelVersion>4.0.0</modelVersion>
 
-    <artifactId>quarkus-platform-descriptor-resolver-json</artifactId>
-    <name>Quarkus - Dev tools - Platform JSON descriptor resolver</name>
+    <artifactId>quarkus-devtools-registry-client</artifactId>
+    <name>Quarkus - Dev tools - Registry Client API</name>
 
     <dependencies>
         <dependency>
             <groupId>io.quarkus</groupId>
-            <artifactId>quarkus-bootstrap-maven-resolver</artifactId>
+            <artifactId>quarkus-devtools-artifact-api</artifactId>
         </dependency>
         <dependency>
             <groupId>io.quarkus</groupId>
-            <artifactId>quarkus-devtools-common</artifactId>
+            <artifactId>quarkus-devtools-message-writer</artifactId>
         </dependency>
         <dependency>
-            <groupId>org.jboss.logging</groupId>
-            <artifactId>jboss-logging</artifactId>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.dataformat</groupId>
+            <artifactId>jackson-dataformat-yaml</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-bootstrap-maven-resolver</artifactId>
         </dependency>
         <dependency>
             <groupId>org.junit.jupiter</groupId>
@@ -32,9 +40,8 @@
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>io.quarkus</groupId>
-            <artifactId>quarkus-bootstrap-core</artifactId>
-            <type>test-jar</type>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
             <scope>test</scope>
         </dependency>
     </dependencies>
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/Constants.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/Constants.java
new file mode 100644
index 00000000000000..a71dd7402442e7
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/Constants.java
@@ -0,0 +1,21 @@
+package io.quarkus.registry;
+
+public interface Constants {
+
+    String DEFAULT_REGISTRY_ID = "registry.quarkus.io";
+    String DEFAULT_REGISTRY_GROUP_ID = "io.quarkus.registry";
+    String DEFAULT_REGISTRY_ARTIFACT_VERSION = "1.0-SNAPSHOT";
+    String DEFAULT_REGISTRY_DESCRIPTOR_ARTIFACT_ID = "quarkus-registry-descriptor";
+    String DEFAULT_REGISTRY_NON_PLATFORM_EXTENSIONS_CATALOG_ARTIFACT_ID = "quarkus-non-platform-extensions";
+    String DEFAULT_REGISTRY_PLATFORMS_CATALOG_ARTIFACT_ID = "quarkus-platforms";
+
+    String DEFAULT_REGISTRY_REST_URL = "https://registry.quarkus.io/api/1";
+    String DEFAULT_REGISTRY_MAVEN_REPO_URL = "https://registry.quarkus.io/maven";
+    String DEFAULT_REGISTRY_BACKUP_MAVEN_REPO_ID = "oss.sonatype.org";
+    String DEFAULT_REGISTRY_BACKUP_MAVEN_REPO_URL = "https://oss.sonatype.org/content/repositories/snapshots";
+
+    String PLATFORM_DESCRIPTOR_ARTIFACT_ID_SUFFIX = "-quarkus-platform-descriptor";
+    String PLATFORM_PROPERTIES_ARTIFACT_ID_SUFFIX = "-quarkus-platform-properties";
+
+    String JSON = "json";
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/ExclusiveProviderConflictException.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/ExclusiveProviderConflictException.java
new file mode 100644
index 00000000000000..18ba96c10465e3
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/ExclusiveProviderConflictException.java
@@ -0,0 +1,13 @@
+package io.quarkus.registry;
+
+import java.util.List;
+
+@SuppressWarnings("serial")
+class ExclusiveProviderConflictException extends Exception {
+
+    List<RegistryExtensionResolver> conflictingRegistries;
+
+    ExclusiveProviderConflictException(List<RegistryExtensionResolver> conflictingRegistries) {
+        this.conflictingRegistries = conflictingRegistries;
+    }
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/ExtensionCatalogResolver.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/ExtensionCatalogResolver.java
new file mode 100644
index 00000000000000..ff7a2d3288f2eb
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/ExtensionCatalogResolver.java
@@ -0,0 +1,416 @@
+package io.quarkus.registry;
+
+import io.quarkus.bootstrap.resolver.maven.BootstrapMavenException;
+import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver;
+import io.quarkus.devtools.messagewriter.MessageWriter;
+import io.quarkus.maven.ArtifactCoords;
+import io.quarkus.maven.ArtifactKey;
+import io.quarkus.registry.catalog.ExtensionCatalog;
+import io.quarkus.registry.catalog.Platform;
+import io.quarkus.registry.catalog.PlatformCatalog;
+import io.quarkus.registry.catalog.json.JsonCatalogMerger;
+import io.quarkus.registry.catalog.json.JsonPlatformCatalog;
+import io.quarkus.registry.client.RegistryClientFactory;
+import io.quarkus.registry.client.maven.MavenRegistryClientFactory;
+import io.quarkus.registry.config.RegistriesConfig;
+import io.quarkus.registry.config.RegistriesConfigLocator;
+import io.quarkus.registry.config.RegistryConfig;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+
+public class ExtensionCatalogResolver {
+
+    public static Builder builder() {
+        return new ExtensionCatalogResolver().new Builder();
+    }
+
+    public class Builder {
+
+        private MavenArtifactResolver artifactResolver;
+        private RegistriesConfig config;
+        private boolean built;
+
+        private Builder() {
+        }
+
+        public Builder artifactResolver(MavenArtifactResolver resolver) {
+            assertNotBuilt();
+            artifactResolver = resolver;
+            return this;
+        }
+
+        public Builder messageWriter(MessageWriter messageWriter) {
+            assertNotBuilt();
+            log = messageWriter;
+            return this;
+        }
+
+        public Builder config(RegistriesConfig registriesConfig) {
+            assertNotBuilt();
+            config = registriesConfig;
+            return this;
+        }
+
+        public ExtensionCatalogResolver build() {
+            assertNotBuilt();
+            built = true;
+            completeConfig();
+            buildRegistryClients();
+            return ExtensionCatalogResolver.this;
+        }
+
+        private void completeConfig() {
+            if (config == null) {
+                config = RegistriesConfigLocator.resolveConfig();
+            }
+            if (log == null) {
+                log = config.isDebug() ? MessageWriter.debug() : MessageWriter.info();
+            }
+            if (artifactResolver == null) {
+                try {
+                    artifactResolver = MavenArtifactResolver.builder()
+                            .setWorkspaceDiscovery(false)
+                            .setArtifactTransferLogging(config.isDebug())
+                            .build();
+                } catch (BootstrapMavenException e) {
+                    throw new IllegalStateException("Failed to intialize the default Maven artifact resolver", e);
+                }
+            }
+        }
+
+        private void buildRegistryClients() {
+            registries = new ArrayList<>(config.getRegistries().size());
+            final RegistryClientFactory defaultClientFactory = new MavenRegistryClientFactory(artifactResolver,
+                    log);
+            for (RegistryConfig config : config.getRegistries()) {
+                if (config.isDisabled()) {
+                    continue;
+                }
+                try {
+                    registries.add(new RegistryExtensionResolver(defaultClientFactory.buildRegistryClient(config), log));
+                } catch (RegistryResolutionException e) {
+                    log.warn(e.getMessage());
+                    continue;
+                }
+            }
+        }
+
+        private void assertNotBuilt() {
+            if (built) {
+                throw new IllegalStateException("The builder has already built an instance");
+            }
+        }
+    }
+
+    private MessageWriter log;
+    private List<RegistryExtensionResolver> registries;
+
+    public boolean hasRegistries() {
+        return !registries.isEmpty();
+    }
+
+    public PlatformCatalog resolvePlatformCatalog() throws RegistryResolutionException {
+        return resolvePlatformCatalog(null);
+    }
+
+    public PlatformCatalog resolvePlatformCatalog(String quarkusVersion) throws RegistryResolutionException {
+        final Set<ArtifactKey> collectedPlatformKeys = new HashSet<>();
+
+        List<PlatformCatalog> catalogs = new ArrayList<>(registries.size());
+        for (RegistryExtensionResolver qer : registries) {
+            final PlatformCatalog catalog = qer.resolvePlatformCatalog(quarkusVersion);
+            if (catalog != null) {
+                catalogs.add(catalog);
+            }
+        }
+
+        if (catalogs.isEmpty()) {
+            return null;
+        }
+        if (catalogs.size() == 1) {
+            return catalogs.get(0);
+        }
+
+        final JsonPlatformCatalog result = new JsonPlatformCatalog();
+
+        result.setDefaultPlatform(catalogs.get(0).getDefaultPlatform());
+
+        final List<Platform> collectedPlatforms = new ArrayList<>();
+        result.setPlatforms(collectedPlatforms);
+
+        collectedPlatformKeys.clear();
+        for (PlatformCatalog c : catalogs) {
+            collectPlatforms(c, collectedPlatforms, collectedPlatformKeys);
+        }
+
+        return result;
+    }
+
+    private void collectPlatforms(PlatformCatalog catalog, List<Platform> collectedPlatforms,
+            Set<ArtifactKey> collectedPlatformKeys) {
+        for (Platform p : catalog.getPlatforms()) {
+            if (collectedPlatformKeys.add(p.getBom().getKey())) {
+                collectedPlatforms.add(p);
+            }
+        }
+    }
+
+    public ExtensionCatalog resolveExtensionCatalog() throws RegistryResolutionException {
+        return resolveExtensionCatalog((String) null);
+    }
+
+    public ExtensionCatalog resolveExtensionCatalog(String quarkusCoreVersion) throws RegistryResolutionException {
+
+        final int registriesTotal = registries.size();
+        if (registriesTotal == 0) {
+            throw new RegistryResolutionException("No registries configured");
+        }
+        final Map<String, List<RegistryExtensionResolver>> registriesByQuarkusCore = new LinkedHashMap<>(registriesTotal);
+        List<ExtensionCatalog> extensionCatalogs = null;
+
+        if (quarkusCoreVersion == null) {
+            // if there is only one registry and all the platforms it recommends are based
+            // on the same quarkus version then we don't need to execute extra requests to align the platforms
+            // on the common quarkus version
+            if (registriesTotal == 1) {
+                final PlatformCatalog platformsCatalog = registries.get(0).resolvePlatformCatalog();
+                final List<Platform> platforms = platformsCatalog == null ? Collections.emptyList()
+                        : platformsCatalog.getPlatforms();
+                if (platforms.isEmpty()) {
+                    // TODO this should be allowed
+                    throw new RegistryResolutionException(
+                            "Registry " + registries.get(0).getId() + " does not provide any platform");
+                }
+                String commonQuarkusVersion = quarkusCoreVersion = platforms.get(0).getQuarkusCoreVersion();
+                Platform defaultPlatform = null;
+                int i = 1;
+                while (i < platforms.size()) {
+                    final Platform p = platforms.get(i++);
+                    if (defaultPlatform == null && p.getBom().equals(platformsCatalog.getDefaultPlatform())) {
+                        defaultPlatform = p;
+                        quarkusCoreVersion = p.getQuarkusCoreVersion();
+                    }
+                    if (commonQuarkusVersion != null && !commonQuarkusVersion.equals(p.getQuarkusCoreVersion())) {
+                        commonQuarkusVersion = null;
+                    }
+                }
+                if (commonQuarkusVersion != null) {
+                    // all platforms are aligned on the same quarkus version
+                    final RegistryExtensionResolver mainRegistry = registries.get(0);
+                    registriesByQuarkusCore.put(commonQuarkusVersion, Arrays.asList(mainRegistry));
+                    extensionCatalogs = new ArrayList<>();
+                    i = 0;
+                    while (i < platforms.size()) {
+                        extensionCatalogs.add(mainRegistry.resolvePlatformExtensions(platforms.get(i++).getBom()));
+                    }
+                }
+            } else {
+                quarkusCoreVersion = registries.get(0).resolveDefaultPlatform().getQuarkusCoreVersion();
+            }
+        }
+
+        if (extensionCatalogs == null) {
+            // collect all the platform extensions from all the registries compatible with the given quarkus core version
+            extensionCatalogs = new ArrayList<>();
+            final Set<String> upstreamQuarkusVersions = new HashSet<>(1);
+
+            collectPlatforms(quarkusCoreVersion, registriesByQuarkusCore, upstreamQuarkusVersions, extensionCatalogs);
+
+            if (!upstreamQuarkusVersions.isEmpty()) {
+                Set<String> upstreamToProcess = upstreamQuarkusVersions;
+                Set<String> collectedUpstream = new HashSet<>(0);
+                do {
+                    collectedUpstream.clear();
+                    final Iterator<String> upstreamIterator = upstreamToProcess.iterator();
+                    while (upstreamIterator.hasNext()) {
+                        collectPlatforms(upstreamIterator.next(),
+                                registriesByQuarkusCore, collectedUpstream, extensionCatalogs);
+                    }
+                    final Set<String> tmp = upstreamToProcess;
+                    upstreamToProcess = collectedUpstream;
+                    collectedUpstream = tmp;
+                } while (!upstreamToProcess.isEmpty());
+            }
+        }
+        return appendNonPlatformExtensions(registriesByQuarkusCore, extensionCatalogs);
+    }
+
+    public ExtensionCatalog resolveExtensionCatalog(List<ArtifactCoords> platforms)
+            throws RegistryResolutionException {
+        if (platforms.isEmpty()) {
+            return resolveExtensionCatalog();
+        }
+        final List<ExtensionCatalog> catalogs = new ArrayList<>(platforms.size() + registries.size());
+        Map<String, List<RegistryExtensionResolver>> registriesByQuarkusCore = new HashMap<>(2);
+        String quarkusVersion = null;
+        for (ArtifactCoords bom : platforms) {
+            final List<RegistryExtensionResolver> registries;
+            try {
+                registries = filterRegistries(r -> r.checkPlatform(bom));
+            } catch (ExclusiveProviderConflictException e) {
+                final StringBuilder buf = new StringBuilder();
+                buf.append(
+                        "The following registries were configured as exclusive providers of the ");
+                buf.append(bom);
+                buf.append("platform: ").append(e.conflictingRegistries.get(0).getId());
+                for (int i = 1; i < e.conflictingRegistries.size(); ++i) {
+                    buf.append(", ").append(e.conflictingRegistries.get(i).getId());
+                }
+                throw new RuntimeException(buf.toString());
+            }
+
+            final ExtensionCatalog catalog = resolvePlatformExtensions(bom, registries);
+            if (catalog != null) {
+                catalogs.add(catalog);
+                if (quarkusVersion == null) {
+                    quarkusVersion = catalog.getQuarkusCoreVersion();
+                    registriesByQuarkusCore.put(quarkusVersion, getRegistriesForQuarkusVersion(quarkusVersion));
+                }
+                final String upstreamQuarkusVersion = catalog.getUpstreamQuarkusCoreVersion();
+                if (upstreamQuarkusVersion != null && !registriesByQuarkusCore.containsKey(upstreamQuarkusVersion)) {
+                    registriesByQuarkusCore.put(upstreamQuarkusVersion,
+                            getRegistriesForQuarkusVersion(upstreamQuarkusVersion));
+                }
+            }
+        }
+        return appendNonPlatformExtensions(registriesByQuarkusCore, catalogs);
+    }
+
+    private ExtensionCatalog resolvePlatformExtensions(ArtifactCoords bom, List<RegistryExtensionResolver> registries) {
+        if (registries.isEmpty()) {
+            log.debug("None of the configured registries recognizes platform %s", bom);
+            return null;
+        }
+        for (RegistryExtensionResolver registry : registries) {
+            try {
+                return registry.resolvePlatformExtensions(bom);
+            } catch (RegistryResolutionException e) {
+            }
+        }
+        final StringBuilder buf = new StringBuilder();
+        buf.append("Failed to resolve platform ").append(bom).append(" using the following registries: ");
+        buf.append(registries.get(0).getId());
+        for (int i = 1; i < registries.size(); ++i) {
+            buf.append(", ").append(registries.get(i++));
+        }
+        log.warn(buf.toString());
+        return null;
+    }
+
+    private ExtensionCatalog appendNonPlatformExtensions(
+            final Map<String, List<RegistryExtensionResolver>> registriesByQuarkusCore,
+            List<ExtensionCatalog> extensionCatalogs) throws RegistryResolutionException {
+        for (Map.Entry<String, List<RegistryExtensionResolver>> quarkusVersionRegistries : registriesByQuarkusCore.entrySet()) {
+            for (RegistryExtensionResolver registry : quarkusVersionRegistries.getValue()) {
+                final ExtensionCatalog nonPlatformCatalog = registry
+                        .resolveNonPlatformExtensions(quarkusVersionRegistries.getKey());
+                if (nonPlatformCatalog != null) {
+                    extensionCatalogs.add(nonPlatformCatalog);
+                }
+            }
+        }
+        return JsonCatalogMerger.merge(extensionCatalogs);
+    }
+
+    private void collectPlatforms(String quarkusCoreVersion,
+            Map<String, List<RegistryExtensionResolver>> registriesByQuarkusCore,
+            Set<String> upstreamQuarkusVersions, List<ExtensionCatalog> extensionCatalogs) throws RegistryResolutionException {
+        List<RegistryExtensionResolver> quarkusVersionRegistries = registriesByQuarkusCore.get(quarkusCoreVersion);
+        if (quarkusVersionRegistries != null) {
+            return;
+        }
+
+        quarkusVersionRegistries = getRegistriesForQuarkusVersion(quarkusCoreVersion);
+        registriesByQuarkusCore.put(quarkusCoreVersion, quarkusVersionRegistries);
+
+        for (RegistryExtensionResolver registry : quarkusVersionRegistries) {
+            final PlatformCatalog platformCatalog = registry.resolvePlatformCatalog(quarkusCoreVersion);
+            if (platformCatalog == null) {
+                continue;
+            }
+            final List<Platform> platforms = platformCatalog.getPlatforms();
+            if (platforms.isEmpty()) {
+                continue;
+            }
+            for (Platform p : platforms) {
+                final String upstreamQuarkusCoreVersion = p.getUpstreamQuarkusCoreVersion();
+                if (upstreamQuarkusCoreVersion != null
+                        && !registriesByQuarkusCore.containsKey(upstreamQuarkusCoreVersion)) {
+                    upstreamQuarkusVersions.add(upstreamQuarkusCoreVersion);
+                }
+                final ExtensionCatalog catalog = registry.resolvePlatformExtensions(p.getBom());
+                if (catalog != null) {
+                    extensionCatalogs.add(catalog);
+                }
+            }
+        }
+    }
+
+    private List<RegistryExtensionResolver> getRegistriesForQuarkusVersion(String quarkusCoreVersion) {
+        try {
+            return filterRegistries(r -> r.checkQuarkusVersion(quarkusCoreVersion));
+        } catch (ExclusiveProviderConflictException e) {
+            final StringBuilder buf = new StringBuilder();
+            buf.append(
+                    "The following registries were configured as exclusive providers of extensions based on Quarkus version ");
+            buf.append(quarkusCoreVersion);
+            buf.append(": ").append(e.conflictingRegistries.get(0).getId());
+            for (int i = 1; i < e.conflictingRegistries.size(); ++i) {
+                buf.append(", ").append(e.conflictingRegistries.get(i).getId());
+            }
+            throw new RuntimeException(buf.toString());
+        }
+    }
+
+    private List<RegistryExtensionResolver> filterRegistries(Function<RegistryExtensionResolver, Integer> recognizer)
+            throws ExclusiveProviderConflictException {
+        RegistryExtensionResolver exclusiveProvider = null;
+        List<RegistryExtensionResolver> filtered = null;
+        List<RegistryExtensionResolver> conflicts = null;
+        for (int i = 0; i < registries.size(); ++i) {
+            final RegistryExtensionResolver registry = registries.get(i);
+            final int versionCheck = recognizer.apply(registry);
+
+            if (versionCheck == RegistryExtensionResolver.VERSION_NOT_RECOGNIZED) {
+                if (exclusiveProvider == null && filtered == null) {
+                    filtered = new ArrayList<>(registries.size() - 1);
+                    for (int j = 0; j < i; ++j) {
+                        filtered.add(registries.get(j));
+                    }
+                }
+                continue;
+            }
+
+            if (versionCheck == RegistryExtensionResolver.VERSION_EXCLUSIVE_PROVIDER) {
+                if (exclusiveProvider == null) {
+                    exclusiveProvider = registry;
+                } else {
+                    if (conflicts == null) {
+                        conflicts = new ArrayList<>();
+                        conflicts.add(exclusiveProvider);
+                    }
+                    conflicts.add(registry);
+                }
+            }
+
+            if (filtered != null) {
+                filtered.add(registry);
+            }
+        }
+
+        if (conflicts != null) {
+            throw new ExclusiveProviderConflictException(conflicts);
+        }
+
+        return exclusiveProvider == null ? filtered == null ? registries : filtered : Arrays.asList(exclusiveProvider);
+    }
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/RegistryExtensionResolver.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/RegistryExtensionResolver.java
new file mode 100644
index 00000000000000..6727b1b08f45f3
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/RegistryExtensionResolver.java
@@ -0,0 +1,92 @@
+package io.quarkus.registry;
+
+import io.quarkus.devtools.messagewriter.MessageWriter;
+import io.quarkus.maven.ArtifactCoords;
+import io.quarkus.registry.catalog.ExtensionCatalog;
+import io.quarkus.registry.catalog.Platform;
+import io.quarkus.registry.catalog.PlatformCatalog;
+import io.quarkus.registry.client.RegistryClient;
+import io.quarkus.registry.config.RegistryConfig;
+import io.quarkus.registry.util.GlobUtil;
+import java.util.Iterator;
+import java.util.Objects;
+import java.util.regex.Pattern;
+
+class RegistryExtensionResolver {
+
+    public static final int VERSION_NOT_RECOGNIZED = -1;
+    public static final int VERSION_NOT_CONFIGURED = 0;
+    public static final int VERSION_RECOGNIZED = 1;
+    public static final int VERSION_EXCLUSIVE_PROVIDER = 2;
+
+    private final RegistryConfig config;
+    private final RegistryClient extensionResolver;
+
+    private Pattern recognizedQuarkusVersions;
+
+    RegistryExtensionResolver(RegistryClient extensionResolver,
+            MessageWriter log) throws RegistryResolutionException {
+        this.extensionResolver = Objects.requireNonNull(extensionResolver, "Registry extension resolver is null");
+        this.config = extensionResolver.resolveRegistryConfig();
+
+        String versionExpr = config.getQuarkusVersions() == null ? null
+                : config.getQuarkusVersions().getRecognizedVersionsExpression();
+        if (versionExpr != null) {
+            recognizedQuarkusVersions = Pattern.compile(GlobUtil.toRegexPattern(versionExpr));
+        }
+    }
+
+    String getId() {
+        return config.getId();
+    }
+
+    int checkQuarkusVersion(String quarkusVersion) {
+        if (recognizedQuarkusVersions == null) {
+            return VERSION_NOT_CONFIGURED;
+        }
+        if (!recognizedQuarkusVersions.matcher(quarkusVersion).matches()) {
+            return VERSION_NOT_RECOGNIZED;
+        }
+        return config.getQuarkusVersions().isExclusiveProvider() ? VERSION_EXCLUSIVE_PROVIDER
+                : VERSION_RECOGNIZED;
+    }
+
+    int checkPlatform(ArtifactCoords platform) {
+        // TODO this should be allowed to check the full coordinates
+        return checkQuarkusVersion(platform.getVersion());
+    }
+
+    PlatformCatalog resolvePlatformCatalog() throws RegistryResolutionException {
+        return resolvePlatformCatalog(null);
+    }
+
+    PlatformCatalog resolvePlatformCatalog(String quarkusCoreVersion) throws RegistryResolutionException {
+        return extensionResolver.resolvePlatforms(quarkusCoreVersion);
+    }
+
+    Platform resolveDefaultPlatform() throws RegistryResolutionException {
+        final PlatformCatalog platformsCatalog = resolvePlatformCatalog();
+        final ArtifactCoords defaultCoords = platformsCatalog.getDefaultPlatform();
+        for (Platform p : platformsCatalog.getPlatforms()) {
+            if (defaultCoords.equals(p.getBom())) {
+                return p;
+            }
+        }
+        final StringBuilder buf = new StringBuilder();
+        buf.append("Failed to locate the default platform ").append(defaultCoords).append(" in the catalog of ");
+        final Iterator<Platform> i = platformsCatalog.getPlatforms().iterator();
+        buf.append(i.next().getBom());
+        while (i.hasNext()) {
+            buf.append(", ").append(i.next().getBom());
+        }
+        throw new RegistryResolutionException(buf.toString());
+    }
+
+    ExtensionCatalog resolveNonPlatformExtensions(String quarkusCoreVersion) throws RegistryResolutionException {
+        return extensionResolver.resolveNonPlatformExtensions(quarkusCoreVersion);
+    }
+
+    ExtensionCatalog resolvePlatformExtensions(ArtifactCoords platform) throws RegistryResolutionException {
+        return extensionResolver.resolvePlatformExtensions(platform);
+    }
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/RegistryResolutionException.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/RegistryResolutionException.java
new file mode 100644
index 00000000000000..468c93dbdbdd91
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/RegistryResolutionException.java
@@ -0,0 +1,12 @@
+package io.quarkus.registry;
+
+public class RegistryResolutionException extends Exception {
+
+    public RegistryResolutionException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public RegistryResolutionException(String message) {
+        super(message);
+    }
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/Category.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/Category.java
new file mode 100644
index 00000000000000..35f9b066a28e49
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/Category.java
@@ -0,0 +1,14 @@
+package io.quarkus.registry.catalog;
+
+import java.util.Map;
+
+public interface Category {
+
+    String getId();
+
+    String getName();
+
+    String getDescription();
+
+    Map<String, Object> getMetadata();
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/Extension.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/Extension.java
new file mode 100644
index 00000000000000..8c2b552eff2566
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/Extension.java
@@ -0,0 +1,102 @@
+package io.quarkus.registry.catalog;
+
+import io.quarkus.maven.ArtifactCoords;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public interface Extension {
+
+    String MD_SHORT_NAME = "short-name";
+    String MD_CODESTART = "codestart";
+    String MD_GUIDE = "guide";
+    /** Key used for keywords in metadata **/
+    String MD_KEYWORDS = "keywords";
+    String MD_UNLISTED = "unlisted";
+    String MD_STATUS = "status";
+
+    String getName();
+
+    String getDescription();
+
+    ArtifactCoords getArtifact();
+
+    List<ExtensionOrigin> getOrigins();
+
+    default boolean hasPlatformOrigin() {
+        final List<ExtensionOrigin> origins = getOrigins();
+        if (origins == null || origins.isEmpty()) {
+            return false;
+        }
+        for (ExtensionOrigin o : origins) {
+            if (o.isPlatform()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    Map<String, Object> getMetadata();
+
+    @SuppressWarnings("unchecked")
+    default List<String> getKeywords() {
+        List<String> kw = (List<String>) getMetadata().get(MD_KEYWORDS);
+        return kw == null ? Collections.emptyList() : kw;
+    }
+
+    /**
+     * List of strings to use for matching.
+     *
+     * Returns keywords + artifactid all in lowercase.
+     *
+     * @return list of labels to use for matching.
+     */
+    default List<String> labelsForMatching() {
+        final List<String> keywords = getKeywords();
+        final List<String> list = new ArrayList<>(1 + (keywords == null ? 0 : keywords.size()));
+        if (keywords != null) {
+            list.addAll(keywords.stream().map(String::toLowerCase).collect(Collectors.toList()));
+        }
+        list.add(getArtifact().getArtifactId().toLowerCase());
+        return list;
+    }
+
+    /**
+     *
+     * @return string representing the location of primary guide for this extension.
+     */
+    default String getGuide() {
+        return (String) getMetadata().get(MD_GUIDE);
+    }
+
+    default String managementKey() {
+        final ArtifactCoords artifact = getArtifact();
+        return artifact.getGroupId() + ":" + artifact.getArtifactId();
+    }
+
+    default String getShortName() {
+        final String shortName = (String) getMetadata().get(MD_SHORT_NAME);
+        return shortName == null ? getName() : shortName;
+    }
+
+    default String getCodestart() {
+        return (String) getMetadata().get(MD_CODESTART);
+    }
+
+    default boolean isUnlisted() {
+        final Object val = getMetadata().get(MD_UNLISTED);
+        if (val == null) {
+            return false;
+        }
+        if (val instanceof Boolean) {
+            return (Boolean) val;
+        }
+        if (val instanceof String) {
+            return Boolean.parseBoolean((String) val);
+        }
+        return false;
+    }
+
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/ExtensionCatalog.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/ExtensionCatalog.java
new file mode 100644
index 00000000000000..0561aae31dd12d
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/ExtensionCatalog.java
@@ -0,0 +1,58 @@
+package io.quarkus.registry.catalog;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+public interface ExtensionCatalog extends ExtensionOrigin {
+
+    /**
+     * All the origins this catalog is derived from.
+     *
+     * @return all the origins this catalog derives from.
+     */
+    List<ExtensionOrigin> getDerivedFrom();
+
+    /**
+     * Quarkus core version used by the extensions in this catalog.
+     *
+     * @return Quarkus core version used by the extensions in this catalog
+     */
+    String getQuarkusCoreVersion();
+
+    /**
+     * In case the catalog was built for a custom version of the Quarkus core,
+     * this version represents the corresponding upstream community Quarkus core version.
+     * This is done to be able to link the custom builds of Quarkus back to the upstream community
+     * extensions ecosystem.
+     *
+     * This method may return null in case the corresponding version does not exist in the upstream community
+     * or simply to link back to it.
+     *
+     *
+     * @return the upstream community Quarkus core version corresponding to the Quarkus core version
+     *         used in this catalog
+     */
+    String getUpstreamQuarkusCoreVersion();
+
+    /**
+     * Quarkus extensions that constitute the catalog.
+     *
+     * @return Quarkus extensions that constitute the catalog.
+     */
+    Collection<Extension> getExtensions();
+
+    /**
+     * Extension categories
+     *
+     * @return extension categories
+     */
+    List<Category> getCategories();
+
+    /**
+     * Various metadata attached to the catalog
+     *
+     * @return metadata attached to the catalog
+     */
+    Map<String, Object> getMetadata();
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/ExtensionOrigin.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/ExtensionOrigin.java
new file mode 100644
index 00000000000000..4380b6bf6046af
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/ExtensionOrigin.java
@@ -0,0 +1,31 @@
+package io.quarkus.registry.catalog;
+
+import io.quarkus.maven.ArtifactCoords;
+
+public interface ExtensionOrigin {
+
+    /**
+     * Origin ID. E.g. GAV of the descriptor.
+     *
+     * @return origin ID
+     */
+    String getId();
+
+    /**
+     * BOM that should be imported by a project
+     * using extensions from this origin. This method normally won't return null.
+     * Given that any Quarkus project would typically be importing at least some version of
+     * io.quarkus:quarkus-bom even if extensions used in the project aren't managed by the quarkus-bom/
+     * the project
+     *
+     * @return BOM coordinates
+     */
+    ArtifactCoords getBom();
+
+    /**
+     * Whether the origin represents a platform.
+     *
+     * @return true in case the origin is a platform, otherwise - false
+     */
+    boolean isPlatform();
+}
diff --git a/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/dependencies/ExtensionPredicate.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/ExtensionPredicate.java
similarity index 94%
rename from independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/dependencies/ExtensionPredicate.java
rename to independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/ExtensionPredicate.java
index 6655a1de710cce..756d30aabcc450 100644
--- a/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/dependencies/ExtensionPredicate.java
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/ExtensionPredicate.java
@@ -1,4 +1,4 @@
-package io.quarkus.dependencies;
+package io.quarkus.registry.catalog;
 
 import java.util.List;
 import java.util.Objects;
@@ -62,7 +62,7 @@ public boolean test(Extension extension) {
         }
         // Partial matches on name, artifactId and short names
         if (extensionName.toLowerCase().contains(q)
-                || extension.getArtifactId().toLowerCase().contains(q)
+                || extension.getArtifact().getArtifactId().toLowerCase().contains(q)
                 || shortName.toLowerCase().contains(q)) {
             return true;
         }
@@ -73,7 +73,7 @@ public boolean test(Extension extension) {
         // find by pattern
         Pattern pattern = toRegex(q);
         return pattern != null && (pattern.matcher(extensionName.toLowerCase()).matches()
-                || pattern.matcher(extension.getArtifactId().toLowerCase()).matches()
+                || pattern.matcher(extension.getArtifact().getArtifactId().toLowerCase()).matches()
                 || pattern.matcher(shortName.toLowerCase()).matches()
                 || matchLabels(pattern, extension.getKeywords()));
     }
@@ -82,7 +82,7 @@ public boolean isExactMatch(Extension extension) {
         String extensionName = Objects.toString(extension.getName(), "");
         // Try exact matches
         if (extensionName.equalsIgnoreCase(q) ||
-                matchesArtifactId(extension.getArtifactId(), q)) {
+                matchesArtifactId(extension.getArtifact().getArtifactId(), q)) {
             return true;
         }
         return false;
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/Platform.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/Platform.java
new file mode 100644
index 00000000000000..99ade7d77a3649
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/Platform.java
@@ -0,0 +1,12 @@
+package io.quarkus.registry.catalog;
+
+import io.quarkus.maven.ArtifactCoords;
+
+public interface Platform {
+
+    ArtifactCoords getBom();
+
+    String getQuarkusCoreVersion();
+
+    String getUpstreamQuarkusCoreVersion();
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/PlatformCatalog.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/PlatformCatalog.java
new file mode 100644
index 00000000000000..30954d3da4dbf7
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/PlatformCatalog.java
@@ -0,0 +1,11 @@
+package io.quarkus.registry.catalog;
+
+import io.quarkus.maven.ArtifactCoords;
+import java.util.List;
+
+public interface PlatformCatalog {
+
+    List<Platform> getPlatforms();
+
+    ArtifactCoords getDefaultPlatform();
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/json/JsonArtifactCoordsDeserializer.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/json/JsonArtifactCoordsDeserializer.java
new file mode 100644
index 00000000000000..149ed84afd241e
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/json/JsonArtifactCoordsDeserializer.java
@@ -0,0 +1,17 @@
+package io.quarkus.registry.catalog.json;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import io.quarkus.maven.ArtifactCoords;
+import java.io.IOException;
+
+public class JsonArtifactCoordsDeserializer extends JsonDeserializer<ArtifactCoords> {
+
+    @Override
+    public ArtifactCoords deserialize(JsonParser p, DeserializationContext ctxt)
+            throws IOException, JsonProcessingException {
+        return ArtifactCoords.fromString(p.getText());
+    }
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/json/JsonArtifactCoordsMixin.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/json/JsonArtifactCoordsMixin.java
new file mode 100644
index 00000000000000..3095c2143e86c7
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/json/JsonArtifactCoordsMixin.java
@@ -0,0 +1,14 @@
+package io.quarkus.registry.catalog.json;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import io.quarkus.maven.ArtifactKey;
+
+@JsonSerialize(using = JsonArtifactCoordsSerializer.class)
+@JsonDeserialize(using = JsonArtifactCoordsDeserializer.class)
+public interface JsonArtifactCoordsMixin {
+
+    @JsonIgnore
+    ArtifactKey getKey();
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/json/JsonArtifactCoordsSerializer.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/json/JsonArtifactCoordsSerializer.java
new file mode 100644
index 00000000000000..94a6c6e521ffe2
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/json/JsonArtifactCoordsSerializer.java
@@ -0,0 +1,15 @@
+package io.quarkus.registry.catalog.json;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import io.quarkus.maven.ArtifactCoords;
+import java.io.IOException;
+
+public class JsonArtifactCoordsSerializer extends JsonSerializer<ArtifactCoords> {
+
+    @Override
+    public void serialize(ArtifactCoords value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+        gen.writeString(value.toString());
+    }
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/json/JsonCatalogMapperHelper.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/json/JsonCatalogMapperHelper.java
new file mode 100644
index 00000000000000..b6af65ad76cc32
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/json/JsonCatalogMapperHelper.java
@@ -0,0 +1,64 @@
+package io.quarkus.registry.catalog.json;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import io.quarkus.maven.ArtifactCoords;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.Writer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+public class JsonCatalogMapperHelper {
+
+    private static ObjectMapper mapper;
+
+    public static ObjectMapper mapper() {
+        return mapper == null ? mapper = initMapper(new ObjectMapper()) : mapper;
+    }
+
+    public static ObjectMapper initMapper(ObjectMapper mapper) {
+        mapper.addMixIn(ArtifactCoords.class, JsonArtifactCoordsMixin.class);
+        mapper.enable(SerializationFeature.INDENT_OUTPUT);
+        mapper.enable(JsonParser.Feature.ALLOW_COMMENTS);
+        mapper.setPropertyNamingStrategy(PropertyNamingStrategies.KEBAB_CASE);
+        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+        return mapper;
+    }
+
+    public static void serialize(Object catalog, Path p) throws IOException {
+        if (!Files.exists(p.getParent())) {
+            Files.createDirectories(p.getParent());
+        }
+        try (BufferedWriter writer = Files.newBufferedWriter(p)) {
+            serialize(catalog, writer);
+        }
+    }
+
+    public static void serialize(Object catalog, Writer writer) throws IOException {
+        mapper().writeValue(writer, catalog);
+    }
+
+    public static <T> T deserialize(Path p, Class<T> t) throws IOException {
+        if (!Files.exists(p)) {
+            throw new IllegalArgumentException("File " + p + " does not exist");
+        }
+        try (BufferedReader reader = Files.newBufferedReader(p)) {
+            return mapper().readValue(reader, t);
+        }
+    }
+
+    public static <T> T deserialize(InputStream is, Class<T> t) throws IOException {
+        return mapper().readValue(is, t);
+    }
+
+    public static <T> T deserialize(Reader reader, Class<T> t) throws IOException {
+        return mapper().readValue(reader, t);
+    }
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/json/JsonCatalogMerger.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/json/JsonCatalogMerger.java
new file mode 100644
index 00000000000000..435a821accb828
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/json/JsonCatalogMerger.java
@@ -0,0 +1,85 @@
+package io.quarkus.registry.catalog.json;
+
+import io.quarkus.maven.ArtifactKey;
+import io.quarkus.registry.catalog.Category;
+import io.quarkus.registry.catalog.Extension;
+import io.quarkus.registry.catalog.ExtensionCatalog;
+import io.quarkus.registry.catalog.ExtensionOrigin;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class JsonCatalogMerger {
+
+    public static ExtensionCatalog merge(List<ExtensionCatalog> catalogs) {
+
+        if (catalogs.isEmpty()) {
+            throw new IllegalArgumentException("No catalogs provided");
+        }
+        if (catalogs.size() == 1) {
+            return catalogs.get(0);
+        }
+
+        final List<ExtensionCatalog> roots = detectRoots(catalogs);
+        if (roots.size() == 1) {
+            return roots.get(0);
+        }
+
+        final JsonExtensionCatalog combined = new JsonExtensionCatalog();
+
+        final Map<String, Category> categories = new LinkedHashMap<>();
+        final Map<String, ExtensionOrigin> derivedFrom = new LinkedHashMap<>();
+        final Map<ArtifactKey, Extension> extensions = new LinkedHashMap<>();
+        final Map<String, Object> metadata = new HashMap<>();
+        for (ExtensionCatalog catalog : roots) {
+            if (combined.getBom() == null) {
+                combined.setBom(catalog.getBom());
+            }
+
+            if (catalog.getId() != null) {
+                derivedFrom.putIfAbsent(catalog.getId(), catalog);
+            }
+            catalog.getDerivedFrom().forEach(o -> derivedFrom.putIfAbsent(o.getId(), o));
+
+            catalog.getCategories().forEach(c -> categories.putIfAbsent(c.getId(), c));
+            catalog.getExtensions().forEach(e -> extensions.putIfAbsent(e.getArtifact().getKey(), e));
+            catalog.getMetadata().entrySet().forEach(entry -> metadata.putIfAbsent(entry.getKey(), entry.getValue()));
+
+            if (combined.getQuarkusCoreVersion() == null && catalog.getQuarkusCoreVersion() != null) {
+                combined.setQuarkusCoreVersion(catalog.getQuarkusCoreVersion());
+            }
+            if (combined.getUpstreamQuarkusCoreVersion() == null && catalog.getUpstreamQuarkusCoreVersion() != null
+                    && !combined.getQuarkusCoreVersion().equals(catalog.getUpstreamQuarkusCoreVersion())) {
+                combined.setUpstreamQuarkusCoreVersion(catalog.getUpstreamQuarkusCoreVersion());
+            }
+        }
+
+        combined.setCategories(new ArrayList<>(categories.values()));
+        combined.setDerivedFrom(new ArrayList<>(derivedFrom.values()));
+        combined.setExtensions(new ArrayList<>(extensions.values()));
+        combined.setMetadata(metadata);
+        return combined;
+    }
+
+    private static List<ExtensionCatalog> detectRoots(List<ExtensionCatalog> catalogs) {
+        final Set<String> allDerivedFrom = new HashSet<>(catalogs.size());
+        for (ExtensionCatalog catalog : catalogs) {
+            for (ExtensionOrigin o : catalog.getDerivedFrom()) {
+                allDerivedFrom.add(o.getId());
+            }
+        }
+        final List<ExtensionCatalog> roots = new ArrayList<>(catalogs.size());
+        for (ExtensionCatalog catalog : catalogs) {
+            if (catalog.getId() == null) {
+                roots.add(catalog);
+            } else if (!allDerivedFrom.contains(catalog.getId())) {
+                roots.add(catalog);
+            }
+        }
+        return roots;
+    }
+}
diff --git a/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/dependencies/Category.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/json/JsonCategory.java
similarity index 67%
rename from independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/dependencies/Category.java
rename to independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/json/JsonCategory.java
index 55c57769900057..fdf8df61698a6f 100644
--- a/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/dependencies/Category.java
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/json/JsonCategory.java
@@ -1,19 +1,20 @@
-package io.quarkus.dependencies;
+package io.quarkus.registry.catalog.json;
 
-import java.io.Serializable;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.quarkus.registry.catalog.Category;
 import java.util.Map;
 import java.util.Objects;
 
-public class Category implements Serializable {
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class JsonCategory implements Category {
 
-    public static final String MD_PINNED = "pinned";
+    protected String id;
+    protected String name;
+    protected String description;
 
-    String id;
-    String name;
-    String description;
-
-    Map<String, Object> metadata;
+    protected Map<String, Object> metadata;
 
+    @Override
     public String getId() {
         return id;
     }
@@ -22,6 +23,7 @@ public void setId(String id) {
         this.id = id;
     }
 
+    @Override
     public String getName() {
         return name;
     }
@@ -30,6 +32,7 @@ public void setName(String name) {
         this.name = name;
     }
 
+    @Override
     public String getDescription() {
         return description;
     }
@@ -38,6 +41,7 @@ public void setDescription(String description) {
         this.description = description;
     }
 
+    @Override
     public Map<String, Object> getMetadata() {
         return metadata;
     }
@@ -54,7 +58,7 @@ public boolean equals(Object o) {
         if (o == null || getClass() != o.getClass()) {
             return false;
         }
-        Category category = (Category) o;
+        final JsonCategory category = (JsonCategory) o;
         return Objects.equals(id, category.id);
     }
 
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/json/JsonExtension.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/json/JsonExtension.java
new file mode 100644
index 00000000000000..4b43b2c1473d0a
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/json/JsonExtension.java
@@ -0,0 +1,153 @@
+package io.quarkus.registry.catalog.json;
+
+import com.fasterxml.jackson.annotation.JsonIdentityReference;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import io.quarkus.maven.ArtifactCoords;
+import io.quarkus.registry.catalog.Extension;
+import io.quarkus.registry.catalog.ExtensionOrigin;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class JsonExtension implements Extension {
+
+    private String name;
+    private String description;
+    private Map<String, Object> metadata;
+    private ArtifactCoords artifact;
+    private List<ExtensionOrigin> origins;
+
+    // to support legacy format
+    private String groupId;
+    private String artifactId;
+    private String version;
+
+    public void setGroupId(String groupId) {
+        this.groupId = groupId;
+    }
+
+    public void setArtifactId(String artifactId) {
+        this.artifactId = artifactId;
+    }
+
+    public void setVersion(String version) {
+        this.version = version;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    @Override
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    @Override
+    public ArtifactCoords getArtifact() {
+        if (artifact == null && artifactId != null) {
+            artifact = new ArtifactCoords(groupId, artifactId, version);
+        }
+        return artifact;
+    }
+
+    public void setArtifact(ArtifactCoords coords) {
+        this.artifact = coords;
+    }
+
+    public void setOrigins(List<ExtensionOrigin> origins) {
+        this.origins = origins;
+    }
+
+    @Override
+    @JsonIdentityReference(alwaysAsId = true)
+    @JsonDeserialize(contentAs = JsonExtensionOrigin.class)
+    public List<ExtensionOrigin> getOrigins() {
+        return origins == null ? Collections.emptyList() : origins;
+    }
+
+    @Override
+    public Map<String, Object> getMetadata() {
+        return metadata == null ? metadata = new HashMap<>() : metadata;
+    }
+
+    public void setMetadata(Map<String, Object> metadata) {
+        this.metadata = metadata;
+    }
+
+    @JsonIgnore
+    public Extension setKeywords(List<String> keywords) {
+        getMetadata().put(MD_KEYWORDS, keywords);
+        return this;
+    }
+
+    @JsonIgnore
+    public Extension setGuide(String guide) {
+        getMetadata().put(MD_GUIDE, guide);
+        return this;
+    }
+
+    @JsonIgnore
+    public Extension setShortName(String shortName) {
+        getMetadata().put(MD_SHORT_NAME, shortName);
+        return this;
+    }
+
+    @JsonIgnore
+    public Extension setCodestart(String codestart) {
+        getMetadata().put(MD_CODESTART, codestart);
+        return this;
+    }
+
+    @JsonIgnore
+    public void setUnlisted(boolean unlisted) {
+        getMetadata().put(MD_UNLISTED, unlisted);
+    }
+
+    public Extension addMetadata(String key, Object value) {
+        getMetadata().put(key, value);
+        return this;
+
+    }
+
+    public Extension removeMetadata(String key) {
+        getMetadata().remove(key);
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        return name + " " + getArtifact();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        JsonExtension extension = (JsonExtension) o;
+        return Objects.equals(artifact, extension.artifact);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(artifact);
+    }
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/json/JsonExtensionCatalog.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/json/JsonExtensionCatalog.java
new file mode 100644
index 00000000000000..c1dcbb60597c05
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/json/JsonExtensionCatalog.java
@@ -0,0 +1,87 @@
+package io.quarkus.registry.catalog.json;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import io.quarkus.registry.catalog.Category;
+import io.quarkus.registry.catalog.Extension;
+import io.quarkus.registry.catalog.ExtensionCatalog;
+import io.quarkus.registry.catalog.ExtensionOrigin;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class JsonExtensionCatalog extends JsonExtensionOrigin implements ExtensionCatalog {
+
+    private String quarkusCore;
+    private String upstreamQuarkusCore;
+    private List<ExtensionOrigin> derivedFrom;
+    private List<Extension> extensions;
+    private List<Category> categories;
+    private Map<String, Object> metadata;
+
+    @Override
+    public String getQuarkusCoreVersion() {
+        return quarkusCore;
+    }
+
+    public void setQuarkusCoreVersion(String quarkusCore) {
+        this.quarkusCore = quarkusCore;
+    }
+
+    @Override
+    public String getUpstreamQuarkusCoreVersion() {
+        return upstreamQuarkusCore;
+    }
+
+    public void setUpstreamQuarkusCoreVersion(String upstreamQuarkusCore) {
+        this.upstreamQuarkusCore = upstreamQuarkusCore;
+    }
+
+    @Override
+    @JsonDeserialize(contentAs = JsonExtensionOrigin.class)
+    public List<ExtensionOrigin> getDerivedFrom() {
+        return derivedFrom == null ? Collections.emptyList() : derivedFrom;
+    }
+
+    public void setDerivedFrom(List<ExtensionOrigin> origins) {
+        this.derivedFrom = origins;
+    }
+
+    @Override
+    @JsonDeserialize(contentAs = JsonExtension.class)
+    public List<Extension> getExtensions() {
+        return extensions == null ? Collections.emptyList() : extensions;
+    }
+
+    public void setExtensions(List<Extension> extensions) {
+        this.extensions = extensions;
+    }
+
+    public void addExtension(Extension e) {
+        if (extensions == null) {
+            extensions = new ArrayList<>();
+        }
+        extensions.add(e);
+    }
+
+    @Override
+    @JsonDeserialize(contentAs = JsonCategory.class)
+    public List<Category> getCategories() {
+        return categories == null ? Collections.emptyList() : categories;
+    }
+
+    public void setCategories(List<Category> categories) {
+        this.categories = categories;
+    }
+
+    @Override
+    public Map<String, Object> getMetadata() {
+        return metadata == null ? Collections.emptyMap() : metadata;
+    }
+
+    public void setMetadata(Map<String, Object> metadata) {
+        this.metadata = metadata;
+    }
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/json/JsonExtensionOrigin.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/json/JsonExtensionOrigin.java
new file mode 100644
index 00000000000000..71abbe826258d1
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/json/JsonExtensionOrigin.java
@@ -0,0 +1,53 @@
+package io.quarkus.registry.catalog.json;
+
+import com.fasterxml.jackson.annotation.JsonIdentityInfo;
+import com.fasterxml.jackson.annotation.ObjectIdGenerators;
+import io.quarkus.maven.ArtifactCoords;
+import io.quarkus.registry.catalog.ExtensionOrigin;
+
+@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
+public class JsonExtensionOrigin implements ExtensionOrigin {
+
+    protected String id;
+    protected boolean platform;
+    protected ArtifactCoords bom;
+
+    @Override
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    @Override
+    public ArtifactCoords getBom() {
+        return bom;
+    }
+
+    public void setBom(ArtifactCoords bom) {
+        this.bom = bom;
+    }
+
+    @Override
+    public boolean isPlatform() {
+        return platform;
+    }
+
+    public void setPlatform(boolean platform) {
+        this.platform = platform;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder buf = new StringBuilder();
+        buf.append('[');
+        if (id != null) {
+            buf.append("id=").append(id).append(' ');
+        }
+        buf.append("platform=").append(platform);
+        buf.append(" boms=").append(bom);
+        return buf.append(']').toString();
+    }
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/json/JsonPlatform.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/json/JsonPlatform.java
new file mode 100644
index 00000000000000..ba29ca4e5eb047
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/json/JsonPlatform.java
@@ -0,0 +1,52 @@
+package io.quarkus.registry.catalog.json;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.quarkus.maven.ArtifactCoords;
+import io.quarkus.registry.catalog.Platform;
+import java.util.Objects;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class JsonPlatform implements Platform {
+
+    public static JsonPlatform of(ArtifactCoords bom) {
+        JsonPlatform p = new JsonPlatform();
+        p.setBom(Objects.requireNonNull(bom, "bom can't be null"));
+        return p;
+    }
+
+    private ArtifactCoords bom;
+    private String quarkusCore;
+    private String upstreamQuarkusCore;
+
+    @Override
+    public ArtifactCoords getBom() {
+        return bom;
+    }
+
+    public void setBom(ArtifactCoords bom) {
+        this.bom = bom;
+    }
+
+    @Override
+    public String getQuarkusCoreVersion() {
+        return quarkusCore;
+    }
+
+    public void setQuarkusCoreVersion(String quarkusCore) {
+        this.quarkusCore = quarkusCore;
+    }
+
+    @Override
+    public String getUpstreamQuarkusCoreVersion() {
+        return upstreamQuarkusCore;
+    }
+
+    public void setUpstreamQuarkusCoreVersion(String upstreamQuarkusCore) {
+        this.upstreamQuarkusCore = upstreamQuarkusCore;
+    }
+
+    @Override
+    public String toString() {
+        return "[platform " + bom + ", quarkus-core=" + quarkusCore + ", upstream-quarkus-core=" + upstreamQuarkusCore + "]";
+    }
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/json/JsonPlatformCatalog.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/json/JsonPlatformCatalog.java
new file mode 100644
index 00000000000000..988993193c5155
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/json/JsonPlatformCatalog.java
@@ -0,0 +1,43 @@
+package io.quarkus.registry.catalog.json;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import io.quarkus.maven.ArtifactCoords;
+import io.quarkus.registry.catalog.Platform;
+import io.quarkus.registry.catalog.PlatformCatalog;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class JsonPlatformCatalog implements PlatformCatalog {
+
+    private List<Platform> platforms;
+    private ArtifactCoords defaultPlatform;
+
+    @Override
+    @JsonDeserialize(contentAs = JsonPlatform.class)
+    public List<Platform> getPlatforms() {
+        return platforms == null ? Collections.emptyList() : platforms;
+    }
+
+    public void setPlatforms(List<Platform> platforms) {
+        this.platforms = platforms;
+    }
+
+    public void addPlatform(Platform platform) {
+        if (platforms == null) {
+            platforms = new ArrayList<>();
+        }
+        platforms.add(platform);
+    }
+
+    @Override
+    public ArtifactCoords getDefaultPlatform() {
+        return defaultPlatform;
+    }
+
+    public void setDefaultPlatform(ArtifactCoords defaultPlatform) {
+        this.defaultPlatform = defaultPlatform;
+    }
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/client/RegistryClient.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/client/RegistryClient.java
new file mode 100644
index 00000000000000..f2b1e83075f0d6
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/client/RegistryClient.java
@@ -0,0 +1,55 @@
+package io.quarkus.registry.client;
+
+import io.quarkus.maven.ArtifactCoords;
+import io.quarkus.registry.RegistryResolutionException;
+import io.quarkus.registry.catalog.ExtensionCatalog;
+import io.quarkus.registry.catalog.PlatformCatalog;
+import io.quarkus.registry.config.RegistryConfig;
+import java.util.Objects;
+
+/**
+ * Implements the basic queries a registry client is supposed to handle.
+ * Although there are only a few kinds of queries, a registry is not required to support
+ * all of them. For example, a registry may be configured to only provide platform extensions or
+ * the other way around - provide only non-platform extensions but not platforms.
+ */
+public class RegistryClient
+        implements RegistryNonPlatformExtensionsResolver, RegistryPlatformExtensionsResolver, RegistryPlatformsResolver,
+        RegistryConfigResolver {
+
+    private final RegistryPlatformsResolver platforms;
+    private final RegistryPlatformExtensionsResolver platformExtensions;
+    private final RegistryNonPlatformExtensionsResolver nonPlatformExtensions;
+    protected RegistryConfig config;
+
+    public RegistryClient(RegistryConfig config, RegistryPlatformsResolver platforms,
+            RegistryPlatformExtensionsResolver platformExtensions,
+            RegistryNonPlatformExtensionsResolver nonPlatformExtensions) {
+        this.config = config;
+        this.platforms = platforms;
+        this.platformExtensions = Objects.requireNonNull(platformExtensions);
+        this.nonPlatformExtensions = nonPlatformExtensions;
+    }
+
+    @Override
+    public PlatformCatalog resolvePlatforms(String quarkusVersion) throws RegistryResolutionException {
+        return platforms == null ? null : platforms.resolvePlatforms(quarkusVersion);
+    }
+
+    @Override
+    public ExtensionCatalog resolvePlatformExtensions(ArtifactCoords platformCoords)
+            throws RegistryResolutionException {
+        return platformExtensions.resolvePlatformExtensions(platformCoords);
+    }
+
+    @Override
+    public ExtensionCatalog resolveNonPlatformExtensions(String quarkusVersion) throws RegistryResolutionException {
+        return nonPlatformExtensions == null ? null
+                : nonPlatformExtensions.resolveNonPlatformExtensions(quarkusVersion);
+    }
+
+    @Override
+    public RegistryConfig resolveRegistryConfig() throws RegistryResolutionException {
+        return config;
+    }
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/client/RegistryClientFactory.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/client/RegistryClientFactory.java
new file mode 100644
index 00000000000000..fbfda63c8bf342
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/client/RegistryClientFactory.java
@@ -0,0 +1,9 @@
+package io.quarkus.registry.client;
+
+import io.quarkus.registry.RegistryResolutionException;
+import io.quarkus.registry.config.RegistryConfig;
+
+public interface RegistryClientFactory {
+
+    RegistryClient buildRegistryClient(RegistryConfig config) throws RegistryResolutionException;
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/client/RegistryConfigResolver.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/client/RegistryConfigResolver.java
new file mode 100644
index 00000000000000..39d7a64f072338
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/client/RegistryConfigResolver.java
@@ -0,0 +1,17 @@
+package io.quarkus.registry.client;
+
+import io.quarkus.registry.RegistryResolutionException;
+import io.quarkus.registry.config.RegistryConfig;
+
+public interface RegistryConfigResolver {
+
+    /**
+     * Returns the complete registry configuration. The idea is that the default configuration
+     * to communicate with the registry will be provided by the registry admins for all users.
+     * Users though will still have a chance to adjust certain config options on the client side.
+     *
+     * @return complete registry configuration
+     * @throws RegistryResolutionException in case of a failure
+     */
+    RegistryConfig resolveRegistryConfig() throws RegistryResolutionException;
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/client/RegistryNonPlatformExtensionsResolver.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/client/RegistryNonPlatformExtensionsResolver.java
new file mode 100644
index 00000000000000..90cacf5a35a8b2
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/client/RegistryNonPlatformExtensionsResolver.java
@@ -0,0 +1,19 @@
+package io.quarkus.registry.client;
+
+import io.quarkus.registry.RegistryResolutionException;
+import io.quarkus.registry.catalog.ExtensionCatalog;
+
+public interface RegistryNonPlatformExtensionsResolver {
+
+    /**
+     * Returns a catalog of extensions that are compatible with a given Quarkus version
+     * or null, in case the registry does not include any extension that is compatible
+     * with the given Quarkus version.
+     *
+     * @param quarkusVersion Quarkus version
+     * @return catalog of extensions compatible with a given Quarkus version or null
+     * @throws RegistryResolutionException in case of a failure
+     */
+    ExtensionCatalog resolveNonPlatformExtensions(String quarkusVersion)
+            throws RegistryResolutionException;
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/client/RegistryPlatformExtensionsResolver.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/client/RegistryPlatformExtensionsResolver.java
new file mode 100644
index 00000000000000..d8cfafda00256e
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/client/RegistryPlatformExtensionsResolver.java
@@ -0,0 +1,18 @@
+package io.quarkus.registry.client;
+
+import io.quarkus.maven.ArtifactCoords;
+import io.quarkus.registry.RegistryResolutionException;
+import io.quarkus.registry.catalog.ExtensionCatalog;
+
+public interface RegistryPlatformExtensionsResolver {
+
+    /**
+     * Returns a catalog of extensions that represents a given platform.
+     *
+     * @param platformCoords either a BOM or a JSON descriptor coordinates
+     * @return catalog of extensions that represents the platform
+     * @throws RegistryResolutionException in case of a failure
+     */
+    ExtensionCatalog resolvePlatformExtensions(ArtifactCoords platformCoords)
+            throws RegistryResolutionException;
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/client/RegistryPlatformsResolver.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/client/RegistryPlatformsResolver.java
new file mode 100644
index 00000000000000..f54594552676ea
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/client/RegistryPlatformsResolver.java
@@ -0,0 +1,18 @@
+package io.quarkus.registry.client;
+
+import io.quarkus.registry.RegistryResolutionException;
+import io.quarkus.registry.catalog.PlatformCatalog;
+
+public interface RegistryPlatformsResolver {
+
+    /**
+     * Returns a catalog of the recommended platform versions, indicating which one of them
+     * is the default one for new project creation, for a given Quarkus version or in general,
+     * in case the caller did not provide any specific Quarkus version.
+     *
+     * @param quarkusVersion Quarkus version or null
+     * @return catalog of the recommended platform versions
+     * @throws RegistryResolutionException in case of a failure
+     */
+    PlatformCatalog resolvePlatforms(String quarkusVersion) throws RegistryResolutionException;
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/client/maven/MavenNonPlatformExtensionsResolver.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/client/maven/MavenNonPlatformExtensionsResolver.java
new file mode 100644
index 00000000000000..167aeffb1289dd
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/client/maven/MavenNonPlatformExtensionsResolver.java
@@ -0,0 +1,51 @@
+package io.quarkus.registry.client.maven;
+
+import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver;
+import io.quarkus.devtools.messagewriter.MessageWriter;
+import io.quarkus.maven.ArtifactCoords;
+import io.quarkus.registry.RegistryResolutionException;
+import io.quarkus.registry.catalog.ExtensionCatalog;
+import io.quarkus.registry.catalog.json.JsonCatalogMapperHelper;
+import io.quarkus.registry.catalog.json.JsonExtensionCatalog;
+import io.quarkus.registry.client.RegistryNonPlatformExtensionsResolver;
+import io.quarkus.registry.config.RegistryNonPlatformExtensionsConfig;
+import java.nio.file.Path;
+import java.util.Objects;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.artifact.DefaultArtifact;
+
+public class MavenNonPlatformExtensionsResolver
+        implements RegistryNonPlatformExtensionsResolver {
+
+    private final RegistryNonPlatformExtensionsConfig config;
+    private final MavenArtifactResolver artifactResolver;
+    private final MessageWriter log;
+
+    public MavenNonPlatformExtensionsResolver(RegistryNonPlatformExtensionsConfig config,
+            MavenArtifactResolver artifactResolver, MessageWriter log) {
+        this.config = Objects.requireNonNull(config);
+        this.artifactResolver = Objects.requireNonNull(artifactResolver);
+        this.log = Objects.requireNonNull(log);
+    }
+
+    @Override
+    public ExtensionCatalog resolveNonPlatformExtensions(String quarkusVersion)
+            throws RegistryResolutionException {
+        final ArtifactCoords baseCoords = config.getArtifact();
+        final Artifact catalogArtifact = new DefaultArtifact(baseCoords.getGroupId(),
+                baseCoords.getArtifactId(), quarkusVersion, baseCoords.getType(), baseCoords.getVersion());
+        log.debug("Resolving non-platform extension catalog %s", catalogArtifact);
+        final Path jsonFile;
+        try {
+            jsonFile = artifactResolver.resolve(catalogArtifact).getArtifact().getFile().toPath();
+        } catch (Exception e) {
+            log.debug("Failed to resolve non-platform extension catalog %s", catalogArtifact);
+            return null;
+        }
+        try {
+            return JsonCatalogMapperHelper.deserialize(jsonFile, JsonExtensionCatalog.class);
+        } catch (Exception e) {
+            throw new RegistryResolutionException("Failed to load non-platform extension catalog from " + jsonFile, e);
+        }
+    }
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/client/maven/MavenPlatformExtensionsResolver.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/client/maven/MavenPlatformExtensionsResolver.java
new file mode 100644
index 00000000000000..84603090c98aba
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/client/maven/MavenPlatformExtensionsResolver.java
@@ -0,0 +1,93 @@
+package io.quarkus.registry.client.maven;
+
+import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver;
+import io.quarkus.devtools.messagewriter.MessageWriter;
+import io.quarkus.maven.ArtifactCoords;
+import io.quarkus.registry.Constants;
+import io.quarkus.registry.RegistryResolutionException;
+import io.quarkus.registry.catalog.ExtensionCatalog;
+import io.quarkus.registry.catalog.json.JsonCatalogMapperHelper;
+import io.quarkus.registry.catalog.json.JsonExtensionCatalog;
+import io.quarkus.registry.client.RegistryPlatformExtensionsResolver;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Objects;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.artifact.DefaultArtifact;
+
+public class MavenPlatformExtensionsResolver implements RegistryPlatformExtensionsResolver {
+
+    private final MavenArtifactResolver artifactResolver;
+    private final MessageWriter log;
+
+    public MavenPlatformExtensionsResolver(MavenArtifactResolver artifactResolver,
+            MessageWriter log) {
+        this.artifactResolver = Objects.requireNonNull(artifactResolver);
+        this.log = Objects.requireNonNull(log);
+    }
+
+    @Override
+    public ExtensionCatalog resolvePlatformExtensions(ArtifactCoords platformCoords) throws RegistryResolutionException {
+        final String version;
+        if (platformCoords.getVersion() == null) {
+            version = resolveLatestBomVersion(platformCoords, "[0-alpha,)");
+        } else if (isVersionRange(platformCoords.getVersion())) {
+            version = resolveLatestBomVersion(platformCoords, platformCoords.getVersion());
+        } else {
+            version = platformCoords.getVersion();
+        }
+        final String groupId = platformCoords.getGroupId();
+        final String artifactId = platformCoords.getArtifactId().endsWith(Constants.PLATFORM_DESCRIPTOR_ARTIFACT_ID_SUFFIX)
+                ? platformCoords.getArtifactId()
+                : platformCoords.getArtifactId() + Constants.PLATFORM_DESCRIPTOR_ARTIFACT_ID_SUFFIX;
+        final String classifier = version;
+        final Artifact catalogArtifact = new DefaultArtifact(groupId, artifactId, classifier, "json", version);
+        log.debug("Resolving platform extension catalog %s", catalogArtifact);
+        final Path jsonPath;
+        try {
+            jsonPath = artifactResolver.resolve(catalogArtifact).getArtifact().getFile().toPath();
+        } catch (Exception e) {
+            throw new RegistryResolutionException("Failed to resolve Quarkus extensions catalog " + catalogArtifact,
+                    e);
+        }
+        try {
+            return JsonCatalogMapperHelper.deserialize(jsonPath, JsonExtensionCatalog.class);
+        } catch (IOException e) {
+            throw new RegistryResolutionException("Failed to parse Quarkus extensions catalog " + jsonPath, e);
+        }
+    }
+
+    private String resolveLatestBomVersion(ArtifactCoords bom, String versionRange)
+            throws RegistryResolutionException {
+        final Artifact bomArtifact = new DefaultArtifact(bom.getGroupId(),
+                bom.getArtifactId().endsWith(Constants.PLATFORM_DESCRIPTOR_ARTIFACT_ID_SUFFIX)
+                        ? bom.getArtifactId().substring(0,
+                                bom.getArtifactId().length()
+                                        - Constants.PLATFORM_DESCRIPTOR_ARTIFACT_ID_SUFFIX.length())
+                        : bom.getArtifactId(),
+                "", "pom", bom.getVersion());
+        log.debug("Resolving the latest version of %s:%s:%s:%s in the range %s", bom.getGroupId(), bom.getArtifactId(),
+                bom.getClassifier(), bom.getType(), versionRange);
+        try {
+            return artifactResolver.getLatestVersionFromRange(bomArtifact, versionRange);
+        } catch (Exception e) {
+            throw new RegistryResolutionException("Failed to resolve the latest version of " + bomArtifact.getGroupId()
+                    + ":" + bom.getArtifactId() + ":" + bom.getClassifier() + ":" + bom.getType() + ":" + versionRange, e);
+        }
+    }
+
+    private static boolean isVersionRange(String versionStr) {
+        if (versionStr == null || versionStr.isEmpty()) {
+            return false;
+        }
+        char c = versionStr.charAt(0);
+        if (c == '[' || c == '(') {
+            return true;
+        }
+        c = versionStr.charAt(versionStr.length() - 1);
+        if (c == ']' || c == ')') {
+            return true;
+        }
+        return versionStr.indexOf(',') >= 0;
+    }
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/client/maven/MavenPlatformsResolver.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/client/maven/MavenPlatformsResolver.java
new file mode 100644
index 00000000000000..a9056846ef1a9a
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/client/maven/MavenPlatformsResolver.java
@@ -0,0 +1,51 @@
+package io.quarkus.registry.client.maven;
+
+import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver;
+import io.quarkus.devtools.messagewriter.MessageWriter;
+import io.quarkus.maven.ArtifactCoords;
+import io.quarkus.registry.RegistryResolutionException;
+import io.quarkus.registry.catalog.PlatformCatalog;
+import io.quarkus.registry.catalog.json.JsonCatalogMapperHelper;
+import io.quarkus.registry.catalog.json.JsonPlatformCatalog;
+import io.quarkus.registry.client.RegistryPlatformsResolver;
+import io.quarkus.registry.config.RegistryPlatformsConfig;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Objects;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.artifact.DefaultArtifact;
+
+public class MavenPlatformsResolver implements RegistryPlatformsResolver {
+
+    private final RegistryPlatformsConfig config;
+    private final MavenArtifactResolver artifactResolver;
+    private final MessageWriter log;
+
+    public MavenPlatformsResolver(RegistryPlatformsConfig config, MavenArtifactResolver artifactResolver,
+            MessageWriter log) {
+        this.config = Objects.requireNonNull(config);
+        this.artifactResolver = Objects.requireNonNull(artifactResolver);
+        this.log = Objects.requireNonNull(log);
+    }
+
+    @Override
+    public PlatformCatalog resolvePlatforms(String quarkusVersion) throws RegistryResolutionException {
+        final ArtifactCoords baseCoords = config.getArtifact();
+        final Artifact catalogArtifact = new DefaultArtifact(baseCoords.getGroupId(), baseCoords.getArtifactId(),
+                quarkusVersion, baseCoords.getType(), baseCoords.getVersion());
+        log.debug("Resolving platform catalog %s", catalogArtifact);
+        final Path jsonFile;
+        try {
+            jsonFile = artifactResolver.resolve(catalogArtifact).getArtifact().getFile().toPath();
+        } catch (Exception e) {
+            log.debug("Failed to resolve platform catalog %s", catalogArtifact);
+            return null;
+        }
+        try {
+            return JsonCatalogMapperHelper.deserialize(jsonFile, JsonPlatformCatalog.class);
+        } catch (IOException e) {
+            throw new RegistryResolutionException(
+                    "Failed to load platform catalog from " + jsonFile, e);
+        }
+    }
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/client/maven/MavenRegistryClientFactory.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/client/maven/MavenRegistryClientFactory.java
new file mode 100644
index 00000000000000..78b94b1a10a86d
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/client/maven/MavenRegistryClientFactory.java
@@ -0,0 +1,368 @@
+package io.quarkus.registry.client.maven;
+
+import io.quarkus.bootstrap.resolver.maven.BootstrapMavenContext;
+import io.quarkus.bootstrap.resolver.maven.BootstrapMavenException;
+import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver;
+import io.quarkus.devtools.messagewriter.MessageWriter;
+import io.quarkus.maven.ArtifactCoords;
+import io.quarkus.registry.Constants;
+import io.quarkus.registry.RegistryResolutionException;
+import io.quarkus.registry.client.RegistryClient;
+import io.quarkus.registry.client.RegistryClientFactory;
+import io.quarkus.registry.client.RegistryNonPlatformExtensionsResolver;
+import io.quarkus.registry.client.RegistryPlatformsResolver;
+import io.quarkus.registry.config.RegistryArtifactConfig;
+import io.quarkus.registry.config.RegistryConfig;
+import io.quarkus.registry.config.RegistryDescriptorConfig;
+import io.quarkus.registry.config.RegistryMavenConfig;
+import io.quarkus.registry.config.RegistryMavenRepoConfig;
+import io.quarkus.registry.config.RegistryNonPlatformExtensionsConfig;
+import io.quarkus.registry.config.RegistryPlatformsConfig;
+import io.quarkus.registry.config.json.JsonRegistryConfig;
+import io.quarkus.registry.config.json.JsonRegistryMavenConfig;
+import io.quarkus.registry.config.json.JsonRegistryMavenRepoConfig;
+import io.quarkus.registry.config.json.JsonRegistryPlatformsConfig;
+import io.quarkus.registry.config.json.RegistriesConfigMapperHelper;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.repository.RepositoryPolicy;
+import org.eclipse.aether.resolution.ArtifactResult;
+
+public class MavenRegistryClientFactory implements RegistryClientFactory {
+
+    private MessageWriter log;
+    private MavenArtifactResolver originalResolver;
+    private List<RemoteRepository> singleRegistryRepos = new ArrayList<RemoteRepository>();
+
+    public MavenRegistryClientFactory(MavenArtifactResolver resolver, MessageWriter log) {
+        this.originalResolver = Objects.requireNonNull(resolver);
+        this.log = Objects.requireNonNull(log);
+    }
+
+    @Override
+    public RegistryClient buildRegistryClient(RegistryConfig config) throws RegistryResolutionException {
+        Objects.requireNonNull(config, "The registry config is null");
+
+        final RegistryDescriptorConfig descriptorConfig = Objects.requireNonNull(config.getDescriptor(),
+                "The registry descriptor configuration is missing");
+
+        MavenArtifactResolver resolver = originalResolver;
+
+        singleRegistryRepos.clear();
+        determineExtraRepos(config, resolver.getRepositories());
+
+        List<RemoteRepository> aggregatedRepos = resolver.getRepositories();
+        if (!singleRegistryRepos.isEmpty()) {
+            aggregatedRepos = resolver.getRemoteRepositoryManager().aggregateRepositories(resolver.getSession(),
+                    Collections.emptyList(), singleRegistryRepos, true);
+            aggregatedRepos = resolver.getRemoteRepositoryManager().aggregateRepositories(resolver.getSession(),
+                    aggregatedRepos, resolver.getRepositories(), false);
+            resolver = newResolver(resolver, aggregatedRepos);
+        }
+
+        final ArtifactCoords originalDescrCoords = descriptorConfig.getArtifact();
+        final Artifact registryDescriptorCoords = new DefaultArtifact(originalDescrCoords.getGroupId(),
+                originalDescrCoords.getArtifactId(), originalDescrCoords.getClassifier(), originalDescrCoords.getType(),
+                originalDescrCoords.getVersion());
+        ArtifactResult result;
+        try {
+            result = resolver.resolve(registryDescriptorCoords);
+        } catch (BootstrapMavenException e) {
+            final StringWriter buf = new StringWriter();
+            try (BufferedWriter writer = new BufferedWriter(buf)) {
+                writer.write("Failed to resolve Quarkus extensions registry descriptor ");
+                writer.write(registryDescriptorCoords.toString());
+                writer.write(" using the following repositories:");
+                for (RemoteRepository repo : aggregatedRepos) {
+                    writer.newLine();
+                    writer.write("- ");
+                    writer.write(repo.getId());
+                    writer.write(" (");
+                    writer.write(repo.getUrl());
+                    writer.write(")");
+                }
+            } catch (IOException e1) {
+                buf.append(e.getLocalizedMessage());
+            }
+            throw new RegistryResolutionException(buf.toString());
+        }
+
+        final String srcRepoId = result.getRepository() == null ? "n/a" : result.getRepository().getId();
+        log.debug("Resolved registry descriptor %s from %s", registryDescriptorCoords, srcRepoId);
+        if (!singleRegistryRepos.isEmpty()) {
+            if (srcRepoId != null && !"local".equals(srcRepoId)) {
+                String srcRepoUrl = null;
+                for (RemoteRepository repo : resolver.getRepositories()) {
+                    if (repo.getId().equals(srcRepoId)) {
+                        srcRepoUrl = repo.getUrl();
+                        break;
+                    }
+                }
+                if (srcRepoUrl == null) {
+                    throw new IllegalStateException(
+                            "Failed to locate the repository URL corresponding to repository " + srcRepoId);
+                }
+            } else {
+                log.debug("Failed to determine the remote repository for %s registry descriptor %s", config.getId(),
+                        registryDescriptorCoords);
+            }
+        }
+
+        final RegistryConfig descriptor;
+        try {
+            descriptor = RegistriesConfigMapperHelper.deserialize(result.getArtifact().getFile().toPath(),
+                    JsonRegistryConfig.class);
+        } catch (IOException e) {
+            throw new IllegalStateException("Failed to deserialize registries descriptor " + result.getArtifact().getFile(), e);
+        }
+
+        if (!isComplete(config, descriptor)) {
+            final JsonRegistryConfig complete = new JsonRegistryConfig();
+            complete(complete, config, descriptor);
+            config = complete;
+        }
+
+        final RegistryNonPlatformExtensionsResolver nonPlatformExtensionsResolver;
+        final RegistryNonPlatformExtensionsConfig nonPlatformExtensions = config.getNonPlatformExtensions();
+        if (nonPlatformExtensions == null || nonPlatformExtensions.isDisabled()) {
+            log.debug("Non-platform extension catalogs were disabled for registry %s", config.getId());
+            nonPlatformExtensionsResolver = null;
+        } else {
+            nonPlatformExtensionsResolver = new MavenNonPlatformExtensionsResolver(nonPlatformExtensions,
+                    resolver, log);
+        }
+
+        final RegistryPlatformsResolver platformsResolver;
+        final RegistryPlatformsConfig platformsConfig = config.getPlatforms();
+        if (platformsConfig == null || platformsConfig.isDisabled()) {
+            log.debug("Platform catalogs were disabled for registry %s", config.getId());
+            platformsResolver = null;
+        } else {
+            platformsResolver = new MavenPlatformsResolver(platformsConfig, resolver, log);
+        }
+
+        return new RegistryClient(config, platformsResolver,
+                Boolean.TRUE.equals(config.getPlatforms().getExtensionCatalogsIncluded())
+                        ? new MavenPlatformExtensionsResolver(resolver, log)
+                        : new MavenPlatformExtensionsResolver(originalResolver, log),
+                nonPlatformExtensionsResolver);
+    }
+
+    private static void complete(JsonRegistryConfig complete, RegistryConfig original, RegistryConfig descriptor) {
+        complete.setId(original.getId() == null ? descriptor.getId() : original.getId());
+
+        if (original.getDescriptor() == null) {
+            complete.setDescriptor(descriptor.getDescriptor());
+        } else {
+            complete.setDescriptor(original.getDescriptor());
+        }
+        if (original.getPlatforms() == null) {
+            complete.setPlatforms(descriptor.getPlatforms());
+        } else {
+            complete.setPlatforms(complete(original.getPlatforms(), descriptor.getPlatforms()));
+        }
+        if (original.getNonPlatformExtensions() == null) {
+            complete.setNonPlatformExtensions(descriptor.getNonPlatformExtensions());
+        } else {
+            complete.setNonPlatformExtensions(descriptor.getNonPlatformExtensions());
+        }
+
+        if (original.getUpdatePolicy() == null) {
+            complete.setUpdatePolicy(descriptor.getUpdatePolicy());
+        } else {
+            complete.setUpdatePolicy(original.getUpdatePolicy());
+        }
+
+        if (original.getMaven() == null) {
+            complete.setMaven(descriptor.getMaven());
+        } else if (isComplete(original.getMaven())) {
+            complete.setMaven(original.getMaven());
+        } else {
+            final JsonRegistryMavenConfig completeMavenConfig = new JsonRegistryMavenConfig();
+            complete.setMaven(completeMavenConfig);
+            complete(completeMavenConfig, original.getMaven(),
+                    descriptor.getMaven() == null ? completeMavenConfig : descriptor.getMaven());
+        }
+        if (original.getQuarkusVersions() == null) {
+            complete.setQuarkusVersions(descriptor.getQuarkusVersions());
+        }
+    }
+
+    private static RegistryPlatformsConfig complete(RegistryPlatformsConfig client, RegistryPlatformsConfig descriptor) {
+        if (client == null) {
+            return descriptor;
+        }
+        if (isComplete(client)) {
+            return client;
+        }
+        JsonRegistryPlatformsConfig complete = new JsonRegistryPlatformsConfig();
+        complete.setArtifact(client.getArtifact() == null ? descriptor.getArtifact() : client.getArtifact());
+        complete.setDisabled(client.isDisabled());
+        complete.setExtensionCatalogsIncluded(
+                client.getExtensionCatalogsIncluded() == null ? descriptor.getExtensionCatalogsIncluded()
+                        : client.getExtensionCatalogsIncluded());
+        return complete;
+    }
+
+    private static void complete(JsonRegistryMavenConfig complete, RegistryMavenConfig original,
+            RegistryMavenConfig descriptor) {
+        if (original.getRepository() == null) {
+            complete.setRepository(descriptor.getRepository());
+        } else if (isComplete(original.getRepository()) || descriptor.getRepository() == null) {
+            complete.setRepository(original.getRepository());
+        } else {
+            final JsonRegistryMavenRepoConfig completeRepo = new JsonRegistryMavenRepoConfig();
+            complete.setRepository(completeRepo);
+            complete(completeRepo, original.getRepository(), descriptor.getRepository());
+        }
+    }
+
+    private static void complete(JsonRegistryMavenRepoConfig complete, RegistryMavenRepoConfig original,
+            RegistryMavenRepoConfig descriptor) {
+        complete.setId(original.getId() == null ? descriptor.getId() : original.getId());
+        complete.setUrl(original.getUrl() == null ? descriptor.getUrl() : original.getUrl());
+    }
+
+    private static boolean isComplete(RegistryConfig client, RegistryConfig descriptor) {
+        if (client.isDisabled()) {
+            return true;
+        }
+        if (client.getDescriptor() == null) {
+            return false;
+        }
+        if (!isComplete(client.getPlatforms(), descriptor.getPlatforms())) {
+            return false;
+        }
+        if (!isComplete(client.getNonPlatformExtensions())) {
+            return false;
+        }
+        if (!isComplete(client.getMaven())) {
+            return false;
+        }
+        if (client.getQuarkusVersions() == null && descriptor.getQuarkusVersions() != null) {
+            return false;
+        }
+        if (client.getUpdatePolicy() == null && descriptor.getUpdatePolicy() != null) {
+            return false;
+        }
+        return true;
+    }
+
+    private static boolean isComplete(RegistryMavenConfig config) {
+        if (config == null) {
+            return false;
+        }
+        if (!isComplete(config.getRepository())) {
+            return false;
+        }
+        return true;
+    }
+
+    private static boolean isComplete(RegistryPlatformsConfig client, RegistryPlatformsConfig descriptor) {
+        if (!isComplete(client)) {
+            return false;
+        }
+        if (descriptor != null && Boolean.TRUE.equals(descriptor.getExtensionCatalogsIncluded())
+                && client.getExtensionCatalogsIncluded() == null) {
+            return false;
+        }
+        return true;
+    }
+
+    private static boolean isComplete(RegistryArtifactConfig config) {
+        if (config == null) {
+            return false;
+        }
+        if (!config.isDisabled() && config.getArtifact() == null) {
+            return false;
+        }
+        return true;
+    }
+
+    private static boolean isComplete(RegistryMavenRepoConfig config) {
+        if (config == null) {
+            return false;
+        }
+        if (config.getId() == null) {
+            return false;
+        }
+        if (config.getUrl() == null) {
+            return false;
+        }
+        return true;
+    }
+
+    private static MavenArtifactResolver newResolver(MavenArtifactResolver resolver, List<RemoteRepository> aggregatedRepos) {
+        try {
+            final BootstrapMavenContext mvnCtx = new BootstrapMavenContext(
+                    BootstrapMavenContext.config()
+                            .setRepositorySystem(resolver.getSystem())
+                            .setRepositorySystemSession(resolver.getSession())
+                            .setRemoteRepositoryManager(resolver.getRemoteRepositoryManager())
+                            .setRemoteRepositories(aggregatedRepos)
+                            .setLocalRepository(resolver.getMavenContext().getLocalRepo())
+                            .setCurrentProject(resolver.getMavenContext().getCurrentProject()));
+            return new MavenArtifactResolver(mvnCtx);
+        } catch (BootstrapMavenException e) {
+            throw new IllegalStateException("Failed to initialize maven context", e);
+        }
+    }
+
+    private void determineExtraRepos(RegistryConfig config,
+            List<RemoteRepository> configuredRepos) {
+        final RegistryMavenConfig mavenConfig = config.getMaven() == null ? null : config.getMaven();
+        final RegistryMavenRepoConfig repoConfig = mavenConfig == null ? null : mavenConfig.getRepository();
+        final String repoUrl = repoConfig == null || repoConfig.getUrl() == null
+                ? Constants.DEFAULT_REGISTRY_BACKUP_MAVEN_REPO_URL
+                : repoConfig.getUrl();
+        addRegistryRepo(repoUrl, repoConfig == null || repoConfig.getId() == null ? config.getId() : repoConfig.getId(),
+                config.getUpdatePolicy(),
+                configuredRepos);
+    }
+
+    private void addRegistryRepo(final String repoUrl, String defaultRepoId, String updatePolicy,
+            List<RemoteRepository> configuredRepos) {
+        final Set<String> ids = new HashSet<>(configuredRepos.size());
+        for (RemoteRepository repo : configuredRepos) {
+            if (repo.getUrl().equals(repoUrl)) {
+                return;
+            }
+            ids.add(repo.getId());
+        }
+
+        String repoId = defaultRepoId;
+        if (ids.contains(repoId)) {
+            int i = 2;
+            String tmp;
+            do {
+                tmp = repoId + "-" + i++;
+            } while (!ids.contains(tmp));
+            repoId = tmp;
+        }
+
+        final RemoteRepository.Builder repoBuilder = new RemoteRepository.Builder(repoId, "default", repoUrl);
+
+        if (updatePolicy != null) {
+            if (updatePolicy.equalsIgnoreCase(RepositoryPolicy.UPDATE_POLICY_DAILY)
+                    || updatePolicy.equalsIgnoreCase(RepositoryPolicy.UPDATE_POLICY_ALWAYS)
+                    || updatePolicy.equalsIgnoreCase(RepositoryPolicy.UPDATE_POLICY_NEVER)
+                    || updatePolicy.startsWith(RepositoryPolicy.UPDATE_POLICY_INTERVAL)) {
+                repoBuilder.setPolicy(new RepositoryPolicy(true, updatePolicy, RepositoryPolicy.CHECKSUM_POLICY_WARN));
+            } else {
+                throw new IllegalStateException("Unrecognized update policy '" + updatePolicy + "' for repository " + repoId);
+            }
+        }
+
+        singleRegistryRepos.add(repoBuilder.build());
+    }
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/PropertiesUtil.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/PropertiesUtil.java
new file mode 100644
index 00000000000000..f8a22577b0cbac
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/PropertiesUtil.java
@@ -0,0 +1,66 @@
+package io.quarkus.registry.config;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Locale;
+
+public class PropertiesUtil {
+
+    private static final String OS_NAME = "os.name";
+    private static final String USER_HOME = "user.home";
+    private static final String WINDOWS = "windows";
+
+    private static final String FALSE = "false";
+    private static final String TRUE = "true";
+
+    private PropertiesUtil() {
+    }
+
+    public static boolean isWindows() {
+        return getProperty(OS_NAME).toLowerCase(Locale.ENGLISH).contains(WINDOWS);
+    }
+
+    public static String getUserHome() {
+        return getProperty(USER_HOME);
+    }
+
+    public static String getProperty(final String name, String defValue) {
+        assert name != null : "name is null";
+        final SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            return AccessController.doPrivileged(new PrivilegedAction<String>() {
+                @Override
+                public String run() {
+                    return System.getProperty(name, defValue);
+                }
+            });
+        } else {
+            return System.getProperty(name, defValue);
+        }
+    }
+
+    public static String getProperty(final String name) {
+        assert name != null : "name is null";
+        final SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            return AccessController.doPrivileged(new PrivilegedAction<String>() {
+                @Override
+                public String run() {
+                    return System.getProperty(name);
+                }
+            });
+        } else {
+            return System.getProperty(name);
+        }
+    }
+
+    public static final Boolean getBooleanOrNull(String name) {
+        final String value = getProperty(name);
+        return value == null ? null : Boolean.parseBoolean(value);
+    }
+
+    public static final boolean getBoolean(String name, boolean notFoundValue) {
+        final String value = getProperty(name, (notFoundValue ? TRUE : FALSE));
+        return value.isEmpty() ? true : Boolean.parseBoolean(value);
+    }
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/RegistriesConfig.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/RegistriesConfig.java
new file mode 100644
index 00000000000000..80357d5e6d3298
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/RegistriesConfig.java
@@ -0,0 +1,10 @@
+package io.quarkus.registry.config;
+
+import java.util.List;
+
+public interface RegistriesConfig {
+
+    boolean isDebug();
+
+    List<RegistryConfig> getRegistries();
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/RegistriesConfigLocator.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/RegistriesConfigLocator.java
new file mode 100644
index 00000000000000..9b83b2062f4700
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/RegistriesConfigLocator.java
@@ -0,0 +1,203 @@
+package io.quarkus.registry.config;
+
+import io.quarkus.maven.ArtifactCoords;
+import io.quarkus.registry.Constants;
+import io.quarkus.registry.config.json.JsonRegistriesConfig;
+import io.quarkus.registry.config.json.JsonRegistryConfig;
+import io.quarkus.registry.config.json.JsonRegistryDescriptorConfig;
+import io.quarkus.registry.config.json.JsonRegistryMavenConfig;
+import io.quarkus.registry.config.json.JsonRegistryMavenRepoConfig;
+import io.quarkus.registry.config.json.JsonRegistryNonPlatformExtensionsConfig;
+import io.quarkus.registry.config.json.JsonRegistryPlatformsConfig;
+import io.quarkus.registry.config.json.RegistriesConfigMapperHelper;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Objects;
+
+public class RegistriesConfigLocator {
+
+    public static final String CONFIG_RELATIVE_PATH = ".quarkus/config.yaml";
+    public static final String CONFIG_FILE_PATH_PROPERTY = "qer.config";
+
+    public static RegistriesConfig resolveConfig() {
+        final Path configYaml = locateConfigYaml();
+        if (configYaml == null) {
+            return completeRequiredConfig(new JsonRegistriesConfig());
+        }
+        return load(configYaml);
+    }
+
+    public static RegistriesConfig load(Path configYaml) {
+        try {
+            return completeRequiredConfig(RegistriesConfigMapperHelper.deserialize(configYaml, JsonRegistriesConfig.class));
+        } catch (IOException e) {
+            throw new IllegalStateException("Failed to parse config file " + configYaml, e);
+        }
+    }
+
+    public static RegistriesConfig load(InputStream configYaml) {
+        try {
+            return completeRequiredConfig(RegistriesConfigMapperHelper.deserializeYaml(configYaml, JsonRegistriesConfig.class));
+        } catch (IOException e) {
+            throw new IllegalStateException("Failed to parse config file " + configYaml, e);
+        }
+    }
+
+    private static Path locateConfigYaml() {
+        final String prop = PropertiesUtil.getProperty(CONFIG_FILE_PATH_PROPERTY);
+        Path configYaml;
+        if (prop != null) {
+            configYaml = Paths.get(prop);
+            if (!Files.exists(configYaml)) {
+                throw new IllegalStateException("Quarkus extensions registry configuration file " + configYaml
+                        + " specified with the system property " + CONFIG_FILE_PATH_PROPERTY + " does not exist");
+            }
+            return configYaml;
+        }
+        configYaml = Paths.get("").normalize().toAbsolutePath().resolve(CONFIG_RELATIVE_PATH);
+        if (Files.exists(configYaml)) {
+            return configYaml;
+        }
+        configYaml = Paths.get(PropertiesUtil.getProperty("user.home")).resolve(CONFIG_RELATIVE_PATH);
+        return Files.exists(configYaml) ? configYaml : null;
+    }
+
+    private static RegistriesConfig completeRequiredConfig(RegistriesConfig original) {
+        final JsonRegistriesConfig config = new JsonRegistriesConfig();
+        config.setDebug(original.isDebug());
+        if (original.getRegistries().isEmpty()) {
+            config.addRegistry(getDefaultRegistry());
+        } else {
+            for (RegistryConfig qerConfig : original.getRegistries()) {
+                if (!qerConfig.isDisabled()) {
+                    config.addRegistry(completeRequiredConfig(qerConfig));
+                }
+            }
+            if (config.isEmpty()) {
+                config.addRegistry(getDefaultRegistry());
+            }
+        }
+        return config;
+    }
+
+    private static RegistryConfig completeRequiredConfig(RegistryConfig original) {
+        if (hasRequiredConfig(original)) {
+            return original;
+        }
+        final String id = original.getId();
+        final JsonRegistryConfig config = new JsonRegistryConfig(id);
+        config.setUpdatePolicy(original.getUpdatePolicy());
+        config.setDescriptor(completeDescriptor(original));
+        config.setMaven(completeRequiedMavenConfig(original));
+        if (original != null) {
+            if (original.getNonPlatformExtensions() != null) {
+                config.setNonPlatformExtensions(original.getNonPlatformExtensions());
+            }
+            if (original.getPlatforms() != null) {
+                config.setPlatforms(original.getPlatforms());
+            }
+        }
+        return config;
+    }
+
+    private static RegistryMavenConfig completeRequiedMavenConfig(RegistryConfig original) {
+        RegistryMavenConfig originalMaven = original.getMaven();
+        if (hasRequiredConfig(originalMaven)) {
+            return originalMaven;
+        }
+        final JsonRegistryMavenConfig config = new JsonRegistryMavenConfig();
+        if (originalMaven != null) {
+            config.setRepository(originalMaven.getRepository());
+        }
+        return config;
+    }
+
+    private static RegistryDescriptorConfig completeDescriptor(RegistryConfig config) {
+        if (config.getDescriptor() != null && config.getDescriptor().getArtifact() != null) {
+            return config.getDescriptor();
+        }
+        final JsonRegistryDescriptorConfig descriptor = new JsonRegistryDescriptorConfig();
+        String host = config.getId();
+        if (host == null) {
+            final RegistryMavenRepoConfig repo = config.getMaven() == null ? null : config.getMaven().getRepository();
+            if (repo != null && repo.getUrl() != null) {
+                throw new IllegalStateException(
+                        "Failed to determine the descriptor coordinates for a registry with no ID and no Maven configuration");
+            }
+            host = Objects.requireNonNull(toUrlOrNull(repo.getUrl()), "REST endpoint is not a valid URL").getHost();
+        }
+        final String[] parts = host.split("\\.");
+        final StringBuilder buf = new StringBuilder(host.length());
+        int i = parts.length;
+        buf.append(parts[--i]);
+        while (--i >= 0) {
+            buf.append('.').append(parts[i]);
+        }
+        descriptor.setArtifact(
+                new ArtifactCoords(buf.toString(), Constants.DEFAULT_REGISTRY_DESCRIPTOR_ARTIFACT_ID, null, Constants.JSON,
+                        Constants.DEFAULT_REGISTRY_ARTIFACT_VERSION));
+        return descriptor;
+    }
+
+    private static URL toUrlOrNull(String str) {
+        try {
+            return new URL(str);
+        } catch (MalformedURLException e) {
+        }
+        return null;
+    }
+
+    private static boolean hasRequiredConfig(RegistryConfig qerConfig) {
+        if (qerConfig.getId() == null) {
+            return false;
+        }
+        if (qerConfig.getDescriptor() == null) {
+            return false;
+        }
+        return hasRequiredConfig(qerConfig.getMaven());
+    }
+
+    private static boolean hasRequiredConfig(RegistryMavenConfig maven) {
+        if (maven == null) {
+            return false;
+        }
+        return true;
+    }
+
+    public static RegistryConfig getDefaultRegistry() {
+        final JsonRegistryConfig qer = new JsonRegistryConfig();
+        qer.setId(Constants.DEFAULT_REGISTRY_ID);
+
+        final JsonRegistryDescriptorConfig descriptor = new JsonRegistryDescriptorConfig();
+        qer.setDescriptor(descriptor);
+        descriptor.setArtifact(
+                new ArtifactCoords(Constants.DEFAULT_REGISTRY_GROUP_ID, Constants.DEFAULT_REGISTRY_DESCRIPTOR_ARTIFACT_ID, null,
+                        Constants.JSON, Constants.DEFAULT_REGISTRY_ARTIFACT_VERSION));
+
+        final JsonRegistryMavenConfig mavenConfig = new JsonRegistryMavenConfig();
+        qer.setMaven(mavenConfig);
+
+        final JsonRegistryPlatformsConfig platformsConfig = new JsonRegistryPlatformsConfig();
+        qer.setPlatforms(platformsConfig);
+        platformsConfig.setArtifact(new ArtifactCoords(Constants.DEFAULT_REGISTRY_GROUP_ID,
+                Constants.DEFAULT_REGISTRY_PLATFORMS_CATALOG_ARTIFACT_ID, null, Constants.JSON,
+                Constants.DEFAULT_REGISTRY_ARTIFACT_VERSION));
+
+        final JsonRegistryNonPlatformExtensionsConfig nonPlatformExtensionsConfig = new JsonRegistryNonPlatformExtensionsConfig();
+        qer.setNonPlatformExtensions(nonPlatformExtensionsConfig);
+        nonPlatformExtensionsConfig.setArtifact(new ArtifactCoords(Constants.DEFAULT_REGISTRY_GROUP_ID,
+                Constants.DEFAULT_REGISTRY_NON_PLATFORM_EXTENSIONS_CATALOG_ARTIFACT_ID, null, Constants.JSON,
+                Constants.DEFAULT_REGISTRY_ARTIFACT_VERSION));
+
+        final JsonRegistryMavenRepoConfig mavenRepo = new JsonRegistryMavenRepoConfig();
+        mavenConfig.setRepository(mavenRepo);
+        mavenRepo.setId(Constants.DEFAULT_REGISTRY_ID);
+        mavenRepo.setUrl(Constants.DEFAULT_REGISTRY_MAVEN_REPO_URL);
+        return qer;
+    }
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/RegistryArtifactConfig.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/RegistryArtifactConfig.java
new file mode 100644
index 00000000000000..55cdcb8a34a701
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/RegistryArtifactConfig.java
@@ -0,0 +1,10 @@
+package io.quarkus.registry.config;
+
+import io.quarkus.maven.ArtifactCoords;
+
+public interface RegistryArtifactConfig {
+
+    boolean isDisabled();
+
+    ArtifactCoords getArtifact();
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/RegistryConfig.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/RegistryConfig.java
new file mode 100644
index 00000000000000..ef758108e7c272
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/RegistryConfig.java
@@ -0,0 +1,20 @@
+package io.quarkus.registry.config;
+
+public interface RegistryConfig {
+
+    String getId();
+
+    boolean isDisabled();
+
+    String getUpdatePolicy();
+
+    RegistryDescriptorConfig getDescriptor();
+
+    RegistryPlatformsConfig getPlatforms();
+
+    RegistryNonPlatformExtensionsConfig getNonPlatformExtensions();
+
+    RegistryMavenConfig getMaven();
+
+    RegistryQuarkusVersionsConfig getQuarkusVersions();
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/RegistryDescriptorConfig.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/RegistryDescriptorConfig.java
new file mode 100644
index 00000000000000..3c1fce320611f9
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/RegistryDescriptorConfig.java
@@ -0,0 +1,8 @@
+package io.quarkus.registry.config;
+
+import io.quarkus.maven.ArtifactCoords;
+
+public interface RegistryDescriptorConfig {
+
+    ArtifactCoords getArtifact();
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/RegistryMavenConfig.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/RegistryMavenConfig.java
new file mode 100644
index 00000000000000..0154206d866fdb
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/RegistryMavenConfig.java
@@ -0,0 +1,6 @@
+package io.quarkus.registry.config;
+
+public interface RegistryMavenConfig {
+
+    RegistryMavenRepoConfig getRepository();
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/RegistryMavenRepoConfig.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/RegistryMavenRepoConfig.java
new file mode 100644
index 00000000000000..601d352246efd3
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/RegistryMavenRepoConfig.java
@@ -0,0 +1,8 @@
+package io.quarkus.registry.config;
+
+public interface RegistryMavenRepoConfig {
+
+    String getId();
+
+    String getUrl();
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/RegistryNonPlatformExtensionsConfig.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/RegistryNonPlatformExtensionsConfig.java
new file mode 100644
index 00000000000000..e0999e39a91d61
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/RegistryNonPlatformExtensionsConfig.java
@@ -0,0 +1,5 @@
+package io.quarkus.registry.config;
+
+public interface RegistryNonPlatformExtensionsConfig extends RegistryArtifactConfig {
+
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/RegistryPlatformsConfig.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/RegistryPlatformsConfig.java
new file mode 100644
index 00000000000000..405dbd2c696392
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/RegistryPlatformsConfig.java
@@ -0,0 +1,6 @@
+package io.quarkus.registry.config;
+
+public interface RegistryPlatformsConfig extends RegistryArtifactConfig {
+
+    Boolean getExtensionCatalogsIncluded();
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/RegistryQuarkusVersionsConfig.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/RegistryQuarkusVersionsConfig.java
new file mode 100644
index 00000000000000..1d3f2fa43b2dc9
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/RegistryQuarkusVersionsConfig.java
@@ -0,0 +1,8 @@
+package io.quarkus.registry.config;
+
+public interface RegistryQuarkusVersionsConfig {
+
+    String getRecognizedVersionsExpression();
+
+    boolean isExclusiveProvider();
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistriesConfig.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistriesConfig.java
new file mode 100644
index 00000000000000..920e76e8c544ff
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistriesConfig.java
@@ -0,0 +1,50 @@
+package io.quarkus.registry.config.json;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import io.quarkus.registry.config.RegistriesConfig;
+import io.quarkus.registry.config.RegistryConfig;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class JsonRegistriesConfig implements RegistriesConfig {
+
+    private boolean debug;
+    private List<RegistryConfig> registries = Collections.emptyList();
+
+    @Override
+    @JsonDeserialize(contentUsing = JsonRegistryConfigDeserializer.class)
+    @JsonSerialize(contentUsing = JsonRegistryConfigSerializer.class)
+    public List<RegistryConfig> getRegistries() {
+        return registries;
+    }
+
+    public void setRegistries(List<RegistryConfig> registries) {
+        this.registries = registries == null ? Collections.emptyList() : registries;
+    }
+
+    public void addRegistry(RegistryConfig registry) {
+        if (registries.isEmpty()) {
+            registries = new ArrayList<>();
+        }
+        registries.add(registry);
+    }
+
+    @Override
+    public boolean isDebug() {
+        return debug;
+    }
+
+    public void setDebug(boolean debug) {
+        this.debug = debug;
+    }
+
+    @JsonIgnore
+    public boolean isEmpty() {
+        return registries.isEmpty();
+    }
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistryArtifactConfig.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistryArtifactConfig.java
new file mode 100644
index 00000000000000..8f36b29ab70dff
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistryArtifactConfig.java
@@ -0,0 +1,30 @@
+package io.quarkus.registry.config.json;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.quarkus.maven.ArtifactCoords;
+import io.quarkus.registry.config.RegistryArtifactConfig;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class JsonRegistryArtifactConfig implements RegistryArtifactConfig {
+
+    protected boolean disabled;
+    protected ArtifactCoords artifact;
+
+    @Override
+    public boolean isDisabled() {
+        return disabled;
+    }
+
+    public void setDisabled(boolean disabled) {
+        this.disabled = disabled;
+    }
+
+    @Override
+    public ArtifactCoords getArtifact() {
+        return artifact;
+    }
+
+    public void setArtifact(ArtifactCoords artifact) {
+        this.artifact = artifact;
+    }
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistryConfig.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistryConfig.java
new file mode 100644
index 00000000000000..f0edf0d9e7fca2
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistryConfig.java
@@ -0,0 +1,118 @@
+package io.quarkus.registry.config.json;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import io.quarkus.registry.config.RegistryConfig;
+import io.quarkus.registry.config.RegistryDescriptorConfig;
+import io.quarkus.registry.config.RegistryMavenConfig;
+import io.quarkus.registry.config.RegistryNonPlatformExtensionsConfig;
+import io.quarkus.registry.config.RegistryPlatformsConfig;
+import io.quarkus.registry.config.RegistryQuarkusVersionsConfig;
+import java.util.Objects;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class JsonRegistryConfig implements RegistryConfig {
+
+    private String id;
+    private boolean disabled;
+    private String updatePolicy;
+    private RegistryDescriptorConfig descriptor;
+    private RegistryPlatformsConfig platforms;
+    private RegistryNonPlatformExtensionsConfig nonPlatformExtensions;
+    private RegistryMavenConfig mavenConfig;
+    private RegistryQuarkusVersionsConfig versionsConfig;
+
+    public JsonRegistryConfig() {
+    }
+
+    public JsonRegistryConfig(String id) {
+        this.id = Objects.requireNonNull(id, "QER ID can't be null");
+    }
+
+    @JsonIgnore
+    @Override
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = Objects.requireNonNull(id);
+    }
+
+    @Override
+    public boolean isDisabled() {
+        return disabled;
+    }
+
+    public void setDisabled(boolean disabled) {
+        this.disabled = disabled;
+    }
+
+    @Override
+    public String getUpdatePolicy() {
+        return updatePolicy;
+    }
+
+    public void setUpdatePolicy(String updatePolicy) {
+        this.updatePolicy = updatePolicy;
+    }
+
+    @Override
+    @JsonDeserialize(as = JsonRegistryDescriptorConfig.class)
+    public RegistryDescriptorConfig getDescriptor() {
+        return descriptor;
+    }
+
+    public void setDescriptor(RegistryDescriptorConfig descriptor) {
+        this.descriptor = descriptor;
+    }
+
+    @JsonDeserialize(as = JsonRegistryPlatformsConfig.class)
+    @Override
+    public RegistryPlatformsConfig getPlatforms() {
+        return platforms;
+    }
+
+    public void setPlatforms(RegistryPlatformsConfig platforms) {
+        this.platforms = platforms;
+    }
+
+    @JsonDeserialize(as = JsonRegistryNonPlatformExtensionsConfig.class)
+    @Override
+    public RegistryNonPlatformExtensionsConfig getNonPlatformExtensions() {
+        return nonPlatformExtensions;
+    }
+
+    public void setNonPlatformExtensions(RegistryNonPlatformExtensionsConfig nonPlatformExtensions) {
+        this.nonPlatformExtensions = nonPlatformExtensions;
+    }
+
+    boolean isIdOnly() {
+        return mavenConfig == null;
+    }
+
+    @JsonDeserialize(as = JsonRegistryMavenConfig.class)
+    @Override
+    public RegistryMavenConfig getMaven() {
+        return mavenConfig;
+    }
+
+    public void setMaven(RegistryMavenConfig mavenConfig) {
+        this.mavenConfig = mavenConfig;
+    }
+
+    @JsonDeserialize(as = JsonRegistryQuarkusVersionsConfig.class)
+    @Override
+    public RegistryQuarkusVersionsConfig getQuarkusVersions() {
+        return versionsConfig;
+    }
+
+    public void setQuarkusVersions(RegistryQuarkusVersionsConfig quarkusVersions) {
+        this.versionsConfig = quarkusVersions;
+    }
+
+    public String toString() {
+        return "[" + id + " maven=" + mavenConfig + "]";
+    }
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistryConfigDeserializer.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistryConfigDeserializer.java
new file mode 100644
index 00000000000000..7ca51a912b2e71
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistryConfigDeserializer.java
@@ -0,0 +1,35 @@
+package io.quarkus.registry.config.json;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.exc.InvalidFormatException;
+import java.io.IOException;
+
+public class JsonRegistryConfigDeserializer extends JsonDeserializer<JsonRegistryConfig> {
+
+    @Override
+    public JsonRegistryConfig deserialize(JsonParser p, DeserializationContext ctxt)
+            throws IOException, JsonProcessingException {
+        if (p.getCurrentToken() == JsonToken.VALUE_STRING) {
+            return new JsonRegistryConfig(p.getText());
+        } else if (p.getCurrentToken() == JsonToken.START_OBJECT) {
+            ensureNextToken(p, JsonToken.FIELD_NAME, ctxt);
+            final String qerId = p.getCurrentName();
+            ensureNextToken(p, JsonToken.START_OBJECT, ctxt);
+            final JsonRegistryConfig qer = p.readValueAs(JsonRegistryConfig.class);
+            qer.setId(qerId);
+            ensureNextToken(p, JsonToken.END_OBJECT, ctxt);
+            return qer;
+        }
+        return null;
+    }
+
+    private void ensureNextToken(JsonParser p, JsonToken expected, DeserializationContext ctxt) throws IOException {
+        if (p.nextToken() != expected) {
+            throw InvalidFormatException.from(p, "Expected " + expected, ctxt, JsonRegistryConfig.class);
+        }
+    }
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistryConfigSerializer.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistryConfigSerializer.java
new file mode 100644
index 00000000000000..f9a58756fceade
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistryConfigSerializer.java
@@ -0,0 +1,39 @@
+package io.quarkus.registry.config.json;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.BeanDescription;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.MapperFeature;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.BeanSerializerFactory;
+import java.io.IOException;
+
+public class JsonRegistryConfigSerializer extends JsonSerializer<JsonRegistryConfig> {
+
+    private JsonSerializer<Object> qerSerializer;
+
+    @Override
+    public void serialize(JsonRegistryConfig value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+        if (value.isIdOnly()) {
+            gen.writeString(value.getId());
+        } else {
+            gen.writeStartObject();
+            gen.writeObjectFieldStart(value.getId());
+            getQerSerializer(serializers).unwrappingSerializer(null).serialize(value, gen, serializers);
+            gen.writeEndObject();
+            gen.writeEndObject();
+        }
+    }
+
+    private JsonSerializer<Object> getQerSerializer(SerializerProvider serializers) throws JsonMappingException {
+        if (qerSerializer == null) {
+            JavaType javaType = serializers.constructType(JsonRegistryConfig.class);
+            BeanDescription beanDesc = serializers.getConfig().introspect(javaType);
+            qerSerializer = BeanSerializerFactory.instance.findBeanOrAddOnSerializer(serializers, javaType, beanDesc,
+                    serializers.isEnabled(MapperFeature.USE_STATIC_TYPING));
+        }
+        return qerSerializer;
+    }
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistryDescriptorConfig.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistryDescriptorConfig.java
new file mode 100644
index 00000000000000..cc5d6c59f9ca2f
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistryDescriptorConfig.java
@@ -0,0 +1,18 @@
+package io.quarkus.registry.config.json;
+
+import io.quarkus.maven.ArtifactCoords;
+import io.quarkus.registry.config.RegistryDescriptorConfig;
+
+public class JsonRegistryDescriptorConfig implements RegistryDescriptorConfig {
+
+    private ArtifactCoords artifact;
+
+    @Override
+    public ArtifactCoords getArtifact() {
+        return artifact;
+    }
+
+    public void setArtifact(ArtifactCoords artifact) {
+        this.artifact = artifact;
+    }
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistryMavenConfig.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistryMavenConfig.java
new file mode 100644
index 00000000000000..5340eb3f91de36
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistryMavenConfig.java
@@ -0,0 +1,30 @@
+package io.quarkus.registry.config.json;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import io.quarkus.registry.config.RegistryMavenConfig;
+import io.quarkus.registry.config.RegistryMavenRepoConfig;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class JsonRegistryMavenConfig implements RegistryMavenConfig {
+
+    private RegistryMavenRepoConfig repo;
+
+    @Override
+    @JsonDeserialize(as = JsonRegistryMavenRepoConfig.class)
+    public RegistryMavenRepoConfig getRepository() {
+        return repo;
+    }
+
+    public void setRepository(RegistryMavenRepoConfig repo) {
+        this.repo = repo;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder buf = new StringBuilder();
+        buf.append('[');
+        buf.append("repo=").append(repo);
+        return buf.append(']').toString();
+    }
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistryMavenRepoConfig.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistryMavenRepoConfig.java
new file mode 100644
index 00000000000000..7188ed1440e9f2
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistryMavenRepoConfig.java
@@ -0,0 +1,34 @@
+package io.quarkus.registry.config.json;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.quarkus.registry.config.RegistryMavenRepoConfig;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class JsonRegistryMavenRepoConfig implements RegistryMavenRepoConfig {
+
+    private String id;
+    private String url;
+
+    @Override
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    @Override
+    public String getUrl() {
+        return url;
+    }
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    @Override
+    public String toString() {
+        return "[id=" + id + ", url=" + url + "]";
+    }
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistryNonPlatformExtensionsConfig.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistryNonPlatformExtensionsConfig.java
new file mode 100644
index 00000000000000..263022369f3b4b
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistryNonPlatformExtensionsConfig.java
@@ -0,0 +1,8 @@
+package io.quarkus.registry.config.json;
+
+import io.quarkus.registry.config.RegistryNonPlatformExtensionsConfig;
+
+public class JsonRegistryNonPlatformExtensionsConfig extends JsonRegistryArtifactConfig
+        implements RegistryNonPlatformExtensionsConfig {
+
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistryPlatformsConfig.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistryPlatformsConfig.java
new file mode 100644
index 00000000000000..190538bd298a5d
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistryPlatformsConfig.java
@@ -0,0 +1,17 @@
+package io.quarkus.registry.config.json;
+
+import io.quarkus.registry.config.RegistryPlatformsConfig;
+
+public class JsonRegistryPlatformsConfig extends JsonRegistryArtifactConfig implements RegistryPlatformsConfig {
+
+    private Boolean extensionCatalogsIncluded;
+
+    @Override
+    public Boolean getExtensionCatalogsIncluded() {
+        return extensionCatalogsIncluded;
+    }
+
+    public void setExtensionCatalogsIncluded(Boolean extensionCatalogsIncluded) {
+        this.extensionCatalogsIncluded = extensionCatalogsIncluded;
+    }
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistryQuarkusVersionsConfig.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistryQuarkusVersionsConfig.java
new file mode 100644
index 00000000000000..bfaf8b4fd976b4
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistryQuarkusVersionsConfig.java
@@ -0,0 +1,27 @@
+package io.quarkus.registry.config.json;
+
+import io.quarkus.registry.config.RegistryQuarkusVersionsConfig;
+
+public class JsonRegistryQuarkusVersionsConfig implements RegistryQuarkusVersionsConfig {
+
+    private String recognizedVersionsExpression;
+    private boolean exclusiveProvider;
+
+    @Override
+    public String getRecognizedVersionsExpression() {
+        return recognizedVersionsExpression;
+    }
+
+    public void setRecognizedVersionsExpression(String recognizedVersionsExpression) {
+        this.recognizedVersionsExpression = recognizedVersionsExpression;
+    }
+
+    @Override
+    public boolean isExclusiveProvider() {
+        return exclusiveProvider;
+    }
+
+    public void setExclusiveProvider(boolean exclusiveProvider) {
+        this.exclusiveProvider = exclusiveProvider;
+    }
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/RegistriesConfigMapperHelper.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/RegistriesConfigMapperHelper.java
new file mode 100644
index 00000000000000..e5bffc75f8265d
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/RegistriesConfigMapperHelper.java
@@ -0,0 +1,145 @@
+package io.quarkus.registry.config.json;
+
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+import io.quarkus.maven.ArtifactCoords;
+import io.quarkus.registry.catalog.json.JsonArtifactCoordsMixin;
+import io.quarkus.registry.config.RegistriesConfig;
+import io.quarkus.registry.config.RegistriesConfigLocator;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.Writer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+public class RegistriesConfigMapperHelper {
+
+    private static ObjectMapper yamlMapper;
+    private static ObjectMapper jsonMapper;
+
+    public static ObjectMapper yamlMapper() {
+        return yamlMapper == null ? yamlMapper = initMapper(new ObjectMapper(new YAMLFactory())) : yamlMapper;
+    }
+
+    public static ObjectMapper jsonMapper() {
+        return jsonMapper == null ? jsonMapper = initMapper(new ObjectMapper()) : jsonMapper;
+    }
+
+    private static ObjectMapper mapper(Path p) {
+        return p.getFileName().toString().endsWith(".json") ? jsonMapper() : yamlMapper();
+    }
+
+    public static ObjectMapper initMapper(ObjectMapper mapper) {
+        mapper.addMixIn(ArtifactCoords.class, JsonArtifactCoordsMixin.class);
+        mapper.enable(SerializationFeature.INDENT_OUTPUT);
+        mapper.setPropertyNamingStrategy(PropertyNamingStrategies.KEBAB_CASE);
+        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+        return mapper;
+    }
+
+    public static void serialize(Object config, Path p) throws IOException {
+        if (!Files.exists(p.getParent())) {
+            Files.createDirectories(p.getParent());
+        }
+        try (BufferedWriter writer = Files.newBufferedWriter(p)) {
+            mapper(p).writeValue(writer, config);
+        }
+    }
+
+    public static void toJson(Object config, Writer writer) throws IOException {
+        jsonMapper().writeValue(writer, config);
+    }
+
+    public static <T> T deserialize(Path p, Class<T> t) throws IOException {
+        if (!Files.exists(p)) {
+            throw new IllegalArgumentException("File " + p + " does not exist");
+        }
+        try (BufferedReader reader = Files.newBufferedReader(p)) {
+            return mapper(p).readValue(reader, t);
+        }
+    }
+
+    public static <T> T deserializeYaml(InputStream is, Class<T> t) throws IOException {
+        return yamlMapper().readValue(is, t);
+    }
+
+    public static <T> T deserializeYaml(Reader reader, Class<T> t) throws IOException {
+        return yamlMapper().readValue(reader, t);
+    }
+
+    public static void main(String[] args) throws Exception {
+        final Path p = serializeConfigSample();
+        //logDeserialized(p);
+        logLoaded(p);
+    }
+
+    private static Path serializeConfigSample() throws IOException {
+        final JsonRegistriesConfig resolver = new JsonRegistriesConfig();
+
+        JsonRegistryConfig qer = new JsonRegistryConfig("qer.acme.com");
+        resolver.addRegistry(qer);
+
+        qer = new JsonRegistryConfig("qer.acme.com");
+        resolver.addRegistry(qer);
+        JsonRegistryMavenConfig mavenConfig = new JsonRegistryMavenConfig();
+        JsonRegistryMavenRepoConfig mvnRepo = new JsonRegistryMavenRepoConfig();
+        mvnRepo.setUrl("https://maven.acme.com");
+        mavenConfig.setRepository(mvnRepo);
+        qer.setMaven(mavenConfig);
+
+        qer = new JsonRegistryConfig("acme.com");
+        resolver.addRegistry(qer);
+        JsonRegistryDescriptorConfig descr = new JsonRegistryDescriptorConfig();
+        qer.setDescriptor(descr);
+        descr.setArtifact(new ArtifactCoords("com.acme", "registry-descriptor", null, "json", "0.1-SNAPSHOT"));
+
+        mavenConfig = new JsonRegistryMavenConfig();
+        mvnRepo = new JsonRegistryMavenRepoConfig();
+        mvnRepo.setUrl("https://acme.com/maven");
+        mavenConfig.setRepository(mvnRepo);
+        qer.setMaven(mavenConfig);
+
+        qer = new JsonRegistryConfig("all");
+        resolver.addRegistry(qer);
+        descr = new JsonRegistryDescriptorConfig();
+        qer.setDescriptor(descr);
+        descr.setArtifact(new ArtifactCoords("org.acme", "registry-descriptor", null, "json", "0.1-SNAPSHOT"));
+
+        mavenConfig = new JsonRegistryMavenConfig();
+        mvnRepo = new JsonRegistryMavenRepoConfig();
+        mvnRepo.setUrl("https://acme.com/rest/api/maven");
+        mavenConfig.setRepository(mvnRepo);
+        qer.setMaven(mavenConfig);
+
+        final Path p = Paths.get(System.getProperty("user.home")).resolve("test-qer-resolver-config.yaml");
+        if (Files.exists(p)) {
+            Files.delete(p);
+        }
+        serialize(resolver, p);
+        return p;
+    }
+
+    private static void logDeserialized(Path p) throws IOException {
+        JsonRegistriesConfig resolver = deserialize(p, JsonRegistriesConfig.class);
+        System.out.println("QER RESOLVER:");
+        resolver.getRegistries().forEach(qer -> {
+            System.out.println(" - " + qer);
+        });
+    }
+
+    private static void logLoaded(Path p) throws IOException {
+        RegistriesConfig resolver = RegistriesConfigLocator.load(p);
+        System.out.println("QER RESOLVER:");
+        resolver.getRegistries().forEach(qer -> {
+            System.out.println(" - " + qer);
+        });
+    }
+
+}
diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/util/GlobUtil.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/util/GlobUtil.java
new file mode 100644
index 00000000000000..bb336dca6b33da
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/util/GlobUtil.java
@@ -0,0 +1,209 @@
+package io.quarkus.registry.util;
+
+import java.util.regex.Pattern;
+
+public class GlobUtil {
+
+    private GlobUtil() {
+    }
+
+    /**
+     * Transforms the given {@code glob} to a regular expression suitable for passing to
+     * {@link Pattern#compile(String)}.
+     *
+     * <h2>Glob syntax
+     * <h2>
+     *
+     * <table>
+     * <tr>
+     * <th>Construct</th>
+     * <th>Description</th>
+     * </tr>
+     * <tr>
+     * <td><code>*</code></td>
+     * <td>Matches a (possibly empty) sequence of characters that does not contain slash ({@code /})</td>
+     * </tr>
+     * <tr>
+     * <td><code>**</code></td>
+     * <td>Matches a (possibly empty) sequence of characters that may contain slash ({@code /})</td>
+     * </tr>
+     * <tr>
+     * <td><code>?</code></td>
+     * <td>Matches one character, but not slash</td>
+     * </tr>
+     * <tr>
+     * <td><code>[abc]</code></td>
+     * <td>Matches one character given in the bracket, but not slash</td>
+     * </tr>
+     * <tr>
+     * <td><code>[a-z]</code></td>
+     * <td>Matches one character from the range given in the bracket, but not slash</td>
+     * </tr>
+     * <tr>
+     * <td><code>[!abc]</code></td>
+     * <td>Matches one character not named in the bracket; does not match slash</td>
+     * </tr>
+     * <tr>
+     * <td><code>[a-z]</code></td>
+     * <td>Matches one character outside the range given in the bracket; does not match slash</td>
+     * </tr>
+     * <tr>
+     * <td><code>{one,two,three}</code></td>
+     * <td>Matches any of the alternating tokens separated by comma; the tokens may contain wildcards, nested
+     * alternations and ranges</td>
+     * </tr>
+     * <tr>
+     * <td><code>\</code></td>
+     * <td>The escape character</td>
+     * </tr>
+     * </table>
+     *
+     * @param glob the glob expression to transform
+     * @return a regular expression suitable for {@link Pattern}
+     * @throws IllegalStateException in case the {@code glob} is syntactically invalid
+     */
+    public static String toRegexPattern(String glob) {
+        final int length = glob.length();
+        final StringBuilder result = new StringBuilder(length + 4);
+        glob(glob, 0, length, null, result);
+        return result.toString();
+    }
+
+    private static int glob(String glob, int i, int length, String stopChars, StringBuilder result) {
+        while (i < length) {
+            char current = glob.charAt(i++);
+            switch (current) {
+                case '*':
+                    if (i < length && glob.charAt(i) == '*') {
+                        result.append(".*");
+                        i++;
+                    } else {
+                        result.append("[^/]*");
+                    }
+                    break;
+                case '?':
+                    result.append("[^/]");
+                    break;
+                case '[':
+                    i = charClass(glob, i, length, result);
+                    break;
+                case '{':
+                    i = alternation(glob, i, length, result);
+                    break;
+                case '\\':
+                    i = unescape(glob, i, length, result, false);
+                    break;
+                default:
+                    if (stopChars != null && stopChars.indexOf(current) >= 0) {
+                        i--;
+                        return i;
+                    } else {
+                        escapeIfNeeded(current, result);
+                    }
+                    break;
+            }
+        }
+        return i;
+    }
+
+    private static int alternation(String glob, int i, int length, StringBuilder result) {
+        result.append("(?:");
+        while (i < length) {
+            char current = glob.charAt(i++);
+            switch (current) {
+                case '}':
+                    result.append(')');
+                    return i;
+                case ',':
+                    result.append('|');
+                    i = glob(glob, i, length, ",}", result);
+                    break;
+                default:
+                    i--;
+                    i = glob(glob, i, length, ",}", result);
+                    break;
+            }
+        }
+        throw new IllegalStateException(String.format("Missing } at the end of input in glob %s", glob));
+    }
+
+    private static int unescape(String glob, int i, int length, StringBuilder result, boolean charClass) {
+        if (i < length) {
+            final char current = glob.charAt(i++);
+            if (charClass) {
+                escapeCharClassIfNeeded(current, result);
+            } else {
+                escapeIfNeeded(current, result);
+            }
+            return i;
+        } else {
+            throw new IllegalStateException(
+                    String.format("Incomplete escape sequence at the end of input in glob %s", glob));
+        }
+    }
+
+    private static int charClass(String glob, int i, int length, StringBuilder result) {
+        result.append("[[^/]&&[");
+        if (i < length && glob.charAt(i) == '!') {
+            i++;
+            result.append('^');
+        }
+        while (i < length) {
+            char current = glob.charAt(i++);
+            switch (current) {
+                case ']':
+                    result.append("]]");
+                    return i;
+                case '-':
+                    result.append('-');
+                    break;
+                case '\\':
+                    i = unescape(glob, i, length, result, true);
+                    break;
+                default:
+                    escapeCharClassIfNeeded(current, result);
+                    break;
+            }
+        }
+        throw new IllegalStateException(String.format("Missing ] at the end of input in glob %s", glob));
+    }
+
+    private static void escapeIfNeeded(char current, StringBuilder result) {
+        switch (current) {
+            case '*':
+            case '?':
+            case '+':
+            case '.':
+            case '^':
+            case '$':
+            case '{':
+            case '[':
+            case ']':
+            case '|':
+            case '(':
+            case ')':
+            case '\\':
+                result.append('\\');
+                break;
+            default:
+                break;
+        }
+        result.append(current);
+    }
+
+    private static void escapeCharClassIfNeeded(char current, StringBuilder result) {
+        switch (current) {
+            case '^':
+            case '[':
+            case ']':
+            case '&':
+            case '-':
+            case '\\':
+                result.append('\\');
+                break;
+            default:
+                break;
+        }
+        result.append(current);
+    }
+}
diff --git a/independent-projects/tools/registry-client/src/test/java/io/quarkus/devtools/registry/catalog/ExtensionPredicateTest.java b/independent-projects/tools/registry-client/src/test/java/io/quarkus/devtools/registry/catalog/ExtensionPredicateTest.java
new file mode 100644
index 00000000000000..bb9d419c7e9e46
--- /dev/null
+++ b/independent-projects/tools/registry-client/src/test/java/io/quarkus/devtools/registry/catalog/ExtensionPredicateTest.java
@@ -0,0 +1,39 @@
+package io.quarkus.devtools.registry.catalog;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import io.quarkus.maven.ArtifactCoords;
+import io.quarkus.registry.catalog.ExtensionPredicate;
+import io.quarkus.registry.catalog.json.JsonExtension;
+import java.util.Arrays;
+import org.junit.jupiter.api.Test;
+
+class ExtensionPredicateTest {
+
+    @Test
+    void rejectUnlisted() {
+        ExtensionPredicate predicate = new ExtensionPredicate("foo");
+        JsonExtension extension = new JsonExtension();
+        extension.setArtifact(new ArtifactCoords("g", "a", null, "jar", "v"));
+        extension.setUnlisted(true);
+        assertThat(predicate).rejects(extension);
+    }
+
+    @Test
+    void acceptKeywordInArtifactId() {
+        ExtensionPredicate predicate = new ExtensionPredicate("foo");
+        JsonExtension extension = new JsonExtension();
+        extension.setArtifact(new ArtifactCoords("g", "foo-bar", null, "jar", "1.0"));
+        assertThat(predicate).accepts(extension);
+    }
+
+    @Test
+    void acceptKeywordInLabel() {
+        ExtensionPredicate predicate = new ExtensionPredicate("foo");
+        JsonExtension extension = new JsonExtension();
+        extension.setArtifact(new ArtifactCoords("g", "a", null, "jar", "1.0"));
+        extension.setKeywords(Arrays.asList("foo", "bar"));
+        assertThat(predicate).accepts(extension);
+    }
+
+}
diff --git a/integration-tests/devtools/pom.xml b/integration-tests/devtools/pom.xml
index 06ec6ff1fa4bc4..27aa22d17fb99e 100644
--- a/integration-tests/devtools/pom.xml
+++ b/integration-tests/devtools/pom.xml
@@ -23,7 +23,7 @@
         <!-- test dependencies -->
         <dependency>
             <groupId>io.quarkus</groupId>
-            <artifactId>quarkus-platform-descriptor-resolver-json</artifactId>
+            <artifactId>quarkus-test-devtools</artifactId>
             <scope>test</scope>
         </dependency>
         <dependency>
@@ -51,6 +51,27 @@
 
     <build>
         <plugins>
+            <plugin>
+                <artifactId>maven-resources-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>copy-resources</id>
+                        <phase>process-test-resources</phase>
+                        <goals>
+                            <goal>testResources</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${basedir}/target/test-classes</outputDirectory>
+                            <resources>
+                                <resource>
+                                    <directory>${basedir}/src/test/resources</directory>
+                                    <filtering>true</filtering>
+                                </resource>
+                            </resources>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-surefire-plugin</artifactId>
diff --git a/integration-tests/devtools/src/test/java/io/quarkus/devtools/PlatformAwareTestBase.java b/integration-tests/devtools/src/test/java/io/quarkus/devtools/PlatformAwareTestBase.java
index 60c229fab484c8..d08612fce384d1 100644
--- a/integration-tests/devtools/src/test/java/io/quarkus/devtools/PlatformAwareTestBase.java
+++ b/integration-tests/devtools/src/test/java/io/quarkus/devtools/PlatformAwareTestBase.java
@@ -1,27 +1,100 @@
 package io.quarkus.devtools;
 
+import static io.quarkus.devtools.codestarts.quarkus.QuarkusCodestartData.QuarkusDataKey.BOM_ARTIFACT_ID;
+import static io.quarkus.devtools.codestarts.quarkus.QuarkusCodestartData.QuarkusDataKey.BOM_GROUP_ID;
+import static io.quarkus.devtools.codestarts.quarkus.QuarkusCodestartData.QuarkusDataKey.BOM_VERSION;
+import static io.quarkus.devtools.codestarts.quarkus.QuarkusCodestartData.QuarkusDataKey.JAVA_VERSION;
+import static io.quarkus.devtools.codestarts.quarkus.QuarkusCodestartData.QuarkusDataKey.KOTLIN_VERSION;
+import static io.quarkus.devtools.codestarts.quarkus.QuarkusCodestartData.QuarkusDataKey.MAVEN_COMPILER_PLUGIN_VERSION;
+import static io.quarkus.devtools.codestarts.quarkus.QuarkusCodestartData.QuarkusDataKey.MAVEN_SUREFIRE_PLUGIN_VERSION;
+import static io.quarkus.devtools.codestarts.quarkus.QuarkusCodestartData.QuarkusDataKey.PROJECT_ARTIFACT_ID;
+import static io.quarkus.devtools.codestarts.quarkus.QuarkusCodestartData.QuarkusDataKey.PROJECT_GROUP_ID;
+import static io.quarkus.devtools.codestarts.quarkus.QuarkusCodestartData.QuarkusDataKey.PROJECT_VERSION;
+import static io.quarkus.devtools.codestarts.quarkus.QuarkusCodestartData.QuarkusDataKey.QUARKUS_GRADLE_PLUGIN_ID;
+import static io.quarkus.devtools.codestarts.quarkus.QuarkusCodestartData.QuarkusDataKey.QUARKUS_GRADLE_PLUGIN_VERSION;
+import static io.quarkus.devtools.codestarts.quarkus.QuarkusCodestartData.QuarkusDataKey.QUARKUS_MAVEN_PLUGIN_ARTIFACT_ID;
+import static io.quarkus.devtools.codestarts.quarkus.QuarkusCodestartData.QuarkusDataKey.QUARKUS_MAVEN_PLUGIN_GROUP_ID;
+import static io.quarkus.devtools.codestarts.quarkus.QuarkusCodestartData.QuarkusDataKey.QUARKUS_MAVEN_PLUGIN_VERSION;
+import static io.quarkus.devtools.codestarts.quarkus.QuarkusCodestartData.QuarkusDataKey.QUARKUS_VERSION;
+import static io.quarkus.devtools.codestarts.quarkus.QuarkusCodestartData.QuarkusDataKey.SCALA_MAVEN_PLUGIN_VERSION;
+import static io.quarkus.devtools.codestarts.quarkus.QuarkusCodestartData.QuarkusDataKey.SCALA_VERSION;
+
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Properties;
 
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
-import io.quarkus.platform.descriptor.resolver.json.QuarkusJsonPlatformDescriptorResolver;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+
+import io.quarkus.devtools.project.QuarkusProjectHelper;
+import io.quarkus.devtools.test.DevToolsRegistryTestHelper;
+import io.quarkus.maven.ArtifactCoords;
+import io.quarkus.platform.descriptor.loader.json.ResourceLoader;
+import io.quarkus.platform.tools.ToolsConstants;
 import io.quarkus.platform.tools.ToolsUtils;
+import io.quarkus.registry.catalog.ExtensionCatalog;
 
 public class PlatformAwareTestBase {
 
-    private QuarkusPlatformDescriptor platformDescr;
+    private ExtensionCatalog catalog;
     private Properties quarkusProps;
 
-    protected QuarkusPlatformDescriptor getPlatformDescriptor() {
-        return platformDescr == null
-                ? platformDescr = QuarkusJsonPlatformDescriptorResolver.newInstance().resolveBundled()
-                : platformDescr;
+    @BeforeAll
+    static void enableDevToolsTestConfig() {
+        DevToolsRegistryTestHelper.enableDevToolsTestConfig();
+    }
+
+    @AfterAll
+    static void disableDevToolsTestConfig() {
+        DevToolsRegistryTestHelper.disableDevToolsTestConfig();
     }
 
-    private Properties getQuarkusProperties() {
-        if (quarkusProps == null) {
-            quarkusProps = ToolsUtils.readQuarkusProperties(getPlatformDescriptor());
+    protected ResourceLoader getCodestartsResourceLoader() {
+        return QuarkusProjectHelper.getResourceLoader(getExtensionsCatalog());
+    }
+
+    protected ExtensionCatalog getExtensionsCatalog() {
+        if (catalog == null) {
+            try {
+                catalog = QuarkusProjectHelper.getCatalogResolver().resolveExtensionCatalog();
+            } catch (Exception e) {
+                throw new RuntimeException("Failed to resolve extensions catalog", e);
+            }
         }
-        return quarkusProps;
+        return catalog;
+    }
+
+    protected Map<String, Object> getTestInputData(ExtensionCatalog catalog, Map<String, Object> override) {
+
+        final ArtifactCoords bom = catalog.getBom();
+
+        final HashMap<String, Object> data = new HashMap<>();
+        final Properties quarkusProp = getQuarkusProperties();
+        data.put(PROJECT_GROUP_ID.key(), "org.test");
+        data.put(PROJECT_ARTIFACT_ID.key(), "test-codestart");
+        data.put(PROJECT_VERSION.key(), "1.0.0-codestart");
+        data.put(BOM_GROUP_ID.key(), bom.getGroupId());
+        data.put(BOM_ARTIFACT_ID.key(), bom.getArtifactId());
+        data.put(BOM_VERSION.key(), bom.getVersion());
+        data.put(QUARKUS_VERSION.key(), catalog.getQuarkusCoreVersion());
+        data.put(QUARKUS_MAVEN_PLUGIN_GROUP_ID.key(), "io.quarkus");
+        data.put(QUARKUS_MAVEN_PLUGIN_ARTIFACT_ID.key(), "quarkus-maven-plugin");
+        data.put(QUARKUS_MAVEN_PLUGIN_VERSION.key(), catalog.getQuarkusCoreVersion());
+        data.put(QUARKUS_GRADLE_PLUGIN_ID.key(), "io.quarkus");
+        data.put(QUARKUS_GRADLE_PLUGIN_VERSION.key(), catalog.getQuarkusCoreVersion());
+        data.put(KOTLIN_VERSION.key(), quarkusProp.getProperty(ToolsConstants.PROP_KOTLIN_VERSION));
+        data.put(SCALA_VERSION.key(), quarkusProp.getProperty(ToolsConstants.PROP_SCALA_VERSION));
+        data.put(SCALA_MAVEN_PLUGIN_VERSION.key(), quarkusProp.getProperty(ToolsConstants.PROP_SCALA_PLUGIN_VERSION));
+        data.put(MAVEN_COMPILER_PLUGIN_VERSION.key(), quarkusProp.getProperty(ToolsConstants.PROP_COMPILER_PLUGIN_VERSION));
+        data.put(MAVEN_SUREFIRE_PLUGIN_VERSION.key(), quarkusProp.getProperty(ToolsConstants.PROP_SUREFIRE_PLUGIN_VERSION));
+        data.put(JAVA_VERSION.key(), "11");
+        if (override != null)
+            data.putAll(override);
+        return data;
+    }
+
+    protected Properties getQuarkusProperties() {
+        return quarkusProps == null ? quarkusProps = ToolsUtils.readQuarkusProperties(getExtensionsCatalog()) : quarkusProps;
     }
 
     protected String getMavenPluginGroupId() {
@@ -40,15 +113,7 @@ protected String getQuarkusCoreVersion() {
         return ToolsUtils.getQuarkusCoreVersion(getQuarkusProperties());
     }
 
-    protected String getBomGroupId() {
-        return getPlatformDescriptor().getBomGroupId();
-    }
-
-    protected String getBomArtifactId() {
-        return getPlatformDescriptor().getBomArtifactId();
-    }
-
     protected String getBomVersion() {
-        return getPlatformDescriptor().getBomVersion();
+        return getQuarkusCoreVersion();
     }
 }
diff --git a/integration-tests/devtools/src/test/java/io/quarkus/devtools/codestarts/jbang/QuarkusJBangCodestartGenerationTest.java b/integration-tests/devtools/src/test/java/io/quarkus/devtools/codestarts/jbang/QuarkusJBangCodestartGenerationTest.java
index 968d8ec261b642..9447516d1a5c56 100644
--- a/integration-tests/devtools/src/test/java/io/quarkus/devtools/codestarts/jbang/QuarkusJBangCodestartGenerationTest.java
+++ b/integration-tests/devtools/src/test/java/io/quarkus/devtools/codestarts/jbang/QuarkusJBangCodestartGenerationTest.java
@@ -54,7 +54,7 @@ void generatePicocliProject(TestInfo testInfo) throws Throwable {
     }
 
     private QuarkusJBangCodestartCatalog getCatalog() throws IOException {
-        return QuarkusJBangCodestartCatalog.fromQuarkusPlatformDescriptor(getPlatformDescriptor());
+        return QuarkusJBangCodestartCatalog.fromResourceLoader(getCodestartsResourceLoader());
     }
 
 }
diff --git a/integration-tests/devtools/src/test/java/io/quarkus/devtools/codestarts/quarkus/QuarkusCodestartCatalogTest.java b/integration-tests/devtools/src/test/java/io/quarkus/devtools/codestarts/quarkus/QuarkusCodestartCatalogTest.java
index 8c99da0d94e470..288e8164225fb9 100644
--- a/integration-tests/devtools/src/test/java/io/quarkus/devtools/codestarts/quarkus/QuarkusCodestartCatalogTest.java
+++ b/integration-tests/devtools/src/test/java/io/quarkus/devtools/codestarts/quarkus/QuarkusCodestartCatalogTest.java
@@ -162,7 +162,7 @@ void prepareProjectTestCommandMode() throws IOException {
     }
 
     private QuarkusCodestartCatalog getCatalog() throws IOException {
-        return QuarkusCodestartCatalog.fromQuarkusPlatformDescriptor(getPlatformDescriptor());
+        return QuarkusCodestartCatalog.fromExtensionsCatalog(getExtensionsCatalog(), getCodestartsResourceLoader());
     }
 
 }
diff --git a/integration-tests/devtools/src/test/java/io/quarkus/devtools/codestarts/quarkus/QuarkusCodestartGenerationTest.java b/integration-tests/devtools/src/test/java/io/quarkus/devtools/codestarts/quarkus/QuarkusCodestartGenerationTest.java
index f7f32ea29ec1e1..14ab10be5d6bc7 100644
--- a/integration-tests/devtools/src/test/java/io/quarkus/devtools/codestarts/quarkus/QuarkusCodestartGenerationTest.java
+++ b/integration-tests/devtools/src/test/java/io/quarkus/devtools/codestarts/quarkus/QuarkusCodestartGenerationTest.java
@@ -5,6 +5,7 @@
 import static io.quarkus.devtools.testing.SnapshotTesting.checkContains;
 import static org.assertj.core.api.Assertions.assertThat;
 
+import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Collections;
@@ -710,8 +711,8 @@ private void checkGradleWithKotlinDsl(Path projectDir) {
                 .satisfies(checkContains("rootProject.name=\"test-codestart\""));
     }
 
-    private QuarkusCodestartCatalog getCatalog() throws Throwable {
-        return QuarkusCodestartCatalog.fromQuarkusPlatformDescriptor(getPlatformDescriptor());
+    private QuarkusCodestartCatalog getCatalog() throws IOException {
+        return QuarkusCodestartCatalog.fromExtensionsCatalog(getExtensionsCatalog(), getCodestartsResourceLoader());
     }
 
 }
diff --git a/integration-tests/devtools/src/test/java/io/quarkus/devtools/codestarts/quarkus/QuarkusCodestartRunIT.java b/integration-tests/devtools/src/test/java/io/quarkus/devtools/codestarts/quarkus/QuarkusCodestartRunIT.java
index 8257ceb70a1df0..2b3f2c83a5863f 100644
--- a/integration-tests/devtools/src/test/java/io/quarkus/devtools/codestarts/quarkus/QuarkusCodestartRunIT.java
+++ b/integration-tests/devtools/src/test/java/io/quarkus/devtools/codestarts/quarkus/QuarkusCodestartRunIT.java
@@ -1,7 +1,5 @@
 package io.quarkus.devtools.codestarts.quarkus;
 
-import static io.quarkus.devtools.codestarts.quarkus.QuarkusCodestartData.QuarkusDataKey.*;
-import static io.quarkus.platform.tools.ToolsUtils.readQuarkusProperties;
 import static java.util.Collections.singletonList;
 import static org.assertj.core.api.Assertions.assertThat;
 
@@ -12,7 +10,6 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Properties;
 import java.util.Set;
 import java.util.UUID;
 import java.util.stream.Collectors;
@@ -35,9 +32,6 @@
 import io.quarkus.devtools.project.BuildTool;
 import io.quarkus.devtools.testing.SnapshotTesting;
 import io.quarkus.devtools.testing.WrapperRunner;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
-import io.quarkus.platform.tools.ToolsConstants;
-import io.quarkus.platform.tools.ToolsUtils;
 
 @TestInstance(TestInstance.Lifecycle.PER_CLASS)
 class QuarkusCodestartRunIT extends PlatformAwareTestBase {
@@ -54,6 +48,10 @@ static void setUp() throws IOException {
         SnapshotTesting.deleteTestDirectory(testDirPath.toFile());
     }
 
+    private Map<String, Object> getTestInputData(final Map<String, Object> override) {
+        return getTestInputData(getExtensionsCatalog(), override);
+    }
+
     @Test
     public void testRunTogetherCodestartsJava() throws Exception {
         generateProjectRunTests("maven", "java", getRunTogetherExamples(), Collections.emptyMap());
@@ -172,41 +170,6 @@ private void generateProjectRunTests(String buildToolName, String language, List
         assertThat(result).isZero();
     }
 
-    private Map<String, Object> getTestInputData() {
-        return getTestInputData(null);
-    }
-
-    private Map<String, Object> getTestInputData(final Map<String, Object> override) {
-        return getTestInputData(getPlatformDescriptor(), override);
-    }
-
-    private static Map<String, Object> getTestInputData(final QuarkusPlatformDescriptor descriptor,
-            final Map<String, Object> override) {
-        final HashMap<String, Object> data = new HashMap<>();
-        final Properties quarkusProp = readQuarkusProperties(descriptor);
-        data.put(PROJECT_GROUP_ID.key(), "org.test");
-        data.put(PROJECT_ARTIFACT_ID.key(), "test-codestart");
-        data.put(PROJECT_VERSION.key(), "1.0.0-codestart");
-        data.put(BOM_GROUP_ID.key(), descriptor.getBomGroupId());
-        data.put(BOM_ARTIFACT_ID.key(), descriptor.getBomArtifactId());
-        data.put(BOM_VERSION.key(), descriptor.getBomVersion());
-        data.put(QUARKUS_VERSION.key(), descriptor.getQuarkusVersion());
-        data.put(QUARKUS_MAVEN_PLUGIN_GROUP_ID.key(), ToolsUtils.getMavenPluginGroupId(quarkusProp));
-        data.put(QUARKUS_MAVEN_PLUGIN_ARTIFACT_ID.key(), ToolsUtils.getMavenPluginArtifactId(quarkusProp));
-        data.put(QUARKUS_MAVEN_PLUGIN_VERSION.key(), ToolsUtils.getMavenPluginVersion(quarkusProp));
-        data.put(QUARKUS_GRADLE_PLUGIN_ID.key(), ToolsUtils.getMavenPluginGroupId(quarkusProp));
-        data.put(QUARKUS_GRADLE_PLUGIN_VERSION.key(), ToolsUtils.getGradlePluginVersion(quarkusProp));
-        data.put(JAVA_VERSION.key(), System.getProperty("java.specification.version"));
-        data.put(KOTLIN_VERSION.key(), quarkusProp.getProperty(ToolsConstants.PROP_KOTLIN_VERSION));
-        data.put(SCALA_VERSION.key(), quarkusProp.getProperty(ToolsConstants.PROP_SCALA_VERSION));
-        data.put(SCALA_MAVEN_PLUGIN_VERSION.key(), quarkusProp.getProperty(ToolsConstants.PROP_SCALA_PLUGIN_VERSION));
-        data.put(MAVEN_COMPILER_PLUGIN_VERSION.key(), quarkusProp.getProperty(ToolsConstants.PROP_COMPILER_PLUGIN_VERSION));
-        data.put(MAVEN_SUREFIRE_PLUGIN_VERSION.key(), quarkusProp.getProperty(ToolsConstants.PROP_SUREFIRE_PLUGIN_VERSION));
-        if (override != null)
-            data.putAll(override);
-        return data;
-    }
-
     private String genName(String buildtool, String language, List<String> codestarts) {
         String name = "project-" + buildtool + "-" + language;
         if (codestarts.isEmpty()) {
@@ -220,7 +183,7 @@ private String genName(String buildtool, String language, List<String> codestart
     }
 
     private QuarkusCodestartCatalog getCatalog() throws IOException {
-        return QuarkusCodestartCatalog.fromQuarkusPlatformDescriptor(getPlatformDescriptor());
+        return QuarkusCodestartCatalog.fromExtensionsCatalog(getExtensionsCatalog(), getCodestartsResourceLoader());
     }
 
     private List<String> getRunTogetherExamples() throws IOException {
diff --git a/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/AbstractAddExtensionsTest.java b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/AbstractAddExtensionsTest.java
index dede5b7c81e26b..61361d070d9ca6 100644
--- a/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/AbstractAddExtensionsTest.java
+++ b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/AbstractAddExtensionsTest.java
@@ -14,10 +14,10 @@
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
-import io.quarkus.dependencies.Extension;
 import io.quarkus.devtools.PlatformAwareTestBase;
 import io.quarkus.devtools.commands.data.QuarkusCommandException;
 import io.quarkus.devtools.commands.data.QuarkusCommandOutcome;
+import io.quarkus.registry.catalog.Extension;
 
 abstract class AbstractAddExtensionsTest<T> extends PlatformAwareTestBase {
 
@@ -62,7 +62,7 @@ void testRegexpMatches() throws Exception {
         final T project = readProject();
 
         getExtensionsWithArtifactContaining("smallrye")
-                .forEach(e -> hasDependency(project, e.getArtifactId()));
+                .forEach(e -> hasDependency(project, e.getArtifact().getArtifactId()));
     }
 
     @Test
@@ -160,7 +160,7 @@ void doNotAddExtensionWhenMultipleMatchWithMultipleKeywords() throws Exception {
         final T project = readProject();
         doesNotHaveDependency(project, "quarkus-agroal");
         getExtensionsWithArtifactContaining("jdbc")
-                .forEach(e -> doesNotHaveDependency(project, e.getArtifactId()));
+                .forEach(e -> doesNotHaveDependency(project, e.getArtifact().getArtifactId()));
     }
 
     @Test
@@ -172,7 +172,7 @@ void doesNotAddExtensionsWhenMultipleMatchWithOneKeyword() throws Exception {
         final T project = readProject();
 
         getExtensionsWithArtifactContaining("jdbc")
-                .forEach(e -> doesNotHaveDependency(project, e.getArtifactId()));
+                .forEach(e -> doesNotHaveDependency(project, e.getArtifact().getArtifactId()));
     }
 
     @Test
@@ -206,12 +206,12 @@ void testVertxWithDot() throws Exception {
 
         final T project = readProject();
         getExtensionsWithArtifactContaining("vertx")
-                .forEach(e -> hasDependency(project, e.getArtifactId()));
+                .forEach(e -> hasDependency(project, e.getArtifact().getArtifactId()));
     }
 
     private Stream<Extension> getExtensionsWithArtifactContaining(String contains) {
-        return getPlatformDescriptor().getExtensions().stream()
-                .filter(e -> e.getArtifactId().contains(contains) && !e.isUnlisted());
+        return getExtensionsCatalog().getExtensions().stream()
+                .filter(e -> e.getArtifact().getArtifactId().contains(contains) && !e.isUnlisted());
     }
 
     private void hasDependency(T project, String artifactId) {
diff --git a/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/AddGradleExtensionsTest.java b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/AddGradleExtensionsTest.java
index 0001b48d785c86..a9ba75e6c1043b 100644
--- a/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/AddGradleExtensionsTest.java
+++ b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/AddGradleExtensionsTest.java
@@ -12,19 +12,20 @@
 import io.quarkus.bootstrap.model.AppArtifactCoords;
 import io.quarkus.devtools.commands.data.QuarkusCommandException;
 import io.quarkus.devtools.commands.data.QuarkusCommandOutcome;
-import io.quarkus.devtools.project.BuildTool;
 import io.quarkus.devtools.project.QuarkusProject;
+import io.quarkus.devtools.project.QuarkusProjectHelper;
 import io.quarkus.devtools.project.buildfile.AbstractGroovyGradleBuildFile;
 import io.quarkus.devtools.testing.SnapshotTesting;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
+import io.quarkus.registry.RegistryResolutionException;
+import io.quarkus.registry.catalog.ExtensionCatalog;
 
 class AddGradleExtensionsTest extends AbstractAddExtensionsTest<List<String>> {
 
     @Override
     protected List<String> createProject() throws IOException, QuarkusCommandException {
         SnapshotTesting.deleteTestDirectory(getProjectPath().toFile());
-        new CreateProject(getProjectPath(), getPlatformDescriptor())
-                .buildTool(BuildTool.GRADLE)
+        final QuarkusProject project = getQuarkusProject();
+        new CreateProject(project)
                 .groupId("org.acme")
                 .artifactId("add-gradle-extension-test")
                 .version("0.0.1-SNAPSHOT")
@@ -57,16 +58,19 @@ private static String getBuildFileDependencyString(final String groupId, final S
         return "    implementation '" + groupId + ":" + artifactId + versionPart + "'";
     }
 
-    private QuarkusProject getQuarkusProject() {
-        final Path projectPath = getProjectPath();
-        final QuarkusPlatformDescriptor platformDescriptor = getPlatformDescriptor();
-        return QuarkusProject.of(projectPath, platformDescriptor, new TestingGradleBuildFile(projectPath, platformDescriptor));
+    protected QuarkusProject getQuarkusProject() throws QuarkusCommandException {
+        try {
+            return QuarkusProjectHelper.getProject(getProjectPath(),
+                    new TestingGradleBuildFile(getProjectPath(), getExtensionsCatalog()));
+        } catch (RegistryResolutionException e) {
+            throw new QuarkusCommandException("Failed to initialize Quarkus project", e);
+        }
     }
 
     static class TestingGradleBuildFile extends AbstractGroovyGradleBuildFile {
 
-        public TestingGradleBuildFile(Path projectDirPath, QuarkusPlatformDescriptor platformDescriptor) {
-            super(projectDirPath, platformDescriptor);
+        public TestingGradleBuildFile(Path projectDirPath, ExtensionCatalog catalog) {
+            super(projectDirPath, catalog);
         }
 
         @Override
diff --git a/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/AddMavenExtensionsTest.java b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/AddMavenExtensionsTest.java
index df6b69e6cd4c3f..c1957fd01e9494 100644
--- a/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/AddMavenExtensionsTest.java
+++ b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/AddMavenExtensionsTest.java
@@ -12,6 +12,7 @@
 import io.quarkus.devtools.commands.data.QuarkusCommandOutcome;
 import io.quarkus.devtools.project.BuildTool;
 import io.quarkus.devtools.project.QuarkusProject;
+import io.quarkus.devtools.project.QuarkusProjectHelper;
 import io.quarkus.devtools.testing.SnapshotTesting;
 import io.quarkus.maven.utilities.MojoUtils;
 
@@ -21,8 +22,8 @@ class AddMavenExtensionsTest extends AbstractAddExtensionsTest<Model> {
     protected Model createProject() throws IOException, QuarkusCommandException {
         final File pom = getProjectPath().resolve("pom.xml").toFile();
         SnapshotTesting.deleteTestDirectory(getProjectPath().toFile());
-        new CreateProject(getProjectPath(), getPlatformDescriptor())
-                .buildTool(BuildTool.MAVEN)
+        final QuarkusProject project = getQuarkusProject();
+        new CreateProject(project)
                 .groupId("org.acme")
                 .artifactId("add-maven-extension-test")
                 .version("0.0.1-SNAPSHOT")
@@ -51,6 +52,6 @@ protected long countDependencyOccurrences(final Model project, final String grou
     }
 
     private QuarkusProject getQuarkusProject() {
-        return QuarkusProject.maven(getProjectPath(), getPlatformDescriptor());
+        return QuarkusProjectHelper.getProject(getProjectPath(), BuildTool.MAVEN);
     }
 }
diff --git a/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/CreateJBangProjectTest.java b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/CreateJBangProjectTest.java
index 95cdd34a176f74..3fc2eb1602c0b6 100644
--- a/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/CreateJBangProjectTest.java
+++ b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/CreateJBangProjectTest.java
@@ -14,6 +14,8 @@
 import io.quarkus.devtools.PlatformAwareTestBase;
 import io.quarkus.devtools.commands.data.QuarkusCommandException;
 import io.quarkus.devtools.commands.data.QuarkusCommandOutcome;
+import io.quarkus.devtools.project.BuildTool;
+import io.quarkus.devtools.project.QuarkusProjectHelper;
 import io.quarkus.devtools.testing.SnapshotTesting;
 
 public class CreateJBangProjectTest extends PlatformAwareTestBase {
@@ -71,13 +73,12 @@ public void createRESTEasyWithExtensions() throws Exception {
     }
 
     private CreateJBangProject newCreateJBangProject(Path dir) {
-        return new CreateJBangProject(dir, getPlatformDescriptor());
+        return new CreateJBangProject(QuarkusProjectHelper.getProject(dir, BuildTool.MAVEN));
     }
 
     private void assertCreateJBangProject(CreateJBangProject createJBangProjectProject)
             throws QuarkusCommandException {
-        final QuarkusCommandOutcome result = createJBangProjectProject
-                .execute();
+        final QuarkusCommandOutcome result = createJBangProjectProject.execute();
         assertTrue(result.isSuccess());
     }
 }
diff --git a/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/CreateProjectPlatformMetadataTest.java b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/CreateProjectPlatformMetadataTest.java
index 54bd71dc2e66f8..bff47f1e244c9c 100644
--- a/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/CreateProjectPlatformMetadataTest.java
+++ b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/CreateProjectPlatformMetadataTest.java
@@ -14,8 +14,6 @@
 
 import org.assertj.core.util.Files;
 import org.junit.jupiter.api.Test;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.ValueSource;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 
@@ -23,19 +21,20 @@
 import io.quarkus.devtools.commands.data.QuarkusCommandException;
 import io.quarkus.devtools.commands.data.QuarkusCommandOutcome;
 import io.quarkus.devtools.project.BuildTool;
+import io.quarkus.devtools.project.QuarkusProject;
+import io.quarkus.devtools.project.QuarkusProjectHelper;
 import io.quarkus.devtools.testing.SnapshotTesting;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
+import io.quarkus.registry.catalog.ExtensionCatalog;
 
 public class CreateProjectPlatformMetadataTest extends PlatformAwareTestBase {
 
     private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
 
-    @ParameterizedTest
-    @ValueSource(booleans = { false, true })
-    public void create(boolean legacyCodegen) throws Exception {
+    @Test
+    public void create() throws Exception {
         final File file = new File("target/meta-rest");
         SnapshotTesting.deleteTestDirectory(file);
-        createProject(BuildTool.MAVEN, file, "io.quarkus", "basic-rest", "1.0.0-SNAPSHOT", legacyCodegen);
+        createProject(BuildTool.MAVEN, file, "io.quarkus", "basic-rest", "1.0.0-SNAPSHOT");
         assertThat(file.toPath().resolve("pom.xml"))
                 .exists()
                 .satisfies(checkContains("<id>redhat</id>"))
@@ -46,12 +45,11 @@ public void create(boolean legacyCodegen) throws Exception {
                 .satisfies(checkContains("<pluginRepositories>"));
     }
 
-    @ParameterizedTest
-    @ValueSource(booleans = { false, true })
-    public void createGradle(boolean legacyCodegen) throws Exception {
+    @Test
+    public void createGradle() throws Exception {
         final File file = new File("target/meta-rest-gradle");
         SnapshotTesting.deleteTestDirectory(file);
-        createProject(BuildTool.GRADLE, file, "io.quarkus", "basic-rest", "1.0.0-SNAPSHOT", legacyCodegen);
+        createProject(BuildTool.GRADLE, file, "io.quarkus", "basic-rest", "1.0.0-SNAPSHOT");
         assertThat(file.toPath().resolve("build.gradle"))
                 .exists()
                 .satisfies(checkContains("maven { url \"https://maven.repository.redhat.com\" }"));
@@ -61,7 +59,7 @@ public void createGradle(boolean legacyCodegen) throws Exception {
     public void createGradleKotlin() throws Exception {
         final File file = new File("target/meta-rest-gradle-kts");
         SnapshotTesting.deleteTestDirectory(file);
-        createProject(BuildTool.GRADLE_KOTLIN_DSL, file, "io.quarkus", "basic-rest", "1.0.0-SNAPSHOT", false);
+        createProject(BuildTool.GRADLE_KOTLIN_DSL, file, "io.quarkus", "basic-rest", "1.0.0-SNAPSHOT");
         assertThat(file.toPath().resolve("build.gradle.kts"))
                 .exists()
                 .satisfies(checkContains("maven { url = uri(\"https://maven.repository.redhat.com\") }"));
@@ -76,20 +74,17 @@ private Map<String, Object> getMetadata() throws java.io.IOException {
                 Map.class);
     }
 
-    private void createProject(BuildTool buildTool, File file, String groupId, String artifactId, String version,
-            boolean legacyCodegen)
+    private void createProject(BuildTool buildTool, File file, String groupId, String artifactId, String version)
             throws QuarkusCommandException, IOException {
-        final QuarkusPlatformDescriptor platformDescriptor = getPlatformDescriptor();
-        final QuarkusPlatformDescriptor spy = spy(platformDescriptor);
+        final ExtensionCatalog platformDescriptor = getExtensionsCatalog();
+        final ExtensionCatalog spy = spy(platformDescriptor);
         when(spy.getMetadata()).thenReturn(getMetadata());
-        final QuarkusCommandOutcome result = new CreateProject(file.toPath(), spy)
-                .buildTool(buildTool)
+        QuarkusProject project = QuarkusProjectHelper.getProject(file.toPath(), spy, buildTool);
+        final QuarkusCommandOutcome result = new CreateProject(project)
                 .groupId(groupId)
                 .artifactId(artifactId)
-                .legacyCodegen(legacyCodegen)
                 .version(version)
-                .quarkusMavenPluginVersion("2.3.5")
-                .quarkusGradlePluginVersion("2.3.5-gradle")
+                .quarkusPluginVersion(buildTool == BuildTool.MAVEN ? "2.3.5" : "2.3.5-gradle")
                 .execute();
         assertTrue(result.isSuccess());
     }
diff --git a/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/CreateProjectTest.java b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/CreateProjectTest.java
index e6ecd54a693d6b..def60c953faa9e 100644
--- a/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/CreateProjectTest.java
+++ b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/CreateProjectTest.java
@@ -4,7 +4,6 @@
 import static io.quarkus.devtools.testing.SnapshotTesting.checkMatches;
 import static io.quarkus.platform.tools.ToolsConstants.PROP_COMPILER_PLUGIN_VERSION;
 import static io.quarkus.platform.tools.ToolsConstants.PROP_SUREFIRE_PLUGIN_VERSION;
-import static io.quarkus.platform.tools.ToolsUtils.readQuarkusProperties;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -34,6 +33,8 @@
 import io.quarkus.devtools.commands.data.QuarkusCommandException;
 import io.quarkus.devtools.commands.data.QuarkusCommandOutcome;
 import io.quarkus.devtools.project.BuildTool;
+import io.quarkus.devtools.project.QuarkusProject;
+import io.quarkus.devtools.project.QuarkusProjectHelper;
 import io.quarkus.devtools.project.codegen.writer.FileProjectWriter;
 import io.quarkus.devtools.testing.SnapshotTesting;
 import io.quarkus.maven.utilities.MojoUtils;
@@ -51,7 +52,7 @@ public void createRESTEasy() throws Exception {
                 .className("org.acme.getting.started.GreetingResource")
                 .resourcePath("/foo")
                 .extensions(Collections.singleton("resteasy")));
-        final Properties quarkusProp = readQuarkusProperties(getPlatformDescriptor());
+        final Properties quarkusProp = getQuarkusProperties();
         assertThat(projectDir.resolve(".gitignore"))
                 .exists()
                 .satisfies(checkMatches("(?s).*target/\\R.*"));
@@ -142,8 +143,7 @@ public void createGradle() throws Exception {
         final File file = new File("target/create-resteasy-gradle");
         final Path projectDir = file.toPath();
         SnapshotTesting.deleteTestDirectory(file);
-        assertCreateProject(newCreateProject(projectDir)
-                .buildTool(BuildTool.GRADLE)
+        assertCreateProject(newCreateProject(projectDir, BuildTool.GRADLE)
                 .groupId("io.foo")
                 .packageName("my.project")
                 .artifactId("resteasy-app")
@@ -175,7 +175,11 @@ public void createGradle() throws Exception {
     }
 
     private CreateProject newCreateProject(Path dir) {
-        return new CreateProject(dir, getPlatformDescriptor());
+        return newCreateProject(dir, BuildTool.MAVEN);
+    }
+
+    private CreateProject newCreateProject(Path dir, BuildTool buildTool) {
+        return new CreateProject(QuarkusProjectHelper.getProject(dir, buildTool));
     }
 
     @Test
@@ -192,7 +196,8 @@ public void createOnTopOfExisting() throws Exception {
         final File pom = new File(testDir, "pom.xml");
         MojoUtils.write(model, pom);
         assertThatExceptionOfType(QuarkusCommandException.class).isThrownBy(() -> {
-            new CreateProject(testDir.toPath(), getPlatformDescriptor())
+            final QuarkusProject project = QuarkusProjectHelper.getProject(testDir.toPath(), BuildTool.MAVEN);
+            new CreateProject(project)
                     .groupId("something.is")
                     .artifactId("wrong")
                     .version("1.0.0-SNAPSHOT")
@@ -211,7 +216,8 @@ void createMultipleTimes() throws InterruptedException {
         List<Callable<Void>> collect = IntStream.range(0, 15).boxed().map(i -> (Callable<Void>) () -> {
             File tempDir = Files.createTempDirectory("test").toFile();
             FileProjectWriter write = new FileProjectWriter(tempDir);
-            final QuarkusCommandOutcome result = new CreateProject(tempDir.toPath(), getPlatformDescriptor())
+            final QuarkusProject project = QuarkusProjectHelper.getProject(tempDir.toPath(), BuildTool.MAVEN);
+            final QuarkusCommandOutcome result = new CreateProject(project)
                     .groupId("org.acme")
                     .artifactId("acme")
                     .version("1.0.0-SNAPSHOT")
@@ -230,8 +236,7 @@ void createMultipleTimes() throws InterruptedException {
     private void assertCreateProject(CreateProject createProject)
             throws QuarkusCommandException {
         final QuarkusCommandOutcome result = createProject
-                .quarkusMavenPluginVersion("2.3.5")
-                .quarkusGradlePluginVersion("2.3.5-gradle")
+                .quarkusPluginVersion("2.3.5")
                 .execute();
         assertTrue(result.isSuccess());
     }
diff --git a/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/ListExtensionsTest.java b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/ListExtensionsTest.java
index 37fb076e927c44..1b525211af0caf 100644
--- a/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/ListExtensionsTest.java
+++ b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/ListExtensionsTest.java
@@ -26,7 +26,9 @@
 import io.quarkus.devtools.PlatformAwareTestBase;
 import io.quarkus.devtools.commands.data.QuarkusCommandException;
 import io.quarkus.devtools.messagewriter.MessageWriter;
+import io.quarkus.devtools.project.BuildTool;
 import io.quarkus.devtools.project.QuarkusProject;
+import io.quarkus.devtools.project.QuarkusProjectHelper;
 import io.quarkus.devtools.testing.SnapshotTesting;
 import io.quarkus.maven.utilities.MojoUtils;
 import io.quarkus.maven.utilities.QuarkusDependencyPredicate;
@@ -94,7 +96,6 @@ public void listWithoutBom() throws Exception {
             boolean resteasy = false;
             boolean panache = false;
             boolean hibernateValidator = false;
-            boolean checkGuideInLineAfter = false;
             for (String line : output.split("\r?\n")) {
                 if (line.contains("agroal")) {
                     assertTrue(line.startsWith("default"), "Agroal should list as being default: " + line);
@@ -102,26 +103,23 @@ public void listWithoutBom() throws Exception {
                 } else if (line.contains("quarkus-resteasy ")) {
                     assertTrue(line.startsWith("custom*"), "RESTEasy should list as being custom*: " + line);
                     assertTrue(
-                            line.endsWith(
+                            line.contains(
                                     String.format("%-15s", getMavenPluginVersion())),
                             "RESTEasy should list as being custom*: " + line);
                     resteasy = true;
-                    checkGuideInLineAfter = true;
+                    assertTrue(
+                            line.endsWith(
+                                    String.format("%s", "https://quarkus.io/guides/rest-json")),
+                            "RESTEasy should list as having an guide: " + line);
                 } else if (line.contains("quarkus-hibernate-orm-panache ")) {
                     assertTrue(line.startsWith("custom"), "Panache should list as being custom: " + line);
                     assertTrue(
-                            line.endsWith(String.format("%-25s", getMavenPluginVersion())),
+                            line.contains(String.format("%-25s", getMavenPluginVersion())),
                             "Panache should list as being custom*: " + line);
                     panache = true;
                 } else if (line.contains("hibernate-validator")) {
                     assertTrue(line.startsWith("   "), "Hibernate Validator should not list as anything: " + line);
                     hibernateValidator = true;
-                } else if (checkGuideInLineAfter) {
-                    checkGuideInLineAfter = false;
-                    assertTrue(
-                            line.endsWith(
-                                    String.format("%s", "https://quarkus.io/guides/rest-json")),
-                            "RESTEasy should list as having an guide: " + line);
                 }
             }
 
@@ -156,7 +154,6 @@ public void searchRest() throws Exception {
         final QuarkusProject quarkusProject = createNewProject(new File("target/list-extensions-test", "pom.xml"));
         addExtensions(quarkusProject, "commons-io:commons-io:2.5", "Agroal");
 
-        final PrintStream out = System.out;
         final ByteArrayOutputStream baos = new ByteArrayOutputStream();
         try (final PrintStream printStream = new PrintStream(baos, false, "UTF-8")) {
             new ListExtensions(quarkusProject, MessageWriter.info(printStream))
@@ -172,10 +169,10 @@ public void searchRest() throws Exception {
     @Test
     void testListExtensionsWithoutAPomFile() throws Exception {
         final Path tempDirectory = Files.createTempDirectory("proj");
-        final QuarkusProject project = QuarkusProject.maven(tempDirectory, getPlatformDescriptor());
+        final QuarkusProject project = QuarkusProjectHelper.getProject(tempDirectory, BuildTool.MAVEN);
         final Map<AppArtifactKey, AppArtifactCoords> installed = readByKey(project);
         assertTrue(installed.isEmpty());
-        assertFalse(project.getPlatformDescriptor().getExtensions().isEmpty());
+        assertFalse(project.getExtensionsCatalog().getExtensions().isEmpty());
 
         final ByteArrayOutputStream baos = new ByteArrayOutputStream();
         try (final PrintStream printStream = new PrintStream(baos, false, "UTF-8")) {
@@ -194,12 +191,13 @@ private void addExtensions(QuarkusProject quarkusProject, String... extensions)
     private QuarkusProject createNewProject(final File pom) throws IOException, QuarkusCommandException {
         SnapshotTesting.deleteTestDirectory(pom.getParentFile());
         final Path projectDirPath = pom.getParentFile().toPath();
-        new CreateProject(projectDirPath, getPlatformDescriptor())
+        final QuarkusProject project = QuarkusProjectHelper.getProject(projectDirPath, BuildTool.MAVEN);
+        new CreateProject(project)
                 .groupId("org.acme")
                 .artifactId("add-extension-test")
                 .version("0.0.1-SNAPSHOT")
                 .execute();
-        return QuarkusProject.maven(projectDirPath, getPlatformDescriptor());
+        return project;
     }
 
     private static Map<AppArtifactKey, AppArtifactCoords> readByKey(QuarkusProject project) throws IOException {
diff --git a/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/RemoveGradleExtensionsTest.java b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/RemoveGradleExtensionsTest.java
index 9d280334420556..84f66d11ad1902 100644
--- a/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/RemoveGradleExtensionsTest.java
+++ b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/RemoveGradleExtensionsTest.java
@@ -2,18 +2,18 @@
 
 import java.io.IOException;
 import java.nio.file.Files;
-import java.nio.file.Path;
 import java.util.HashSet;
 import java.util.List;
 
 import org.junit.jupiter.api.Disabled;
 
+import io.quarkus.devtools.commands.AddGradleExtensionsTest.TestingGradleBuildFile;
 import io.quarkus.devtools.commands.data.QuarkusCommandException;
 import io.quarkus.devtools.commands.data.QuarkusCommandOutcome;
-import io.quarkus.devtools.project.BuildTool;
 import io.quarkus.devtools.project.QuarkusProject;
+import io.quarkus.devtools.project.QuarkusProjectHelper;
 import io.quarkus.devtools.testing.SnapshotTesting;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
+import io.quarkus.registry.RegistryResolutionException;
 
 class RemoveGradleExtensionsTest extends AbstractRemoveExtensionsTest<List<String>> {
 
@@ -25,8 +25,7 @@ void addExtensionTwiceInTwoBatches() throws IOException {
     @Override
     protected List<String> createProject() throws IOException, QuarkusCommandException {
         SnapshotTesting.deleteTestDirectory(getProjectPath().toFile());
-        new CreateProject(getProjectPath(), getPlatformDescriptor())
-                .buildTool(BuildTool.GRADLE)
+        new CreateProject(getQuarkusProject())
                 .groupId("org.acme")
                 .artifactId("add-gradle-extension-test")
                 .version("0.0.1-SNAPSHOT")
@@ -62,11 +61,13 @@ protected long countDependencyOccurrences(final List<String> buildFile, final St
                 .count();
     }
 
-    private QuarkusProject getQuarkusProject() {
-        final Path projectPath = getProjectPath();
-        final QuarkusPlatformDescriptor platformDescriptor = getPlatformDescriptor();
-        return QuarkusProject.of(projectPath, platformDescriptor,
-                new AddGradleExtensionsTest.TestingGradleBuildFile(projectPath, platformDescriptor));
+    protected QuarkusProject getQuarkusProject() throws QuarkusCommandException {
+        try {
+            return QuarkusProjectHelper.getProject(getProjectPath(),
+                    new TestingGradleBuildFile(getProjectPath(), getExtensionsCatalog()));
+        } catch (RegistryResolutionException e) {
+            throw new QuarkusCommandException("Failed to initialize Quarkus project", e);
+        }
     }
 
     private static String getBuildFileDependencyString(final String groupId, final String artifactId, final String version) {
diff --git a/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/RemoveMavenExtensionsTest.java b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/RemoveMavenExtensionsTest.java
index 3b48d948a3ad4c..6fa2ecf1864c5f 100644
--- a/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/RemoveMavenExtensionsTest.java
+++ b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/RemoveMavenExtensionsTest.java
@@ -10,7 +10,9 @@
 
 import io.quarkus.devtools.commands.data.QuarkusCommandException;
 import io.quarkus.devtools.commands.data.QuarkusCommandOutcome;
+import io.quarkus.devtools.project.BuildTool;
 import io.quarkus.devtools.project.QuarkusProject;
+import io.quarkus.devtools.project.QuarkusProjectHelper;
 import io.quarkus.devtools.testing.SnapshotTesting;
 import io.quarkus.maven.utilities.MojoUtils;
 
@@ -20,7 +22,8 @@ class RemoveMavenExtensionsTest extends AbstractRemoveExtensionsTest<Model> {
     protected Model createProject() throws IOException, QuarkusCommandException {
         final File pom = getProjectPath().resolve("pom.xml").toFile();
         SnapshotTesting.deleteTestDirectory(getProjectPath().toFile());
-        new CreateProject(getProjectPath(), getPlatformDescriptor())
+        final QuarkusProject project = getQuarkusProject();
+        new CreateProject(project)
                 .groupId("org.acme")
                 .artifactId("add-maven-extension-test")
                 .version("0.0.1-SNAPSHOT")
@@ -56,6 +59,6 @@ protected long countDependencyOccurrences(final Model project, final String grou
     }
 
     private QuarkusProject getQuarkusProject() {
-        return QuarkusProject.maven(getProjectPath(), getPlatformDescriptor());
+        return QuarkusProjectHelper.getProject(getProjectPath(), BuildTool.MAVEN);
     }
 }
diff --git a/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/handlers/QuarkusCommandHandlersTest.java b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/handlers/QuarkusCommandHandlersTest.java
index 2b6f9b51bcabe7..ba043654d46495 100644
--- a/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/handlers/QuarkusCommandHandlersTest.java
+++ b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/handlers/QuarkusCommandHandlersTest.java
@@ -3,28 +3,34 @@
 import static io.quarkus.devtools.commands.handlers.QuarkusCommandHandlers.select;
 import static java.util.Arrays.asList;
 
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
-import io.quarkus.dependencies.Extension;
 import io.quarkus.devtools.commands.data.SelectionResult;
+import io.quarkus.maven.ArtifactCoords;
+import io.quarkus.registry.catalog.Extension;
+import io.quarkus.registry.catalog.json.JsonExtension;
 
 class QuarkusCommandHandlersTest {
 
     @Test
     void testMultiMatchByLabels() {
-        Extension e1 = new Extension("org.acme", "e1", "1.0")
-                .setName("some extension 1")
-                .setKeywords(new String[] { "foo", "bar" });
-        Extension e2 = new Extension("org.acme", "e2", "1.0")
-                .setName("some extension 2")
-                .setKeywords(new String[] { "foo", "bar", "baz" });
-        Extension e3 = new Extension("org.acme", "e3", "1.0")
-                .setName("unrelated")
-                .setKeywords(new String[] { "bar" });
+        JsonExtension e1 = new JsonExtension();
+        e1.setArtifact(new ArtifactCoords("org.acme", "e1", "1.0"));
+        e1.setName("some extension 1");
+        e1.setKeywords(Arrays.asList("foo", "bar"));
+        JsonExtension e2 = new JsonExtension();
+        e2.setArtifact(new ArtifactCoords("org.acme", "e2", "1.0"));
+        e2.setName("some extension 2");
+        e2.setKeywords(Arrays.asList("foo", "bar", "baz"));
+        JsonExtension e3 = new JsonExtension();
+        e3.setArtifact(new ArtifactCoords("org.acme", "e3", "1.0"));
+        e3.setName("unrelated");
+        e3.setKeywords(Arrays.asList("bar"));
 
         List<Extension> extensions = asList(e1, e2, e3);
         Collections.shuffle(extensions);
@@ -39,12 +45,14 @@ void testMultiMatchByLabels() {
 
     @Test
     void testThatSingleLabelMatchIsNotAMatch() {
-        Extension e1 = new Extension("org.acme", "e1", "1.0")
-                .setName("e1")
-                .setKeywords(new String[] { "foo", "bar" });
-        Extension e2 = new Extension("org.acme", "e2", "1.0")
-                .setName("e2")
-                .setKeywords(new String[] { "bar", "baz" });
+        JsonExtension e1 = new JsonExtension();
+        e1.setArtifact(new ArtifactCoords("org.acme", "e1", "1.0"));
+        e1.setName("e1");
+        e1.setKeywords(Arrays.asList("foo", "bar"));
+        JsonExtension e2 = new JsonExtension();
+        e2.setArtifact(new ArtifactCoords("org.acme", "e2", "1.0"));
+        e2.setName("e2");
+        e2.setKeywords(Arrays.asList("bar", "baz"));
 
         List<Extension> extensions = asList(e1, e2);
         Collections.shuffle(extensions);
@@ -55,20 +63,23 @@ void testThatSingleLabelMatchIsNotAMatch() {
 
     @Test
     void testMultiMatchByArtifactIdsAndNames() {
-        Extension e1 = new Extension("org.acme", "e1", "1.0")
-                .setName("foo")
-                .setKeywords(new String[] { "foo", "bar" });
-        Extension e2 = new Extension("org.acme", "quarkus-foo", "1.0")
-                .setName("some foo bar")
-                .setKeywords(new String[] { "foo", "bar", "baz" });
-        Extension e3 = new Extension("org.acme", "e3", "1.0")
-                .setName("unrelated")
-                .setKeywords(new String[] { "foo" });
+        JsonExtension e1 = new JsonExtension();
+        e1.setArtifact(new ArtifactCoords("org.acme", "e1", "1.0"));
+        e1.setName("foo");
+        e1.setKeywords(asList("foo", "bar"));
+        JsonExtension e2 = new JsonExtension();
+        e2.setArtifact(new ArtifactCoords("org.acme", "quarkus-foo", "1.0"));
+        e2.setName("some foo bar");
+        e2.setKeywords(asList("foo", "bar", "baz"));
+        JsonExtension e3 = new JsonExtension();
+        e3.setArtifact(new ArtifactCoords("org.acme", "e3", "1.0"));
+        e3.setName("unrelated");
+        e3.setKeywords(asList("foo"));
 
         List<Extension> extensions = asList(e1, e2, e3);
         Collections.shuffle(extensions);
         SelectionResult matches = select("foo", extensions, false);
-        Assertions.assertFalse(matches.matches());
+        Assertions.assertFalse(matches.matches(), " " + matches.getExtensions().size());
         Assertions.assertEquals(2, matches.getExtensions().size());
 
         matches = select("foo", extensions, true);
@@ -79,16 +90,19 @@ void testMultiMatchByArtifactIdsAndNames() {
 
     @Test
     void testShortNameSelection() {
-        Extension e1 = new Extension("org.acme", "some-complex-seo-unaware-artifactid", "1.0")
-                .setName("some complex seo unaware name")
-                .setShortName("foo")
-                .setKeywords(new String[] { "foo", "bar" });
-        Extension e2 = new Extension("org.acme", "some-foo-bar", "1.0")
-                .setName("some foo bar")
-                .setKeywords(new String[] { "foo", "bar", "baz" });
-        Extension e3 = new Extension("org.acme", "unrelated", "1.0")
-                .setName("unrelated")
-                .setKeywords(new String[] { "foo" });
+        JsonExtension e1 = new JsonExtension();
+        e1.setArtifact(new ArtifactCoords("org.acme", "some-complex-seo-unaware-artifactid", "1.0"));
+        e1.setName("some complex seo unaware name");
+        e1.setShortName("foo");
+        e1.setKeywords(asList("foo", "bar"));
+        JsonExtension e2 = new JsonExtension();
+        e2.setArtifact(new ArtifactCoords("org.acme", "some-foo-bar", "1.0"));
+        e2.setName("some foo bar");
+        e2.setKeywords(asList("foo", "bar", "baz"));
+        JsonExtension e3 = new JsonExtension();
+        e3.setArtifact(new ArtifactCoords("org.acme", "unrelated", "1.0"));
+        e3.setName("unrelated");
+        e3.setKeywords(asList("foo"));
 
         List<Extension> extensions = asList(e1, e2, e3);
         Collections.shuffle(extensions);
@@ -97,43 +111,52 @@ void testShortNameSelection() {
         Assertions.assertEquals(1, matches.getExtensions().size());
         Assertions.assertTrue(matches.iterator().hasNext());
         Assertions
-                .assertTrue(matches.iterator().next().getArtifactId().equalsIgnoreCase("some-complex-seo-unaware-artifactid"));
+                .assertTrue(matches.iterator().next().getArtifact().getArtifactId()
+                        .equalsIgnoreCase("some-complex-seo-unaware-artifactid"));
     }
 
     @Test
     void testArtifactIdSelectionWithQuarkusPrefix() {
-        Extension e1 = new Extension("org.acme", "quarkus-foo", "1.0")
-                .setName("some complex seo unaware name")
-                .setShortName("foo")
-                .setKeywords(new String[] { "foo", "bar" });
-        Extension e2 = new Extension("org.acme", "quarkus-foo-bar", "1.0")
-                .setName("some foo bar")
-                .setKeywords(new String[] { "foo", "bar", "baz" });
-        Extension e3 = new Extension("org.acme", "quarkus-unrelated", "1.0")
-                .setName("unrelated")
-                .setKeywords(new String[] { "foo" });
+        JsonExtension e1 = new JsonExtension();
+        e1.setArtifact(new ArtifactCoords("org.acme", "quarkus-foo", "1.0"));
+        e1.setName("some complex seo unaware name");
+        e1.setShortName("foo");
+        e1.setKeywords(asList("foo", "bar"));
+        JsonExtension e2 = new JsonExtension();
+        e2.setArtifact(new ArtifactCoords("org.acme", "quarkus-foo-bar", "1.0"));
+        e2.setName("some foo bar");
+        e2.setKeywords(asList("foo", "bar", "baz"));
+        JsonExtension e3 = new JsonExtension();
+        e3.setArtifact(new ArtifactCoords("org.acme", "quarkus-unrelated", "1.0"));
+        e3.setName("unrelated");
+        e3.setKeywords(asList("foo"));
 
         List<Extension> extensions = asList(e1, e2, e3);
         Collections.shuffle(extensions);
         SelectionResult matches = select("foo", extensions, false);
         Assertions.assertEquals(1, matches.getExtensions().size());
         Assertions.assertTrue(matches.iterator().hasNext());
-        Assertions.assertTrue(matches.iterator().next().getArtifactId().equalsIgnoreCase("quarkus-foo"));
+        Assertions.assertTrue(matches.iterator().next().getArtifact().getArtifactId().equalsIgnoreCase("quarkus-foo"));
     }
 
     @Test
     void testListedVsUnlisted() {
-        Extension e1 = new Extension("org.acme", "quarkus-foo-unlisted", "1.0")
-                .setName("some complex seo unaware name")
-                .setShortName("foo")
-                .setKeywords(new String[] { "foo", "bar" }).addMetadata("unlisted", "true");
-
-        Extension e2 = new Extension("org.acme", "quarkus-foo-bar", "1.0")
-                .setName("some foo bar")
-                .setKeywords(new String[] { "foo", "bar", "baz" }).addMetadata("unlisted", "false");
-        Extension e3 = new Extension("org.acme", "quarkus-foo-baz", "1.0")
-                .setName("unrelated")
-                .setKeywords(new String[] { "foo" });
+        JsonExtension e1 = new JsonExtension();
+        e1.setArtifact(new ArtifactCoords("org.acme", "quarkus-foo-unlisted", "1.0"));
+        e1.setName("some complex seo unaware name");
+        e1.setShortName("foo");
+        e1.setKeywords(asList("foo", "bar"));
+        e1.addMetadata("unlisted", "true");
+
+        JsonExtension e2 = new JsonExtension();
+        e2.setArtifact(new ArtifactCoords("org.acme", "quarkus-foo-bar", "1.0"));
+        e2.setName("some foo bar");
+        e2.setKeywords(asList("foo", "bar", "baz"));
+        e2.addMetadata("unlisted", "false");
+        JsonExtension e3 = new JsonExtension();
+        e3.setArtifact(new ArtifactCoords("org.acme", "quarkus-foo-baz", "1.0"));
+        e3.setName("unrelated");
+        e3.setKeywords(asList("foo"));
 
         List<Extension> extensions = asList(e1, e2, e3);
         Collections.shuffle(extensions);
diff --git a/integration-tests/devtools/src/test/java/io/quarkus/devtools/project/codegen/rest/BasicRestProjectGeneratorTest.java b/integration-tests/devtools/src/test/java/io/quarkus/devtools/project/codegen/rest/BasicRestProjectGeneratorTest.java
deleted file mode 100644
index b870251971fc17..00000000000000
--- a/integration-tests/devtools/src/test/java/io/quarkus/devtools/project/codegen/rest/BasicRestProjectGeneratorTest.java
+++ /dev/null
@@ -1,148 +0,0 @@
-package io.quarkus.devtools.project.codegen.rest;
-
-import static io.quarkus.devtools.project.codegen.ProjectGenerator.BOM_VERSION;
-import static io.quarkus.devtools.project.codegen.ProjectGenerator.CLASS_NAME;
-import static io.quarkus.devtools.project.codegen.ProjectGenerator.IS_SPRING;
-import static io.quarkus.devtools.project.codegen.ProjectGenerator.PACKAGE_NAME;
-import static io.quarkus.devtools.project.codegen.ProjectGenerator.PROJECT_ARTIFACT_ID;
-import static io.quarkus.devtools.project.codegen.ProjectGenerator.PROJECT_GROUP_ID;
-import static io.quarkus.devtools.project.codegen.ProjectGenerator.PROJECT_VERSION;
-import static io.quarkus.devtools.project.codegen.ProjectGenerator.SOURCE_TYPE;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.Callable;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.Timeout;
-
-import io.quarkus.bootstrap.util.IoUtils;
-import io.quarkus.devtools.PlatformAwareTestBase;
-import io.quarkus.devtools.commands.data.QuarkusCommandInvocation;
-import io.quarkus.devtools.project.QuarkusProject;
-import io.quarkus.devtools.project.codegen.SourceType;
-import io.quarkus.devtools.project.codegen.writer.ProjectWriter;
-import io.quarkus.maven.utilities.MojoUtils;
-
-class BasicRestProjectGeneratorTest extends PlatformAwareTestBase {
-
-    private final Map<String, Object> basicProjectContext = new HashMap<>();
-
-    @BeforeEach
-    void setUp() {
-        basicProjectContext.put(PROJECT_GROUP_ID, "org.example");
-        basicProjectContext.put(PROJECT_ARTIFACT_ID, "quarkus-app");
-        basicProjectContext.put(PROJECT_VERSION, "0.0.1-SNAPSHOT");
-        basicProjectContext.put(BOM_VERSION, getBomVersion());
-        basicProjectContext.put(PACKAGE_NAME, "org.example");
-        basicProjectContext.put(CLASS_NAME, "ExampleResource");
-        basicProjectContext.put("path", "/hello");
-        basicProjectContext.put(SOURCE_TYPE, SourceType.JAVA);
-    }
-
-    @Test
-    @Timeout(5)
-    @DisplayName("Should generate correctly multiple times in parallel with multiple threads")
-    void generateMultipleTimes() throws InterruptedException {
-        final ExecutorService executorService = Executors.newFixedThreadPool(4);
-        final CountDownLatch latch = new CountDownLatch(20);
-        final BasicRestProjectGenerator basicRestProjectGenerator = new BasicRestProjectGenerator();
-        List<Callable<Void>> collect = IntStream.range(0, 20).boxed().map(i -> (Callable<Void>) () -> {
-            final Path path = Files.createTempDirectory("test");
-            try {
-                basicRestProjectGenerator.generate(createQuarkusCommandInvocation(path));
-            } finally {
-                IoUtils.recursiveDelete(path);
-            }
-            latch.countDown();
-            return null;
-        }).collect(Collectors.toList());
-        executorService.invokeAll(collect);
-        latch.await();
-    }
-
-    @Test
-    @DisplayName("Should generate project files with basic context")
-    void generateFilesWithJaxRsResource() throws Exception {
-        final ProjectWriter mockWriter = mock(ProjectWriter.class);
-        final Path mockProjectPath = mock(Path.class);
-        final BasicRestProjectGenerator basicRestProjectGenerator = new BasicRestProjectGenerator();
-
-        when(mockWriter.mkdirs(anyString())).thenAnswer(invocationOnMock -> invocationOnMock.getArgument(0, String.class));
-
-        basicRestProjectGenerator.generate(mockWriter,
-                createQuarkusCommandInvocation(mockProjectPath));
-
-        verify(mockWriter, times(10)).mkdirs(anyString());
-        verify(mockWriter, times(3)).mkdirs("");
-        verify(mockWriter, times(1)).mkdirs("src/main/java");
-        verify(mockWriter, times(1)).mkdirs("src/main/java/org/example");
-        verify(mockWriter, times(1)).mkdirs("src/test/java");
-        verify(mockWriter, times(1)).mkdirs("src/test/java/org/example");
-        verify(mockWriter, times(1)).mkdirs("src/main/resources");
-        verify(mockWriter, times(1)).mkdirs("src/main/resources/META-INF/resources");
-        verify(mockWriter, times(1)).mkdirs("src/main/docker");
-
-        verify(mockWriter, times(12)).write(anyString(), anyString());
-        verify(mockWriter, times(1)).write(eq("pom.xml"),
-                argThat(argument -> argument.contains("<groupId>org.example</groupId>")
-                        && argument.contains("<artifactId>quarkus-app</artifactId")
-                        && argument.contains("<version>0.0.1-SNAPSHOT</version>")
-                        && argument.contains(
-                                "<" + MojoUtils.TEMPLATE_PROPERTY_QUARKUS_PLATFORM_VERSION_NAME + ">" + getQuarkusCoreVersion()
-                                        + "</" + MojoUtils.TEMPLATE_PROPERTY_QUARKUS_PLATFORM_VERSION_NAME + ">")));
-        verify(mockWriter, times(1)).write(eq("src/main/java/org/example/ExampleResource.java"),
-                argThat(argument -> argument.contains("@Path(\"/hello\")")));
-        verify(mockWriter, times(1)).write(eq("src/test/java/org/example/ExampleResourceTest.java"), anyString());
-        verify(mockWriter, times(1)).write(eq("src/test/java/org/example/NativeExampleResourceIT.java"), anyString());
-        verify(mockWriter, times(1)).write(eq("src/main/resources/application.properties"), anyString());
-        verify(mockWriter, times(1)).write(eq("src/main/resources/META-INF/resources/index.html"), anyString());
-        verify(mockWriter, times(1)).write(eq("src/main/docker/Dockerfile.native"), anyString());
-        verify(mockWriter, times(1)).write(eq("src/main/docker/Dockerfile.jvm"), anyString());
-        verify(mockWriter, times(1)).write(eq("src/main/docker/Dockerfile.legacy-jar"), anyString());
-        verify(mockWriter, times(1)).write(eq(".dockerignore"), anyString());
-    }
-
-    @Test
-    @DisplayName("Should generate project files with basic spring web context")
-    void generateFilesWithSpringControllerResource() throws Exception {
-        final ProjectWriter mockWriter = mock(ProjectWriter.class);
-        final Path mockProjectPath = mock(Path.class);
-        final BasicRestProjectGenerator basicRestProjectGenerator = new BasicRestProjectGenerator();
-
-        when(mockWriter.mkdirs(anyString())).thenAnswer(invocationOnMock -> invocationOnMock.getArgument(0, String.class));
-
-        QuarkusCommandInvocation springContext = createQuarkusCommandInvocation(mockProjectPath);
-        springContext.setValue(IS_SPRING, Boolean.TRUE);
-        basicRestProjectGenerator.generate(mockWriter, springContext);
-
-        verify(mockWriter, times(1)).write(eq("src/main/java/org/example/ExampleResource.java"),
-                argThat(argument -> argument.contains("@RequestMapping(\"/hello\")")));
-        verify(mockWriter, times(1)).write(eq("src/main/java/org/example/ExampleResource.java"),
-                argThat(argument -> argument.contains("@RestController")));
-
-    }
-
-    private QuarkusCommandInvocation createQuarkusCommandInvocation(Path projectPath) {
-        return new QuarkusCommandInvocation(QuarkusProject.maven(projectPath, getPlatformDescriptor()),
-                basicProjectContext);
-    }
-
-}
diff --git a/integration-tests/devtools/src/test/java/io/quarkus/devtools/project/compress/QuarkusProjectCompressTest.java b/integration-tests/devtools/src/test/java/io/quarkus/devtools/project/compress/QuarkusProjectCompressTest.java
index 883e755e4be0a5..12fdb6f4fb4067 100644
--- a/integration-tests/devtools/src/test/java/io/quarkus/devtools/project/compress/QuarkusProjectCompressTest.java
+++ b/integration-tests/devtools/src/test/java/io/quarkus/devtools/project/compress/QuarkusProjectCompressTest.java
@@ -22,6 +22,9 @@
 import io.quarkus.devtools.commands.CreateProject;
 import io.quarkus.devtools.commands.data.QuarkusCommandException;
 import io.quarkus.devtools.commands.data.QuarkusCommandOutcome;
+import io.quarkus.devtools.project.BuildTool;
+import io.quarkus.devtools.project.QuarkusProject;
+import io.quarkus.devtools.project.QuarkusProjectHelper;
 import io.quarkus.devtools.testing.SnapshotTesting;
 
 class QuarkusProjectCompressTest extends PlatformAwareTestBase {
@@ -82,7 +85,8 @@ private void checkUnzipped(Path projectPath, Path unzipProject) throws IOExcepti
 
     private Path createProject(Path testDir) throws QuarkusCommandException, IOException {
         final Path projectPath = testDir.resolve("project");
-        final QuarkusCommandOutcome result = new CreateProject(projectPath, getPlatformDescriptor())
+        final QuarkusProject project = QuarkusProjectHelper.getProject(projectPath, BuildTool.MAVEN);
+        final QuarkusCommandOutcome result = new CreateProject(project)
                 .groupId("org.acme")
                 .artifactId("basic-rest")
                 .version("1.0.0-SNAPSHOT")
diff --git a/integration-tests/devtools/src/test/resources/platform-metadata.json b/integration-tests/devtools/src/test/resources/platform-metadata.json
index f392cfc6d3e338..49c0eb3f4dc3ac 100644
--- a/integration-tests/devtools/src/test/resources/platform-metadata.json
+++ b/integration-tests/devtools/src/test/resources/platform-metadata.json
@@ -24,5 +24,29 @@
         "url": "https://maven.repository.redhat.com"
       }
     ]
-  }
+  },
+     "project": {
+      "properties": {
+        "doc-root": "https://quarkus.io",
+        "rest-assured-version": "${rest-assured.version}",
+        "compiler-plugin-version": "${compiler-plugin.version}",
+        "surefire-plugin-version": "${version.surefire.plugin}",
+        "kotlin-version": "${kotlin.version}",
+        "scala-version": "${scala.version}",
+        "scala-plugin-version": "${scala-plugin.version}",
+        "quarkus-core-version": "${project.version}",
+        "maven-plugin-groupId": "${project.groupId}",
+        "maven-plugin-artifactId": "quarkus-maven-plugin",
+        "maven-plugin-version": "${project.version}",
+        "gradle-plugin-id": "io.quarkus",
+        "gradle-plugin-version": "${project.version}",
+        "supported-maven-versions": "${supported-maven-versions}",
+        "proposed-maven-version": "${proposed-maven-version}",
+        "maven-wrapper-version": "${maven-wrapper.version}",
+        "gradle-wrapper-version": "${gradle-wrapper.version}"
+      }
+    },
+    "codestarts-artifacts": [
+      "${project.groupId}:quarkus-platform-descriptor-json::jar:${project.version}"
+    ]
 }
\ No newline at end of file
diff --git a/integration-tests/gradle/build.gradle b/integration-tests/gradle/build.gradle
index ab1a01a881e7ed..ff678de35e9943 100644
--- a/integration-tests/gradle/build.gradle
+++ b/integration-tests/gradle/build.gradle
@@ -27,17 +27,13 @@ repositories {
     mavenCentral()
 }
 
-configurations.all {
-    exclude group: 'io.quarkus', module: 'quarkus-bootstrap-maven-resolver'
-}
-
 dependencies {
+    testImplementation "io.quarkus:quarkus-test-devtools:${version}"
     testImplementation "io.quarkus:quarkus-bootstrap-core:${version}"
     testImplementation "io.quarkus:quarkus-core-deployment:${version}"
     testImplementation "io.quarkus:quarkus-devmode-test-utils:${version}"
     testImplementation "io.quarkus:quarkus-devtools-common:${version}"
     testImplementation "io.quarkus:quarkus-platform-descriptor-json:${version}"
-    testImplementation "io.quarkus:quarkus-platform-descriptor-resolver-json:${version}"
     testImplementation "io.quarkus:io.quarkus.gradle.plugin:${version}"
     testImplementation 'org.mockito:mockito-core:3.8.0'
     testImplementation 'org.assertj:assertj-core:3.19.0'
@@ -45,11 +41,21 @@ dependencies {
     testImplementation 'org.awaitility:awaitility:4.0.3'
 }
 
+processTestResources {
+    filesMatching('.quarkus/config.yaml') {
+        expand(testResourcesDir: destinationDir)
+    }
+    filesMatching('test-registry-repo/**') {
+        expand(project.properties)
+    }
+}
+
 test {
     // propagate the custom local maven repo, in case it's configured
     if (System.properties.containsKey('maven.repo.local')) {
         systemProperty 'maven.repo.local', System.properties.get('maven.repo.local')
     }
+    systemProperty 'project.version', "${version}"
     useJUnitPlatform()
 
     // Kotlin compiler does not support Java 14
diff --git a/integration-tests/gradle/gradle.properties b/integration-tests/gradle/gradle.properties
index 72a61ab94d2419..6b1bd1f00fe120 100644
--- a/integration-tests/gradle/gradle.properties
+++ b/integration-tests/gradle/gradle.properties
@@ -1 +1,3 @@
+quarkusPlatformArtifactId=quarkus-bom
+quarkusPlatformGroupId=io.quarkus
 version = 999-SNAPSHOT
diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/AddExtensionToModuleInMultiModuleKtsProjectTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/AddExtensionToModuleInMultiModuleKtsProjectTest.java
index b822dae349dbc8..e42fae0ed98829 100644
--- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/AddExtensionToModuleInMultiModuleKtsProjectTest.java
+++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/AddExtensionToModuleInMultiModuleKtsProjectTest.java
@@ -11,7 +11,7 @@
 
 import org.junit.jupiter.api.Test;
 
-public class AddExtensionToModuleInMultiModuleKtsProjectTest extends QuarkusGradleWrapperTestBase {
+public class AddExtensionToModuleInMultiModuleKtsProjectTest extends QuarkusGradleDevToolsTestBase {
 
     private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
 
diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/AddExtensionToModuleInMultiModuleProjectTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/AddExtensionToModuleInMultiModuleProjectTest.java
index 9c98a0d679d199..18b49e556cc957 100644
--- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/AddExtensionToModuleInMultiModuleProjectTest.java
+++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/AddExtensionToModuleInMultiModuleProjectTest.java
@@ -11,7 +11,7 @@
 
 import org.junit.jupiter.api.Test;
 
-public class AddExtensionToModuleInMultiModuleProjectTest extends QuarkusGradleWrapperTestBase {
+public class AddExtensionToModuleInMultiModuleProjectTest extends QuarkusGradleDevToolsTestBase {
 
     private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
 
diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/AddExtensionToSingleModuleKtsProjectTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/AddExtensionToSingleModuleKtsProjectTest.java
index 4c66932a39f8e8..ab7ea55623e5dc 100644
--- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/AddExtensionToSingleModuleKtsProjectTest.java
+++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/AddExtensionToSingleModuleKtsProjectTest.java
@@ -10,7 +10,7 @@
 
 import org.junit.jupiter.api.Test;
 
-public class AddExtensionToSingleModuleKtsProjectTest extends QuarkusGradleWrapperTestBase {
+public class AddExtensionToSingleModuleKtsProjectTest extends QuarkusGradleDevToolsTestBase {
 
     @Test
     public void testAddAndRemoveExtension() throws IOException, URISyntaxException, InterruptedException {
diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/AddExtensionToSingleModuleProjectTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/AddExtensionToSingleModuleProjectTest.java
index 40ee566171989c..9d6965d4547f69 100644
--- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/AddExtensionToSingleModuleProjectTest.java
+++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/AddExtensionToSingleModuleProjectTest.java
@@ -10,7 +10,7 @@
 
 import org.junit.jupiter.api.Test;
 
-public class AddExtensionToSingleModuleProjectTest extends QuarkusGradleWrapperTestBase {
+public class AddExtensionToSingleModuleProjectTest extends QuarkusGradleDevToolsTestBase {
 
     @Test
     public void testAddAndRemoveExtension() throws IOException, URISyntaxException, InterruptedException {
diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/ListExtensionsTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/ListExtensionsTest.java
index 5185de14bd9512..5d880c0300714e 100644
--- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/ListExtensionsTest.java
+++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/ListExtensionsTest.java
@@ -10,13 +10,13 @@
 
 import org.junit.jupiter.api.Test;
 
-public class ListExtensionsTest extends QuarkusGradleWrapperTestBase {
+public class ListExtensionsTest extends QuarkusGradleDevToolsTestBase {
 
     @Test
     public void testListExtensionsWork() throws IOException, URISyntaxException, InterruptedException {
 
         final File projectDir = getProjectDir("list-extension-single-module");
-        runGradleWrapper(projectDir, ":listExtension");
+        runGradleWrapper(projectDir, ":listExtensions");
 
         List<String> outputLogLines = listExtensions(projectDir, ":listExtension");
 
diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleDevToolsTestBase.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleDevToolsTestBase.java
new file mode 100644
index 00000000000000..d8fa4d49832ca1
--- /dev/null
+++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleDevToolsTestBase.java
@@ -0,0 +1,36 @@
+package io.quarkus.gradle;
+
+import java.nio.file.Paths;
+import java.util.Map;
+import java.util.Properties;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+
+import io.quarkus.devtools.test.DevToolsRegistryTestHelper;
+
+public class QuarkusGradleDevToolsTestBase extends QuarkusGradleWrapperTestBase {
+
+    private static Properties devToolsProps = new Properties(2);
+
+    @BeforeAll
+    static void enableDevToolsTestConfig() {
+        DevToolsRegistryTestHelper.enableDevToolsTestConfig(Paths.get("").normalize().toAbsolutePath().resolve("build"),
+                devToolsProps);
+        for (Map.Entry<?, ?> prop : devToolsProps.entrySet()) {
+            System.setProperty(prop.getKey().toString(), prop.getValue().toString());
+        }
+    }
+
+    @AfterAll
+    static void disableDevToolsTestConfig() {
+        DevToolsRegistryTestHelper.disableDevToolsTestConfig();
+    }
+
+    @Override
+    protected void setupTestCommand() {
+        for (Map.Entry<?, ?> prop : devToolsProps.entrySet()) {
+            setSystemProperty(prop.getKey().toString(), prop.getValue().toString());
+        }
+    }
+}
diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleWrapperTestBase.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleWrapperTestBase.java
index 18d7b7fb4f6e48..ddc00eb24dd81a 100644
--- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleWrapperTestBase.java
+++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleWrapperTestBase.java
@@ -7,9 +7,12 @@
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
 public class QuarkusGradleWrapperTestBase extends QuarkusGradleTestBase {
@@ -19,7 +22,14 @@ public class QuarkusGradleWrapperTestBase extends QuarkusGradleTestBase {
     private static final String GRADLE_NO_DAEMON = "--no-daemon";
     private static final String MAVEN_REPO_LOCAL = "maven.repo.local";
 
+    private Map<String, String> systemProps;
+
+    protected void setupTestCommand() {
+
+    }
+
     public BuildResult runGradleWrapper(File projectDir, String... args) throws IOException, InterruptedException {
+        setupTestCommand();
         List<String> command = new LinkedList<>();
         command.add(getGradleWrapperCommand());
         command.add(GRADLE_NO_DAEMON);
@@ -48,6 +58,13 @@ public BuildResult runGradleWrapper(File projectDir, String... args) throws IOEx
         }
     }
 
+    protected void setSystemProperty(String name, String value) {
+        if (systemProps == null) {
+            systemProps = new HashMap<>();
+        }
+        systemProps.put(name, value);
+    }
+
     private String getGradleWrapperCommand() {
         return Paths.get(getGradleWrapperName()).toAbsolutePath().toString();
     }
@@ -60,11 +77,27 @@ private String getGradleWrapperName() {
     }
 
     private List<String> getSytemProperties() {
-        List<String> systemProperties = new ArrayList<>();
-        if (System.getProperties().containsKey(MAVEN_REPO_LOCAL)) {
-            systemProperties.add(String.format("-D%s=%s", MAVEN_REPO_LOCAL, System.getProperty(MAVEN_REPO_LOCAL)));
+        List<String> args = null;
+        if (systemProps != null) {
+            args = new ArrayList<>(systemProps.size() + 1);
+            for (Map.Entry<String, String> prop : systemProps.entrySet()) {
+                args.add(toPropertyArg(prop.getKey(), prop.getValue()));
+            }
+        }
+        final String mavenRepoLocal = System.getProperty(MAVEN_REPO_LOCAL);
+        if (mavenRepoLocal != null) {
+            final String arg = toPropertyArg(MAVEN_REPO_LOCAL, mavenRepoLocal);
+            if (args == null) {
+                args = Collections.singletonList(arg);
+            } else {
+                args.add(arg);
+            }
         }
-        return systemProperties;
+        return args == null ? Collections.emptyList() : args;
+    }
+
+    private static String toPropertyArg(String name, String value) {
+        return new StringBuilder().append("-D=").append(name).append("=").append(value).toString();
     }
 
     private void printCommandOutput(List<String> command, BuildResult commandResult) {
diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java
index b6523fd3709fd5..d3829a0da725b8 100644
--- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java
+++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java
@@ -18,11 +18,11 @@
 
 import io.quarkus.devtools.commands.CreateProject;
 import io.quarkus.devtools.project.BuildTool;
+import io.quarkus.devtools.project.QuarkusProjectHelper;
 import io.quarkus.devtools.project.codegen.SourceType;
-import io.quarkus.platform.tools.config.QuarkusPlatformConfig;
 import io.quarkus.test.devmode.util.DevModeTestUtils;
 
-public class QuarkusPluginFunctionalTest extends QuarkusGradleWrapperTestBase {
+public class QuarkusPluginFunctionalTest extends QuarkusGradleDevToolsTestBase {
 
     private File projectRoot;
 
@@ -164,12 +164,11 @@ public void canRunTest() throws Exception {
     private void createProject(SourceType sourceType) throws Exception {
         Map<String, Object> context = new HashMap<>();
         context.put("path", "/greeting");
-        assertThat(new CreateProject(projectRoot.toPath(),
-                QuarkusPlatformConfig.getGlobalDefault().getPlatformDescriptor())
+        assertThat(new CreateProject(QuarkusProjectHelper.getProject(projectRoot.toPath(),
+                BuildTool.GRADLE))
                         .groupId("com.acme.foo")
                         .artifactId("foo")
                         .version("1.0.0-SNAPSHOT")
-                        .buildTool(BuildTool.GRADLE)
                         .className("org.acme.foo.GreetingResource")
                         .sourceType(sourceType)
                         .doCreateProject(context))
diff --git a/integration-tests/gradle/src/test/resources/.quarkus/config.yaml b/integration-tests/gradle/src/test/resources/.quarkus/config.yaml
new file mode 100644
index 00000000000000..82f4783ee7130b
--- /dev/null
+++ b/integration-tests/gradle/src/test/resources/.quarkus/config.yaml
@@ -0,0 +1,7 @@
+---
+debug: true
+registries:
+- test.registry.quarkus.io:
+    maven:
+        repository:
+            url: file://$testResourcesDir/test-registry-repo
diff --git a/integration-tests/gradle/src/test/resources/test-registry-repo/io/quarkus/registry/test/quarkus-platforms/1.0-SNAPSHOT/quarkus-platforms-1.0-SNAPSHOT.json b/integration-tests/gradle/src/test/resources/test-registry-repo/io/quarkus/registry/test/quarkus-platforms/1.0-SNAPSHOT/quarkus-platforms-1.0-SNAPSHOT.json
new file mode 100644
index 00000000000000..005be0766b3da3
--- /dev/null
+++ b/integration-tests/gradle/src/test/resources/test-registry-repo/io/quarkus/registry/test/quarkus-platforms/1.0-SNAPSHOT/quarkus-platforms-1.0-SNAPSHOT.json
@@ -0,0 +1,7 @@
+{
+  "default-platform" : "$quarkusPlatformGroupId:$quarkusPlatformArtifactId::pom:$version",
+  "platforms" : [ {
+    "bom": "$quarkusPlatformGroupId:$quarkusPlatformArtifactId::pom:$version",
+    "quarkus-core-version": "$version"
+  } ]
+}
\ No newline at end of file
diff --git a/integration-tests/gradle/src/test/resources/test-registry-repo/io/quarkus/registry/test/quarkus-registry-descriptor/1.0-SNAPSHOT/quarkus-registry-descriptor-1.0-SNAPSHOT.json b/integration-tests/gradle/src/test/resources/test-registry-repo/io/quarkus/registry/test/quarkus-registry-descriptor/1.0-SNAPSHOT/quarkus-registry-descriptor-1.0-SNAPSHOT.json
new file mode 100644
index 00000000000000..c1a3b991ba2b12
--- /dev/null
+++ b/integration-tests/gradle/src/test/resources/test-registry-repo/io/quarkus/registry/test/quarkus-registry-descriptor/1.0-SNAPSHOT/quarkus-registry-descriptor-1.0-SNAPSHOT.json
@@ -0,0 +1,8 @@
+{
+  "platforms" : {
+    "artifact" : "io.quarkus.registry.test:quarkus-platforms::json:1.0-SNAPSHOT"
+  },
+  "non-platform-extensions" : {
+    "disabled" : true
+  }
+}
\ No newline at end of file
diff --git a/integration-tests/kotlin/pom.xml b/integration-tests/kotlin/pom.xml
index 617dee803b549e..b1cf0f5215bc12 100644
--- a/integration-tests/kotlin/pom.xml
+++ b/integration-tests/kotlin/pom.xml
@@ -29,6 +29,11 @@
             <groupId>io.quarkus</groupId>
             <artifactId>quarkus-kotlin-deployment</artifactId>
         </dependency>
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-bootstrap-maven-resolver</artifactId>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>io.quarkus</groupId>
             <artifactId>quarkus-platform-descriptor-json</artifactId>
diff --git a/integration-tests/kotlin/src/test/java/io/quarkus/kotlin/maven/it/KotlinCreateMavenProjectIT.java b/integration-tests/kotlin/src/test/java/io/quarkus/kotlin/maven/it/KotlinCreateMavenProjectIT.java
index ff46b7862acb3b..a287af51a1778c 100644
--- a/integration-tests/kotlin/src/test/java/io/quarkus/kotlin/maven/it/KotlinCreateMavenProjectIT.java
+++ b/integration-tests/kotlin/src/test/java/io/quarkus/kotlin/maven/it/KotlinCreateMavenProjectIT.java
@@ -77,6 +77,7 @@ private InvocationResult setup(Properties params)
 
         params.setProperty("platformArtifactId", "quarkus-bom");
         params.setProperty("platformVersion", getQuarkusCoreVersion());
+        enableDevToolsTestConfig(params);
 
         InvocationRequest request = new DefaultInvocationRequest();
         request.setBatchMode(true);
diff --git a/integration-tests/maven/pom.xml b/integration-tests/maven/pom.xml
index bcfaede377a50c..840d608e604a7e 100644
--- a/integration-tests/maven/pom.xml
+++ b/integration-tests/maven/pom.xml
@@ -63,10 +63,6 @@
                 <directory>src/test/resources</directory>
                 <filtering>true</filtering>
             </testResource>
-            <testResource>
-                <directory>src/it</directory>
-                <filtering>true</filtering>
-            </testResource>
         </testResources>
         <plugins>
             <plugin>
diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/AddExtensionMojoTest.java b/integration-tests/maven/src/test/java/io/quarkus/maven/AddExtensionMojoTest.java
index a99524f7b5da31..3f64e33c431c58 100644
--- a/integration-tests/maven/src/test/java/io/quarkus/maven/AddExtensionMojoTest.java
+++ b/integration-tests/maven/src/test/java/io/quarkus/maven/AddExtensionMojoTest.java
@@ -21,12 +21,15 @@
 import org.eclipse.aether.artifact.Artifact;
 import org.eclipse.aether.artifact.DefaultArtifact;
 import org.eclipse.aether.resolution.ArtifactDescriptorResult;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 import io.quarkus.bootstrap.resolver.maven.BootstrapMavenContext;
 import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver;
 import io.quarkus.bootstrap.resolver.maven.workspace.ModelUtils;
+import io.quarkus.devtools.test.DevToolsRegistryTestHelper;
 
 class AddExtensionMojoTest {
 
@@ -35,6 +38,16 @@ class AddExtensionMojoTest {
     private static final String DEP_GAV = "org.apache.commons:commons-lang3:3.8.1";
     private AddExtensionMojo mojo;
 
+    @BeforeAll
+    static void globalInit() {
+        DevToolsRegistryTestHelper.enableDevToolsTestConfig();
+    }
+
+    @AfterAll
+    static void globalCleanUp() {
+        DevToolsRegistryTestHelper.disableDevToolsTestConfig();
+    }
+
     @BeforeEach
     void init() throws Exception {
         mojo = getMojo();
diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/CreateExtensionLegacyMojoTest.java b/integration-tests/maven/src/test/java/io/quarkus/maven/CreateExtensionLegacyMojoTest.java
deleted file mode 100644
index cb99c902be64e2..00000000000000
--- a/integration-tests/maven/src/test/java/io/quarkus/maven/CreateExtensionLegacyMojoTest.java
+++ /dev/null
@@ -1,272 +0,0 @@
-package io.quarkus.maven;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.fail;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Arrays;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Set;
-import java.util.UUID;
-import java.util.stream.Collectors;
-
-import org.apache.maven.model.Build;
-import org.apache.maven.model.Dependency;
-import org.apache.maven.model.Model;
-import org.apache.maven.model.PluginManagement;
-import org.apache.maven.plugin.MojoExecutionException;
-import org.apache.maven.plugin.MojoFailureException;
-import org.apache.maven.project.MavenProject;
-import org.junit.jupiter.api.Test;
-
-import io.quarkus.maven.utilities.MojoUtils;
-
-public class CreateExtensionLegacyMojoTest {
-
-    private static CreateExtensionLegacyMojo initMojo(final Path projectDir) throws IOException {
-        final CreateExtensionLegacyMojo mojo = new CreateExtensionLegacyMojo();
-        mojo.project = new MavenProject();
-        mojo.basedir = projectDir.toFile();
-        mojo.generateDevModeTest = true;
-        mojo.generateUnitTest = true;
-
-        final File pom = new File(projectDir.toFile(), "pom.xml");
-        if (pom.exists()) {
-            mojo.project.setFile(pom);
-            final Model rawModel = MojoUtils.readPom(pom);
-            // the project would have an interpolated model at runtime, which we can't fully init here
-            // here are just some key parts
-            if (rawModel.getDependencyManagement() != null) {
-                List<Dependency> deps = rawModel.getDependencyManagement().getDependencies();
-                if (deps != null && !deps.isEmpty()) {
-                    Dependency deploymentBom = null;
-                    for (Dependency dep : deps) {
-                        if (dep.getArtifactId().equals("quarkus-bom") && dep.getGroupId().equals("io.quarkus")) {
-                            deploymentBom = dep;
-                        }
-                    }
-                    if (deploymentBom != null) {
-                        String version = deploymentBom.getVersion();
-                        if (CreateExtensionLegacyMojo.QUARKUS_VERSION_POM_EXPR.equals(version)) {
-                            version = rawModel.getProperties().getProperty(version.substring(2, version.length() - 1));
-                            if (version == null) {
-                                throw new IllegalStateException(
-                                        "Failed to resolve " + deploymentBom.getVersion() + " from " + pom);
-                            }
-                        }
-                        Dependency dep = new Dependency();
-                        dep.setGroupId("io.quarkus");
-                        dep.setArtifactId("quarkus-core-deployment");
-                        dep.setType("jar");
-                        dep.setVersion(version);
-                        deps.add(dep);
-                    }
-                }
-            }
-            mojo.project.setModel(rawModel);
-        }
-
-        Build build = mojo.project.getBuild();
-        if (build.getPluginManagement() == null) {
-            build.setPluginManagement(new PluginManagement());
-        }
-
-        mojo.encoding = CreateExtensionLegacyMojo.DEFAULT_ENCODING;
-        mojo.templatesUriBase = CreateExtensionLegacyMojo.DEFAULT_TEMPLATES_URI_BASE;
-        mojo.quarkusVersion = CreateExtensionLegacyMojo.DEFAULT_QUARKUS_VERSION;
-        mojo.bomEntryVersion = CreateExtensionLegacyMojo.DEFAULT_BOM_ENTRY_VERSION;
-        mojo.assumeManaged = true;
-        mojo.nameSegmentDelimiter = CreateExtensionLegacyMojo.DEFAULT_NAME_SEGMENT_DELIMITER;
-        mojo.platformGroupId = CreateExtensionLegacyMojo.PLATFORM_DEFAULT_GROUP_ID;
-        mojo.platformArtifactId = CreateExtensionLegacyMojo.PLATFORM_DEFAULT_ARTIFACT_ID;
-        mojo.compilerPluginVersion = CreateExtensionLegacyMojo.COMPILER_PLUGIN_DEFAULT_VERSION;
-        return mojo;
-    }
-
-    private static Path createProjectFromTemplate(String testProjectName) throws IOException {
-        final Path srcDir = Paths.get("src/test/resources/projects/" + testProjectName);
-        /*
-         * We want to run on the same project multiple times with different args so let's create a copy with a random
-         * suffix
-         */
-        final Path copyDir = newProjectDir(testProjectName);
-        Files.walk(srcDir).forEach(source -> {
-            final Path dest = copyDir.resolve(srcDir.relativize(source));
-            try {
-                Files.copy(source, dest);
-            } catch (IOException e) {
-                if (!Files.isDirectory(dest)) {
-                    throw new RuntimeException(e);
-                }
-            }
-        });
-        return copyDir;
-    }
-
-    private static Path newProjectDir(String testProjectName) throws IOException {
-        int count = 0;
-        while (count < 100) {
-            Path path = Paths.get("target/test-classes/projects/" + testProjectName + "-" + UUID.randomUUID());
-            if (!Files.exists(path)) {
-                Files.createDirectories(path);
-                return path;
-            }
-            count++;
-        }
-
-        // if we have tried too many times we just give up instead of looping forever which could cause the test to never end
-        throw new RuntimeException("Unable to create a directory for copying the test application into");
-    }
-
-    @Test
-    void createExtensionUnderExistingPomMinimal() throws MojoExecutionException, MojoFailureException,
-            IllegalArgumentException, SecurityException, IOException {
-        final CreateExtensionLegacyMojo mojo = initMojo(createProjectFromTemplate("create-extension-pom"));
-        mojo.artifactId = "my-project-(minimal-extension)";
-        mojo.assumeManaged = false;
-        mojo.execute();
-
-        assertTreesMatch(Paths.get("src/test/resources/expected/create-extension-pom-minimal"),
-                mojo.basedir.toPath());
-    }
-
-    @Test
-    void createExtensionUnderExistingPomWithAdditionalRuntimeDependencies() throws MojoExecutionException, MojoFailureException,
-            IllegalArgumentException, SecurityException, IOException {
-        final CreateExtensionLegacyMojo mojo = initMojo(createProjectFromTemplate("create-extension-pom"));
-        mojo.artifactId = "my-project-(add-to-bom)";
-        mojo.assumeManaged = false;
-        mojo.bomPath = Paths.get("bom/pom.xml");
-        mojo.additionalRuntimeDependencies = Arrays.asList("org.example:example-1:1.2.3",
-                "org.acme:acme-@{quarkus.artifactIdBase}:@{$}{acme.version}");
-        mojo.execute();
-
-        assertTreesMatch(Paths.get("src/test/resources/expected/create-extension-pom-add-to-bom"),
-                mojo.basedir.toPath());
-    }
-
-    @Test
-    void createExtensionUnderExistingPomWithItest() throws MojoExecutionException, MojoFailureException,
-            IllegalArgumentException, SecurityException, IOException {
-        final CreateExtensionLegacyMojo mojo = initMojo(createProjectFromTemplate("create-extension-pom"));
-        mojo.artifactId = "my-project-(itest)";
-        mojo.assumeManaged = false;
-        mojo.itestParentPath = Paths.get("integration-tests/pom.xml");
-        mojo.execute();
-
-        assertTreesMatch(Paths.get("src/test/resources/expected/create-extension-pom-itest"),
-                mojo.basedir.toPath());
-    }
-
-    @Test
-    void createExtensionUnderExistingPomCustomGrandParent() throws MojoExecutionException, MojoFailureException,
-            IllegalArgumentException, SecurityException, IOException {
-        final CreateExtensionLegacyMojo mojo = initMojo(createProjectFromTemplate("create-extension-pom"));
-        mojo.artifactId = "myproject-(with-grand-parent)";
-        mojo.parentArtifactId = "grand-parent";
-        mojo.parentRelativePath = "../pom.xml";
-        mojo.templatesUriBase = "file:templates";
-
-        mojo.bomPath = Paths.get("bom/pom.xml");
-        mojo.execute();
-        assertTreesMatch(
-                Paths.get("src/test/resources/expected/create-extension-pom-with-grand-parent"),
-                mojo.basedir.toPath());
-    }
-
-    @Test
-    void createNewExtensionProject() throws Exception {
-        final CreateExtensionLegacyMojo mojo = initMojo(newProjectDir("new-ext-project"));
-        mojo.groupId = "org.acme";
-        mojo.artifactId = "my-ext";
-        mojo.version = "1.0-SNAPSHOT";
-        mojo.assumeManaged = null;
-        mojo.execute();
-        assertTreesMatch(
-                Paths.get("target/test-classes/expected/new-extension-project"),
-                mojo.basedir.toPath());
-    }
-
-    @Test
-    void createNewExtensionOnCurrentDirectory() throws Exception {
-        final CreateExtensionLegacyMojo mojo = initMojo(newProjectDir("new-extension-current-directory-project"));
-        mojo.groupId = "org.acme";
-        mojo.artifactId = "my-ext";
-        mojo.version = "1.0-SNAPSHOT";
-        mojo.useCurrentDirectory = true;
-        mojo.assumeManaged = null;
-        mojo.execute();
-        assertTreesMatch(
-                Paths.get("target/test-classes/expected/new-extension-current-directory-project"),
-                mojo.basedir.toPath());
-    }
-
-    @Test
-    void createNewExtensionProjectWithJBossParent() throws Exception {
-        final CreateExtensionLegacyMojo mojo = initMojo(newProjectDir("new-ext-project-with-jboss-parent"));
-        mojo.parentGroupId = "org.jboss";
-        mojo.parentArtifactId = "jboss-parent";
-        mojo.parentVersion = "37";
-        mojo.groupId = "org.acme";
-        mojo.artifactId = "my-ext";
-        mojo.version = "1.0-SNAPSHOT";
-        mojo.assumeManaged = null;
-        mojo.execute();
-        assertTreesMatch(
-                Paths.get("target/test-classes/expected/new-extension-project-with-jboss-parent"),
-                mojo.basedir.toPath());
-    }
-
-    static void assertTreesMatch(Path expected, Path actual) throws IOException {
-        final Set<Path> expectedFiles = new LinkedHashSet<>();
-        Files.walk(expected).filter(Files::isRegularFile).forEach(p -> {
-            final Path relative = expected.relativize(p);
-            expectedFiles.add(relative);
-            final Path actualPath = actual.resolve(relative);
-            try {
-                String expectedContent = new String(Files.readAllBytes(p), StandardCharsets.UTF_8);
-                String actualContent = new String(Files.readAllBytes(actualPath), StandardCharsets.UTF_8);
-                if (System.getProperty("os.name").toLowerCase(Locale.ROOT).startsWith("windows")) {
-                    expectedContent = expectedContent.replace(System.lineSeparator(), "\n");
-                    actualContent = actualContent.replace(System.lineSeparator(), "\n");
-                }
-                assertEquals(expectedContent, actualContent);
-            } catch (IOException e) {
-                throw new RuntimeException(e);
-            }
-        });
-
-        final Set<Path> unexpectedFiles = new LinkedHashSet<>();
-        Files.walk(actual).filter(Files::isRegularFile).forEach(p -> {
-            final Path relative = actual.relativize(p);
-            if (!expectedFiles.contains(relative)) {
-                unexpectedFiles.add(relative);
-            }
-        });
-        if (!unexpectedFiles.isEmpty()) {
-            fail(String.format("Files found under [%s] but not defined as expected under [%s]:%s", actual,
-                    expected, unexpectedFiles.stream().map(Path::toString).collect(Collectors.joining("\n    "))));
-        }
-    }
-
-    @Test
-    void getPackage() {
-        assertEquals("org.apache.camel.quarkus.aws.sns.deployment", CreateExtensionLegacyMojo
-                .getJavaPackage("org.apache.camel.quarkus", null, "camel-quarkus-aws-sns-deployment"));
-        assertEquals("org.apache.camel.quarkus.component.aws.sns.deployment", CreateExtensionLegacyMojo
-                .getJavaPackage("org.apache.camel.quarkus", "component", "camel-quarkus-aws-sns-deployment"));
-    }
-
-    @Test
-    void toCapCamelCase() {
-        assertEquals("FooBarBaz", CreateExtensionLegacyMojo.toCapCamelCase("foo-bar-baz"));
-    }
-
-}
diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/AddExtensionIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/AddExtensionIT.java
index cc41f11ae2a0a3..e2202e8a55430b 100644
--- a/integration-tests/maven/src/test/java/io/quarkus/maven/it/AddExtensionIT.java
+++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/AddExtensionIT.java
@@ -139,7 +139,9 @@ private void addExtension(boolean plural, String ext)
         } else {
             properties.setProperty("extension", ext);
         }
+        enableDevToolsTestConfig(properties);
         request.setProperties(properties);
+
         getEnv().forEach(request::addShellEnvironment);
         File log = new File(testDir, "build-add-extension-" + testDir.getName() + ".log");
         PrintStreamLogger logger = new PrintStreamLogger(new PrintStream(new FileOutputStream(log), false, "UTF-8"),
diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/CreateJBangProjectMojoIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/CreateJBangProjectMojoIT.java
index 90421d68ada506..760dc86d0b2dd7 100644
--- a/integration-tests/maven/src/test/java/io/quarkus/maven/it/CreateJBangProjectMojoIT.java
+++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/CreateJBangProjectMojoIT.java
@@ -51,6 +51,7 @@ private InvocationResult setup(Properties params)
                 getMavenPluginGroupId() + ":" + getMavenPluginArtifactId() + ":" + getMavenPluginVersion() + ":create-jbang"));
         request.setDebug(false);
         request.setShowErrors(true);
+        enableDevToolsTestConfig(params);
         request.setProperties(params);
         getEnv().forEach(request::addShellEnvironment);
         File log = new File(testDir, "build-create-" + testDir.getName() + ".log");
diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/CreateProjectCodestartMojoIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/CreateProjectCodestartMojoIT.java
index 0dd71949149a04..3748ed2a673da8 100644
--- a/integration-tests/maven/src/test/java/io/quarkus/maven/it/CreateProjectCodestartMojoIT.java
+++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/CreateProjectCodestartMojoIT.java
@@ -160,7 +160,9 @@ private InvocationResult executeCreate(Properties params)
                 getMavenPluginGroupId() + ":" + getMavenPluginArtifactId() + ":" + getMavenPluginVersion() + ":create"));
         request.setDebug(false);
         request.setShowErrors(true);
+        enableDevToolsTestConfig(params);
         request.setProperties(params);
+
         getEnv().forEach(request::addShellEnvironment);
         PrintStreamLogger logger = getPrintStreamLogger("create-codestart.log");
         invoker.setLogger(logger);
diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/CreateProjectMojoIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/CreateProjectMojoIT.java
index d2ebba6f4e3d44..dd73536b451f03 100644
--- a/integration-tests/maven/src/test/java/io/quarkus/maven/it/CreateProjectMojoIT.java
+++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/CreateProjectMojoIT.java
@@ -61,6 +61,7 @@ public void testProjectGenerationFromScratch() throws MavenInvocationException,
         properties.put("projectGroupId", "org.acme");
         properties.put("projectArtifactId", "acme");
         properties.put("projectVersion", "1.0.0-SNAPSHOT");
+
         InvocationResult result = setup(properties);
 
         assertThat(result.getExitCode()).isZero();
@@ -438,6 +439,7 @@ private InvocationResult setup(Properties params)
                 getMavenPluginGroupId() + ":" + getMavenPluginArtifactId() + ":" + getMavenPluginVersion() + ":create"));
         request.setDebug(false);
         request.setShowErrors(true);
+        enableDevToolsTestConfig(params);
         request.setProperties(params);
         getEnv().forEach(request::addShellEnvironment);
         File log = new File(testDir, "build-create-" + testDir.getName() + ".log");
@@ -446,5 +448,4 @@ private InvocationResult setup(Properties params)
         invoker.setLogger(logger);
         return invoker.execute(request);
     }
-
 }
diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/ListExtensionsIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/ListExtensionsIT.java
index 3edcc11e4d4e12..0f684b26134dd6 100644
--- a/integration-tests/maven/src/test/java/io/quarkus/maven/it/ListExtensionsIT.java
+++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/ListExtensionsIT.java
@@ -65,6 +65,7 @@ private List<String> listExtensions()
                 getMavenPluginGroupId() + ":" + getMavenPluginArtifactId() + ":" + getMavenPluginVersion()
                         + ":list-extensions"));
         getEnv().forEach(request::addShellEnvironment);
+        enableDevToolsTestConfig(request);
 
         File outputLog = new File(testDir, "output.log");
         InvocationOutputHandler outputHandler = new PrintStreamHandler(
diff --git a/integration-tests/scala/pom.xml b/integration-tests/scala/pom.xml
index ad266e97853a3f..d8f7e63b69104a 100644
--- a/integration-tests/scala/pom.xml
+++ b/integration-tests/scala/pom.xml
@@ -25,12 +25,12 @@
         </dependency>
         <dependency>
             <groupId>io.quarkus</groupId>
-            <artifactId>quarkus-test-maven</artifactId>
+            <artifactId>quarkus-bootstrap-maven-resolver</artifactId>
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>org.apache.maven</groupId>
-            <artifactId>maven-model</artifactId>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-test-maven</artifactId>
             <scope>test</scope>
         </dependency>
         <dependency>
diff --git a/integration-tests/scala/src/test/java/io/quarkus/scala/maven/it/ScalaCreateMavenProjectIT.java b/integration-tests/scala/src/test/java/io/quarkus/scala/maven/it/ScalaCreateMavenProjectIT.java
index 4a25f04bba3e6e..895636aa9c82c4 100644
--- a/integration-tests/scala/src/test/java/io/quarkus/scala/maven/it/ScalaCreateMavenProjectIT.java
+++ b/integration-tests/scala/src/test/java/io/quarkus/scala/maven/it/ScalaCreateMavenProjectIT.java
@@ -84,6 +84,7 @@ private InvocationResult setup(Properties params)
         }
         params.setProperty("platformArtifactId", getBomArtifactId());
         params.setProperty("platformVersion", getQuarkusCoreVersion());
+        enableDevToolsTestConfig(params);
 
         InvocationRequest request = new DefaultInvocationRequest();
         request.setBatchMode(true);
diff --git a/test-framework/devtools/pom.xml b/test-framework/devtools/pom.xml
new file mode 100644
index 00000000000000..a8edb0e9a338e0
--- /dev/null
+++ b/test-framework/devtools/pom.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>io.quarkus</groupId>
+        <artifactId>quarkus-test-framework</artifactId>
+        <version>999-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>quarkus-test-devtools</artifactId>
+    <name>Quarkus - Test Framework - Dev Tools</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-devtools-registry-client</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/test-framework/devtools/src/main/java/io/quarkus/devtools/test/DevToolsRegistryTestHelper.java b/test-framework/devtools/src/main/java/io/quarkus/devtools/test/DevToolsRegistryTestHelper.java
new file mode 100644
index 00000000000000..e61b6203ac4e73
--- /dev/null
+++ b/test-framework/devtools/src/main/java/io/quarkus/devtools/test/DevToolsRegistryTestHelper.java
@@ -0,0 +1,139 @@
+package io.quarkus.devtools.test;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.Properties;
+
+import io.quarkus.maven.ArtifactCoords;
+import io.quarkus.registry.Constants;
+import io.quarkus.registry.catalog.json.JsonCatalogMapperHelper;
+import io.quarkus.registry.catalog.json.JsonPlatform;
+import io.quarkus.registry.catalog.json.JsonPlatformCatalog;
+import io.quarkus.registry.config.RegistriesConfigLocator;
+import io.quarkus.registry.config.json.JsonRegistriesConfig;
+import io.quarkus.registry.config.json.JsonRegistryConfig;
+import io.quarkus.registry.config.json.JsonRegistryMavenConfig;
+import io.quarkus.registry.config.json.JsonRegistryMavenRepoConfig;
+import io.quarkus.registry.config.json.JsonRegistryNonPlatformExtensionsConfig;
+import io.quarkus.registry.config.json.JsonRegistryPlatformsConfig;
+import io.quarkus.registry.config.json.RegistriesConfigMapperHelper;
+
+public class DevToolsRegistryTestHelper {
+
+    public static void enableDevToolsTestConfig() {
+        enableDevToolsTestConfig(System.getProperties());
+    }
+
+    public static void enableDevToolsTestConfig(Properties properties) {
+        enableDevToolsTestConfig(Paths.get("").normalize().toAbsolutePath().resolve("target"),
+                properties);
+    }
+
+    public static void enableDevToolsTestConfig(Path outputDir, Properties properties) {
+        final Path toolsConfigPath = outputDir.resolve(RegistriesConfigLocator.CONFIG_RELATIVE_PATH);
+        final Path registryRepoPath = outputDir.resolve("test-registry-repo");
+        final Path groupIdDir = registryRepoPath.resolve("io/quarkus/registry/test");
+
+        generateToolsConfig(toolsConfigPath, registryRepoPath);
+        generateRegistryDescriptor(groupIdDir);
+        generatePlatformCatalog(groupIdDir);
+
+        properties.setProperty("io.quarkus.maven.secondary-local-repo", registryRepoPath.toString());
+        properties.setProperty(RegistriesConfigLocator.CONFIG_FILE_PATH_PROPERTY, toolsConfigPath.toString());
+    }
+
+    private static void generatePlatformCatalog(final Path groupIdDir) {
+        final String projectVersion = System.getProperty("project.version");
+        if (projectVersion == null) {
+            throw new IllegalStateException("System property project.version isn't set");
+        }
+        final Path platformsPath = groupIdDir.resolve(Constants.DEFAULT_REGISTRY_PLATFORMS_CATALOG_ARTIFACT_ID)
+                .resolve(Constants.DEFAULT_REGISTRY_ARTIFACT_VERSION)
+                .resolve(Constants.DEFAULT_REGISTRY_PLATFORMS_CATALOG_ARTIFACT_ID + "-"
+                        + Constants.DEFAULT_REGISTRY_ARTIFACT_VERSION + ".json");
+        final Path versionedPlatformsPath = groupIdDir.resolve(Constants.DEFAULT_REGISTRY_PLATFORMS_CATALOG_ARTIFACT_ID)
+                .resolve(Constants.DEFAULT_REGISTRY_ARTIFACT_VERSION)
+                .resolve(Constants.DEFAULT_REGISTRY_PLATFORMS_CATALOG_ARTIFACT_ID + "-"
+                        + Constants.DEFAULT_REGISTRY_ARTIFACT_VERSION + "-" + projectVersion + ".json");
+        if (Files.exists(platformsPath) && Files.exists(versionedPlatformsPath)) {
+            return;
+        }
+        final ArtifactCoords bom = new ArtifactCoords("io.quarkus", "quarkus-bom", null, "pom", projectVersion);
+        final JsonPlatformCatalog platforms = new JsonPlatformCatalog();
+        platforms.setDefaultPlatform(bom);
+        final JsonPlatform platform = new JsonPlatform();
+        platforms.addPlatform(platform);
+        platform.setBom(bom);
+        platform.setQuarkusCoreVersion(projectVersion);
+        try {
+            JsonCatalogMapperHelper.serialize(platforms, platformsPath);
+            Files.copy(platformsPath, versionedPlatformsPath, StandardCopyOption.REPLACE_EXISTING);
+        } catch (IOException e) {
+            throw new IllegalStateException("Failed to persist registry platforms config", e);
+        }
+    }
+
+    private static void generateRegistryDescriptor(Path repoGroupIdDir) {
+        final Path descriptorPath = repoGroupIdDir.resolve(Constants.DEFAULT_REGISTRY_DESCRIPTOR_ARTIFACT_ID)
+                .resolve(Constants.DEFAULT_REGISTRY_ARTIFACT_VERSION).resolve(Constants.DEFAULT_REGISTRY_DESCRIPTOR_ARTIFACT_ID
+                        + "-" + Constants.DEFAULT_REGISTRY_ARTIFACT_VERSION + ".json");
+        if (Files.exists(descriptorPath)) {
+            return;
+        }
+        final JsonRegistryConfig descriptor = new JsonRegistryConfig();
+        final JsonRegistryPlatformsConfig platformsConfig = new JsonRegistryPlatformsConfig();
+        descriptor.setPlatforms(platformsConfig);
+        platformsConfig.setArtifact(
+                new ArtifactCoords("io.quarkus.registry.test", Constants.DEFAULT_REGISTRY_PLATFORMS_CATALOG_ARTIFACT_ID, null,
+                        "json", Constants.DEFAULT_REGISTRY_ARTIFACT_VERSION));
+        final JsonRegistryNonPlatformExtensionsConfig nonPlatformsConfig = new JsonRegistryNonPlatformExtensionsConfig();
+        descriptor.setNonPlatformExtensions(nonPlatformsConfig);
+        nonPlatformsConfig.setDisabled(true);
+        try {
+            RegistriesConfigMapperHelper.serialize(descriptor, descriptorPath);
+        } catch (IOException e) {
+            throw new IllegalStateException("Failed to persist the registry descriptor", e);
+        }
+    }
+
+    private static void generateToolsConfig(Path toolsConfigPath, Path registryRepoPath) {
+        if (Files.exists(toolsConfigPath)) {
+            return;
+        }
+        final JsonRegistryConfig registryConfig = new JsonRegistryConfig();
+        registryConfig.setId("test.registry.quarkus.io");
+        final JsonRegistryMavenConfig mavenConfig = new JsonRegistryMavenConfig();
+        registryConfig.setMaven(mavenConfig);
+        final JsonRegistryMavenRepoConfig repoConfig = new JsonRegistryMavenRepoConfig();
+        mavenConfig.setRepository(repoConfig);
+        try {
+            repoConfig.setUrl(registryRepoPath.toUri().toURL().toString());
+        } catch (MalformedURLException e) {
+            throw new IllegalStateException("Failed to translate a path to url", e);
+        }
+
+        final JsonRegistriesConfig toolsConfig = new JsonRegistriesConfig();
+        toolsConfig.addRegistry(registryConfig);
+        toolsConfig.setDebug(true);
+
+        try {
+            RegistriesConfigMapperHelper.serialize(toolsConfig,
+                    toolsConfigPath);
+        } catch (IOException e) {
+            throw new IllegalStateException("Failed to persist the tools config", e);
+        }
+    }
+
+    public static void disableDevToolsTestConfig() {
+        disableDevToolsTestConfig(System.getProperties());
+    }
+
+    public static void disableDevToolsTestConfig(Properties properties) {
+        properties.remove("io.quarkus.maven.secondary-local-repo");
+        properties.remove(RegistriesConfigLocator.CONFIG_FILE_PATH_PROPERTY);
+    }
+}
diff --git a/test-framework/maven/pom.xml b/test-framework/maven/pom.xml
index ade8ac27fa23fb..43df88db9cbd96 100644
--- a/test-framework/maven/pom.xml
+++ b/test-framework/maven/pom.xml
@@ -16,15 +16,15 @@
     <dependencies>
         <dependency>
             <groupId>io.quarkus</groupId>
-            <artifactId>quarkus-devmode-test-utils</artifactId>
+            <artifactId>quarkus-test-devtools</artifactId>
         </dependency>
         <dependency>
             <groupId>io.quarkus</groupId>
-            <artifactId>quarkus-devtools-common</artifactId>
+            <artifactId>quarkus-devmode-test-utils</artifactId>
         </dependency>
         <dependency>
             <groupId>io.quarkus</groupId>
-            <artifactId>quarkus-platform-descriptor-resolver-json</artifactId>
+            <artifactId>quarkus-devtools-common</artifactId>
         </dependency>
         <dependency>
             <groupId>org.apache.maven.shared</groupId>
diff --git a/test-framework/maven/src/main/java/io/quarkus/maven/it/MojoTestBase.java b/test-framework/maven/src/main/java/io/quarkus/maven/it/MojoTestBase.java
index f36d1604a4dfbe..de71d2d74cf616 100644
--- a/test-framework/maven/src/main/java/io/quarkus/maven/it/MojoTestBase.java
+++ b/test-framework/maven/src/main/java/io/quarkus/maven/it/MojoTestBase.java
@@ -12,6 +12,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Properties;
 import java.util.function.Predicate;
 import java.util.logging.Level;
 import java.util.logging.Logger;
@@ -21,9 +22,11 @@
 import org.apache.maven.model.Model;
 import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
 import org.apache.maven.shared.invoker.DefaultInvoker;
+import org.apache.maven.shared.invoker.InvocationRequest;
 import org.apache.maven.shared.invoker.Invoker;
 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
 
+import io.quarkus.devtools.test.DevToolsRegistryTestHelper;
 import io.quarkus.test.devmode.util.DevModeTestUtils;
 
 public class MojoTestBase {
@@ -49,6 +52,7 @@ public static File initEmptyProject(String name) {
             }
         }
         boolean mkdirs = tc.mkdirs();
+
         Logger.getLogger(MojoTestBase.class.getName())
                 .log(Level.FINE, "test-classes created? %s", mkdirs);
         return tc;
@@ -140,4 +144,20 @@ public static List<File> getFilesEndingWith(File dir, String suffix) {
         return files != null ? Arrays.asList(files) : Collections.emptyList();
     }
 
+    public static void enableDevToolsTestConfig(InvocationRequest request) {
+        Properties properties = request.getProperties();
+        if (properties == null) {
+            properties = new Properties();
+            request.setProperties(properties);
+        }
+        enableDevToolsTestConfig(properties);
+    }
+
+    public static void enableDevToolsTestConfig(Properties properties) {
+        DevToolsRegistryTestHelper.enableDevToolsTestConfig(properties);
+    }
+
+    public static void disableDevToolsTestConfig(Properties properties) {
+        DevToolsRegistryTestHelper.disableDevToolsTestConfig(properties);
+    }
 }
diff --git a/test-framework/maven/src/main/java/io/quarkus/maven/it/QuarkusPlatformAwareMojoTestBase.java b/test-framework/maven/src/main/java/io/quarkus/maven/it/QuarkusPlatformAwareMojoTestBase.java
index 9b401ae8dc0365..b0120149d94e7a 100644
--- a/test-framework/maven/src/main/java/io/quarkus/maven/it/QuarkusPlatformAwareMojoTestBase.java
+++ b/test-framework/maven/src/main/java/io/quarkus/maven/it/QuarkusPlatformAwareMojoTestBase.java
@@ -2,25 +2,32 @@
 
 import java.util.Properties;
 
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
-import io.quarkus.platform.descriptor.resolver.json.QuarkusJsonPlatformDescriptorResolver;
+import io.quarkus.devtools.project.QuarkusProjectHelper;
 import io.quarkus.platform.tools.ToolsUtils;
+import io.quarkus.registry.RegistryResolutionException;
+import io.quarkus.registry.catalog.ExtensionCatalog;
 
 public class QuarkusPlatformAwareMojoTestBase extends MojoTestBase {
 
-    private QuarkusPlatformDescriptor platformDescr;
+    private ExtensionCatalog catalog;
     private Properties quarkusProps;
 
-    protected QuarkusPlatformDescriptor getPlatformDescriptor() {
-        return platformDescr == null ? platformDescr = QuarkusJsonPlatformDescriptorResolver.newInstance().resolveBundled()
-                : platformDescr;
+    private ExtensionCatalog getPlatformDescriptor() {
+        if (catalog == null) {
+            enableDevToolsTestConfig(System.getProperties());
+            try {
+                catalog = QuarkusProjectHelper.getCatalogResolver().resolveExtensionCatalog();
+            } catch (RegistryResolutionException e) {
+                throw new RuntimeException("Failed to resolve the extensions catalog", e);
+            } finally {
+                disableDevToolsTestConfig(System.getProperties());
+            }
+        }
+        return catalog;
     }
 
     private Properties getQuarkusProperties() {
-        if (quarkusProps == null) {
-            quarkusProps = ToolsUtils.readQuarkusProperties(getPlatformDescriptor());
-        }
-        return quarkusProps;
+        return quarkusProps == null ? quarkusProps = ToolsUtils.readQuarkusProperties(getPlatformDescriptor()) : quarkusProps;
     }
 
     protected String getMavenPluginGroupId() {
@@ -40,14 +47,14 @@ protected String getQuarkusCoreVersion() {
     }
 
     protected String getBomGroupId() {
-        return getPlatformDescriptor().getBomGroupId();
+        return getPlatformDescriptor().getBom().getGroupId();
     }
 
     protected String getBomArtifactId() {
-        return getPlatformDescriptor().getBomArtifactId();
+        return getPlatformDescriptor().getBom().getArtifactId();
     }
 
     protected String getBomVersion() {
-        return getPlatformDescriptor().getBomVersion();
+        return getPlatformDescriptor().getBom().getVersion();
     }
 }
diff --git a/test-framework/maven/src/main/java/io/quarkus/maven/it/assertions/SetupVerifier.java b/test-framework/maven/src/main/java/io/quarkus/maven/it/assertions/SetupVerifier.java
index 6f51cc0a7a731f..70f38ff685a97d 100644
--- a/test-framework/maven/src/main/java/io/quarkus/maven/it/assertions/SetupVerifier.java
+++ b/test-framework/maven/src/main/java/io/quarkus/maven/it/assertions/SetupVerifier.java
@@ -17,10 +17,11 @@
 import org.apache.maven.project.MavenProject;
 import org.codehaus.plexus.util.xml.Xpp3Dom;
 
+import io.quarkus.devtools.project.QuarkusProjectHelper;
+import io.quarkus.devtools.test.DevToolsRegistryTestHelper;
 import io.quarkus.maven.utilities.MojoUtils;
-import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor;
-import io.quarkus.platform.descriptor.resolver.json.QuarkusJsonPlatformDescriptorResolver;
 import io.quarkus.platform.tools.ToolsConstants;
+import io.quarkus.registry.catalog.ExtensionCatalog;
 
 public class SetupVerifier {
 
@@ -111,11 +112,16 @@ public static void verifySetupWithVersion(File pomFile) throws Exception {
         Properties projectProps = project.getProperties();
         assertNotNull(projectProps);
         assertFalse(projectProps.isEmpty());
-        final String quarkusVersion = getPlatformDescriptor().getQuarkusVersion();
+        final String quarkusVersion = getPlatformDescriptor().getQuarkusCoreVersion();
         assertEquals(quarkusVersion, projectProps.getProperty(MojoUtils.TEMPLATE_PROPERTY_QUARKUS_PLUGIN_VERSION_NAME));
     }
 
-    private static QuarkusPlatformDescriptor getPlatformDescriptor() {
-        return QuarkusJsonPlatformDescriptorResolver.newInstance().resolveBundled();
+    private static ExtensionCatalog getPlatformDescriptor() throws Exception {
+        DevToolsRegistryTestHelper.enableDevToolsTestConfig();
+        try {
+            return QuarkusProjectHelper.getCatalogResolver().resolveExtensionCatalog();
+        } finally {
+            DevToolsRegistryTestHelper.disableDevToolsTestConfig();
+        }
     }
 }
diff --git a/test-framework/pom.xml b/test-framework/pom.xml
index 0321e590d9e206..65c5659605d553 100644
--- a/test-framework/pom.xml
+++ b/test-framework/pom.xml
@@ -31,6 +31,7 @@
         <module>amazon-lambda</module>
         <module>arquillian</module>
         <module>devmode-test-utils</module>
+        <module>devtools</module>
         <module>maven</module>
         <module>vault</module>
         <module>ldap</module>
diff --git a/update-extension-dependencies.sh b/update-extension-dependencies.sh
index 97e1c81701b55d..74bc73c50e7dfc 100755
--- a/update-extension-dependencies.sh
+++ b/update-extension-dependencies.sh
@@ -32,7 +32,7 @@ echo ''
 # get all "artifact-id" values from the generated json file
 # pipefail is switched off briefly so that a better error can be logged when nothing is found
 set +o pipefail
-ARTIFACT_IDS=`grep -Po '(?<="artifact-id": ")(?!quarkus-bom)[^"]+' devtools/bom-descriptor-json/target/*.json | sort`
+ARTIFACT_IDS=`grep '"artifact"' devtools/bom-descriptor-json/target/*.json | grep -Po '(?<=io.quarkus:)(?!quarkus-bom)[^:]+' | sort`
 set -o pipefail
 if [ -z "${ARTIFACT_IDS}" ]
 then