Repository: quarkusio/quarkus
Issue: Hot reload does not build multi module project in correct order

### Describe the bug

I have a multi module project, where I get exceptions on hot reload.
If I only change one module at a time (and hot reload), no exception is thrown.

If i change multiple modules at the same time however, I get the exception from below.

In a related bug, #23164, only one additionalModule existed other than the applicationArchive. #24187 was the fix for that situation.

However, in my real world applications, I have a few modules more.. about 20 of them iirc.

My project structure looks like this:
parent
- service (business logic)
- rest (web stuff, has dependency on service)
- backend (module containing the quarkus plugin, has dependency on rest)

Doing a change in service (e.g. adding a method), using the new method in rest, and then reloading the changes in these 2 modules causes the exception.

### Expected behavior

Hot reload works flawlessly even when changing multiple modules at once.

### Actual behavior

```
Listening for transport dt_socket at address: 5005
__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \\/ / / / _ | / _ \\/ //_/ / / / __/
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\\ \\
--\\___\\_\\____/_/ |_/_/|_/_/|_|\\____/___/
2022-03-17 20:17:21,863 INFO  [io.quarkus] (Quarkus Main Thread) backend 1.0.0-SNAPSHOT on JVM (powered by Quarkus 999-SNAPSHOT) started in 1.481s. Listening on: http://localhost:8080

2022-03-17 20:17:21,875 INFO  [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2022-03-17 20:17:21,876 INFO  [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, resteasy-reactive, smallrye-context-propagation, vertx]                                                                                                                            
                                                                                                                                       
--                                                                                                                                     
Compilation Failed:                                                                                                                    
C:\\Users\\Martin\\IdeaProjects\\ap-test\\rest\\src\\main\\java\\org\\acme\\rest\\ExampleResource.java:24: error: method greet in class org.acme.service.ExampleService cannot be applied to given types;                                                                                 
        return service.greet("Quarkus");                                                                                               
                      ^                                                                                                                
  required: java.lang.String,java.lang.String                                                                                          
  found:    java.lang.String                                                                                                           
  reason: actual and formal argument lists differ in length     
```

### How to Reproduce?

Download the reproducer: 
[ap-test.zip](https://github.com/quarkusio/quarkus/files/8291575/ap-test.zip)


1. mvn clean compile quarkus:dev
2. open dev ui - this works
3. In org.acme.service.ExampleService, adder another parameter to greet()
4. In org.acme.rest.ExampleResource, change the resource to call the changed greet method.
5. reload the dev ui
6. compilation error from above happens
7. Stop quarkus
8. mvn clean compile quarkus:dev
9. open dev ui - this works. The source itself is compileable by maven, but not by quarkus.

### Output of `uname -a` or `ver`

Microsoft Windows [Version 10.0.22000.556]

### Output of `java -version`

openjdk 17.0.1 2021-10-19 OpenJDK Runtime Environment Temurin-17.0.1+12 (build 17.0.1+12) OpenJDK 64-Bit Server VM Temurin-17.0.1+12 (build 17.0.1+12, mixed mode, sharing)

### GraalVM version (if different from Java)

_No response_

### Quarkus version or git rev

2.7.5.Final

### Build tool (ie. output of `mvnw --version` or `gradlew --version`)

Apache Maven 3.8.3 (ff8e977a158738155dc465c6a97ffaf31982d739) Maven home: C:\\tools\\java\\maven Java version: 17.0.1, vendor: Eclipse Adoptium, runtime: C:\\tools\\java\\17-temurin Default locale: de_DE, platform encoding: Cp1252 OS name: "windows 10", version: "10.0", arch: "amd64", family: "windows"

### Additional information

_No response_

Changed files:
core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java
independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/devmode/DependenciesFilter.java
independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/LocalProject.java
independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/ArtifactCoords.java
independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/CuratedApplication.java
integration-tests/maven/src/test/java/io/quarkus/maven/it/DevMojoIT.java

diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java
index bcf1759c005..844727aad0f 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java
@@ -42,7 +42,6 @@
 import io.quarkus.deployment.pkg.builditem.ProcessInheritIODisabled;
 import io.quarkus.deployment.pkg.builditem.ProcessInheritIODisabledBuildItem;
 import io.quarkus.deployment.steps.LocaleProcessor;
-import io.quarkus.maven.dependency.GACTV;
 import io.quarkus.maven.dependency.ResolvedDependency;
 import io.quarkus.runtime.LocalesBuildTimeConfig;
 
@@ -313,7 +312,7 @@ private void copyJarSourcesToLib(OutputTargetBuildItem outputTargetBuildItem,
         }
 
         for (ResolvedDependency depArtifact : curateOutcomeBuildItem.getApplicationModel().getRuntimeDependencies()) {
-            if (depArtifact.getType().equals(GACTV.TYPE_JAR)) {
+            if (depArtifact.isJar()) {
                 for (Path resolvedDep : depArtifact.getResolvedPaths()) {
                     if (!Files.isDirectory(resolvedDep)) {
                         // Do we need to handle transformed classes?
diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/ArtifactCoords.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/ArtifactCoords.java
index 58ce084b729..1ba8bd4f7ed 100644
--- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/ArtifactCoords.java
+++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/ArtifactCoords.java
@@ -18,6 +18,10 @@ public interface ArtifactCoords {
 
     ArtifactKey getKey();
 
+    default boolean isJar() {
+        return TYPE_JAR.equals(getType());
+    }
+
     default String toGACTVString() {
         return getGroupId() + ":" + getArtifactId() + ":" + getClassifier() + ":" + getType() + ":" + getVersion();
     }
@@ -28,7 +32,7 @@ default String toCompactCoords() {
         if (!getClassifier().isEmpty()) {
             b.append(getClassifier()).append(':');
         }
-        if (!TYPE_JAR.equals(getType())) {
+        if (!isJar()) {
             b.append(getType()).append(':');
         }
         b.append(getVersion());
diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/CuratedApplication.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/CuratedApplication.java
index 129541af447..d6b826942f4 100644
--- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/CuratedApplication.java
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/CuratedApplication.java
@@ -8,7 +8,6 @@
 import io.quarkus.bootstrap.model.AppModel;
 import io.quarkus.bootstrap.model.ApplicationModel;
 import io.quarkus.bootstrap.util.BootstrapUtils;
-import io.quarkus.maven.dependency.ArtifactCoords;
 import io.quarkus.maven.dependency.ArtifactKey;
 import io.quarkus.maven.dependency.ResolvedDependency;
 import io.quarkus.paths.OpenPathTree;
@@ -150,7 +149,7 @@ private Object runInCl(String consumerName, Map<String, Object> params, QuarkusC
     }
 
     private synchronized void processCpElement(ResolvedDependency artifact, Consumer<ClassPathElement> consumer) {
-        if (!artifact.getType().equals(ArtifactCoords.TYPE_JAR)) {
+        if (!artifact.isJar()) {
             //avoid the need for this sort of check in multiple places
             consumer.accept(ClassPathElement.EMPTY);
             return;
@@ -337,8 +336,7 @@ public QuarkusClassLoader createDeploymentClassLoader() {
             }
         }
         for (ResolvedDependency dependency : appModel.getDependencies()) {
-            if (dependency.isRuntimeCp() &&
-                    dependency.getType().equals(ArtifactCoords.TYPE_JAR) &&
+            if (dependency.isRuntimeCp() && dependency.isJar() &&
                     (dependency.isReloadable() && appModel.getReloadableWorkspaceDependencies().contains(dependency.getKey()) ||
                             configuredClassLoading.reloadableArtifacts.contains(dependency.getKey()))) {
                 processCpElement(dependency, element -> addCpElement(builder, dependency, element));
@@ -375,8 +373,7 @@ public QuarkusClassLoader createRuntimeClassLoader(ClassLoader base, Map<String,
             }
         }
         for (ResolvedDependency dependency : appModel.getDependencies()) {
-            if (dependency.isRuntimeCp() &&
-                    dependency.getType().equals(ArtifactCoords.TYPE_JAR) &&
+            if (dependency.isRuntimeCp() && dependency.isJar() &&
                     (dependency.isReloadable() && appModel.getReloadableWorkspaceDependencies().contains(dependency.getKey()) ||
                             configuredClassLoading.reloadableArtifacts.contains(dependency.getKey()))) {
                 processCpElement(dependency, element -> addCpElement(builder, dependency, element));
diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/devmode/DependenciesFilter.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/devmode/DependenciesFilter.java
index 388c6db0fda..622d4a5ed3a 100644
--- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/devmode/DependenciesFilter.java
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/devmode/DependenciesFilter.java
@@ -1,10 +1,13 @@
 package io.quarkus.bootstrap.devmode;
 
 import io.quarkus.bootstrap.model.ApplicationModel;
-import io.quarkus.maven.dependency.GACTV;
+import io.quarkus.maven.dependency.ArtifactKey;
+import io.quarkus.maven.dependency.Dependency;
 import io.quarkus.maven.dependency.ResolvedDependency;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import org.jboss.logging.Logger;
 
 public class DependenciesFilter {
@@ -12,30 +15,86 @@ public class DependenciesFilter {
     private static final Logger log = Logger.getLogger(DependenciesFilter.class);
 
     public static List<ResolvedDependency> getReloadableModules(ApplicationModel appModel) {
-        final List<ResolvedDependency> reloadable = new ArrayList<>();
-        if (appModel.getApplicationModule() != null) {
-            reloadable.add(appModel.getAppArtifact());
-        }
+        final Map<ArtifactKey, WorkspaceDependencies> modules = new HashMap<>();
         appModel.getDependencies().forEach(d -> {
             if (d.isReloadable()) {
-                reloadable.add(d);
+                modules.put(d.getKey(), new WorkspaceDependencies(d));
             } else if (d.isWorkspaceModule()) {
                 //if this project also contains Quarkus extensions we do no want to include these in the discovery
                 //a bit of an edge case, but if you try and include a sample project with your extension you will
                 //run into problems without this
                 final StringBuilder msg = new StringBuilder();
-                msg.append("Local Quarkus extension dependency ");
-                msg.append(d.getGroupId()).append(":").append(d.getArtifactId()).append(":");
-                if (!d.getClassifier().isEmpty()) {
-                    msg.append(d.getClassifier()).append(":");
-                }
-                if (!GACTV.TYPE_JAR.equals(d.getType())) {
-                    msg.append(d.getType()).append(":");
-                }
-                msg.append(d.getVersion()).append(" will not be hot-reloadable");
+                msg.append("Local Quarkus extension dependency ").append(d.toCompactCoords())
+                        .append(" will not be hot-reloadable");
                 log.warn(msg.toString());
             }
         });
-        return reloadable;
+        if (modules.isEmpty()) {
+            return appModel.getApplicationModule() == null ? List.of() : List.of(appModel.getAppArtifact());
+        }
+
+        if (appModel.getApplicationModule() != null) {
+            modules.put(appModel.getAppArtifact().getKey(), new WorkspaceDependencies(appModel.getAppArtifact()));
+        }
+
+        // Here we are sorting the reloadable dependencies according to their interdependencies to make sure
+        // they are compiled in the correct order
+
+        for (WorkspaceDependencies ad : modules.values()) {
+            for (Dependency dd : ad.artifact.getWorkspaceModule().getDirectDependencies()) {
+                final WorkspaceDependencies dep = modules.get(dd.getKey());
+                if (dep != null) {
+                    ad.addDependency(dep);
+                }
+            }
+        }
+
+        final List<ResolvedDependency> sorted = new ArrayList<>();
+        int toBeSorted;
+        do {
+            toBeSorted = 0;
+            for (WorkspaceDependencies ad : modules.values()) {
+                if (ad.sorted) {
+                    continue;
+                }
+                if (ad.hasNotSortedDependencies()) {
+                    ++toBeSorted;
+                    continue;
+                }
+                ad.sorted = true;
+                sorted.add(ad.artifact);
+            }
+        } while (toBeSorted > 0);
+
+        return sorted;
+    }
+
+    private static class WorkspaceDependencies {
+        final ResolvedDependency artifact;
+        List<WorkspaceDependencies> deps;
+        boolean sorted;
+
+        WorkspaceDependencies(ResolvedDependency artifact) {
+            this.artifact = artifact;
+        }
+
+        boolean hasNotSortedDependencies() {
+            if (deps == null) {
+                return false;
+            }
+            for (WorkspaceDependencies d : deps) {
+                if (!d.sorted) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        void addDependency(WorkspaceDependencies dep) {
+            if (deps == null) {
+                deps = new ArrayList<>();
+            }
+            deps.add(dep);
+        }
     }
 }
diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/LocalProject.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/LocalProject.java
index 25cd99bc9c5..e3b46676c41 100644
--- a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/LocalProject.java
+++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/LocalProject.java
@@ -374,7 +374,8 @@ public WorkspaceModule toWorkspaceModule() {
         moduleBuilder.setDependencyConstraints(getRawModel().getDependencyManagement() == null ? Collections.emptyList()
                 : toArtifactDependencies(getRawModel().getDependencyManagement().getDependencies()));
 
-        moduleBuilder.setDependencies(toArtifactDependencies(getRawModel().getDependencies()));
+        final Model model = modelBuildingResult == null ? rawModel : modelBuildingResult.getEffectiveModel();
+        moduleBuilder.setDependencies(toArtifactDependencies(model.getDependencies()));
 
         return this.module = moduleBuilder.build();
     }
diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/DevMojoIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/DevMojoIT.java
index b1c1c028676..f8c7e7f4b2d 100644
--- a/integration-tests/maven/src/test/java/io/quarkus/maven/it/DevMojoIT.java
+++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/DevMojoIT.java
@@ -1194,6 +1194,39 @@ public void testThatDependencyInParentIsEvaluated() throws IOException, MavenInv
         runAndCheck();
     }
 
+    @Test
+    public void testModuleCompileOrder() throws IOException, MavenInvocationException {
+        testDir = initProject("projects/multimodule-parent-dep", "projects/multimodule-compile-order");
+        runAndCheck("-Dquarkus.bootstrap.effective-model-builder");
+
+        assertThat(DevModeTestUtils.getHttpResponse("/app/hello/")).isEqualTo("hello");
+
+        // modify classes in all the modules and make sure they are compiled in a correct order
+        File resource = new File(testDir, "level0/src/main/java/org/acme/level0/Level0Service.java");
+        filter(resource, Collections.singletonMap("getGreeting()", "getGreeting(String name)"));
+        filter(resource, Collections.singletonMap("return greeting;", "return greeting + \\" \\" + name;"));
+
+        resource = new File(testDir, "level1/src/main/java/org/acme/level1/Level1Service.java");
+        filter(resource, Collections.singletonMap("getGreetingFromLevel0()", "getGreetingFromLevel0(String name)"));
+        filter(resource, Collections.singletonMap("level0Service.getGreeting()", "level0Service.getGreeting(name)"));
+
+        resource = new File(testDir, "level2/submodule/src/main/java/org/acme/level2/submodule/Level2Service.java");
+        filter(resource, Collections.singletonMap("getGreetingFromLevel1()", "getGreetingFromLevel1(String name)"));
+        filter(resource,
+                Collections.singletonMap("level1Service.getGreetingFromLevel0()", "level1Service.getGreetingFromLevel0(name)"));
+
+        resource = new File(testDir, "runner/src/main/java/org/acme/rest/HelloResource.java");
+        filter(resource, Collections.singletonMap("level0Service.getGreeting()",
+                "level0Service.getGreeting(\\"world\\")"));
+        filter(resource, Collections.singletonMap("level2Service.getGreetingFromLevel1()",
+                "level2Service.getGreetingFromLevel1(\\"world\\")"));
+
+        await()
+                .pollDelay(300, TimeUnit.MILLISECONDS)
+                .atMost(1, TimeUnit.MINUTES)
+                .until(() -> DevModeTestUtils.getHttpResponse("/app/hello").contains("hello world"));
+    }
+
     @Test
     public void testThatGenerateCodeGoalIsNotTriggeredIfNotConfigured() throws IOException, MavenInvocationException {
         testDir = initProject("projects/classic-no-generate");