diff --git a/.github/workflows/ci-actions-incremental.yml b/.github/workflows/ci-actions-incremental.yml
index 52b1a2b0cdc40..89765207d8c9b 100644
--- a/.github/workflows/ci-actions-incremental.yml
+++ b/.github/workflows/ci-actions-incremental.yml
@@ -430,7 +430,7 @@ jobs:
       MAVEN_OPTS: -Xmx1g
     # Skip main in forks
     if: "needs.calculate-test-jobs.outputs.run_gradle == 'true' && (github.repository == 'quarkusio/quarkus' || !endsWith(github.ref, '/main'))"
-    timeout-minutes: 80
+    timeout-minutes: 120
     strategy:
       fail-fast: false
       matrix:
diff --git a/build-parent/pom.xml b/build-parent/pom.xml
index 6c61cac1a87be..89b2d95825de8 100644
--- a/build-parent/pom.xml
+++ b/build-parent/pom.xml
@@ -65,7 +65,7 @@
         <!-- These 2 properties are used by CreateProjectMojo to add the Maven Wrapper -->
         <proposed-maven-version>3.8.8</proposed-maven-version>
         <maven-wrapper.version>3.2.0</maven-wrapper.version>
-        <gradle-wrapper.version>8.0.2</gradle-wrapper.version>
+        <gradle-wrapper.version>8.1</gradle-wrapper.version>
         <quarkus-gradle-plugin.version>${project.version}</quarkus-gradle-plugin.version>
         <quarkus-maven-plugin.version>${project.version}</quarkus-maven-plugin.version>
         <maven-plugin-plugin.version>3.8.1</maven-plugin-plugin.version>
diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/QuarkusPlugin.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/QuarkusPlugin.java
index 55348a663780c..1e6faad216359 100644
--- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/QuarkusPlugin.java
+++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/QuarkusPlugin.java
@@ -233,9 +233,8 @@ public boolean isSatisfiedBy(Task t) {
 
         tasks.register(BUILD_NATIVE_TASK_NAME, DefaultTask.class, task -> {
             task.finalizedBy(quarkusBuild);
-            task.doFirst(t -> project.getLogger()
+            task.doFirst(t -> t.getLogger()
                     .warn("The 'buildNative' task has been deprecated in favor of 'build -Dquarkus.package.type=native'"));
-
         });
 
         configureBuildNativeTask(project);
diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusTask.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusTask.java
index a67a3a8a40052..1fc804e23f468 100644
--- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusTask.java
+++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusTask.java
@@ -62,6 +62,11 @@ private void configureProcessWorkerSpec(ProcessWorkerSpec processWorkerSpec, Map
 
         customizations.forEach(a -> a.execute(forkOptions));
 
+        String quarkusWorkerMaxHeap = System.getProperty("quarkus.gradle-worker.max-heap");
+        if (quarkusWorkerMaxHeap != null && forkOptions.getAllJvmArgs().stream().noneMatch(arg -> arg.startsWith("-Xmx"))) {
+            forkOptions.jvmArgs("-Xmx" + quarkusWorkerMaxHeap);
+        }
+
         // Pass all environment variables
         forkOptions.environment(System.getenv());
 
diff --git a/devtools/gradle/gradle/wrapper/gradle-wrapper.properties b/devtools/gradle/gradle/wrapper/gradle-wrapper.properties
index a2efda8857b1b..d5e7b2ac5e417 100644
--- a/devtools/gradle/gradle/wrapper/gradle-wrapper.properties
+++ b/devtools/gradle/gradle/wrapper/gradle-wrapper.properties
@@ -1,7 +1,8 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionSha256Sum=47a5bfed9ef814f90f8debcbbb315e8e7c654109acd224595ea39fca95c5d4da
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-all.zip
+# https://gradle.org/release-checksums/
+distributionSha256Sum=2cbafcd2c47a101cb2165f636b4677fac0b954949c9429c1c988da399defe6a9
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-all.zip
 networkTimeout=10000
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/independent-projects/bootstrap/pom.xml b/independent-projects/bootstrap/pom.xml
index df396f74b67b2..e1f3a0173fe29 100644
--- a/independent-projects/bootstrap/pom.xml
+++ b/independent-projects/bootstrap/pom.xml
@@ -77,7 +77,7 @@
         <plexus-utils.version>3.5.1</plexus-utils.version>
         <smallrye-common.version>2.1.0</smallrye-common.version>
         <smallrye-beanbag.version>1.1.0</smallrye-beanbag.version>
-        <gradle-tooling.version>8.0.2</gradle-tooling.version>
+        <gradle-tooling.version>8.1</gradle-tooling.version>
         <quarkus-fs-util.version>0.0.9</quarkus-fs-util.version>
         <org-crac.version>0.1.3</org-crac.version>
         <formatter-maven-plugin.version>2.22.0</formatter-maven-plugin.version>
diff --git a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/tooling/gradle-wrapper/codestart.yml b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/tooling/gradle-wrapper/codestart.yml
index 031e2b3791c9d..e67e0bfd333e5 100644
--- a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/tooling/gradle-wrapper/codestart.yml
+++ b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/tooling/gradle-wrapper/codestart.yml
@@ -6,7 +6,7 @@ language:
   base:
     data:
       gradle:
-        version: 8.0.2
+        version: 8.1
     shared-data:
       buildtool:
         cli: ./gradlew
diff --git a/independent-projects/tools/devtools-testing/src/main/resources/fake-catalog.json b/independent-projects/tools/devtools-testing/src/main/resources/fake-catalog.json
index a5f64b1b928e7..97e6b6045cc25 100644
--- a/independent-projects/tools/devtools-testing/src/main/resources/fake-catalog.json
+++ b/independent-projects/tools/devtools-testing/src/main/resources/fake-catalog.json
@@ -388,7 +388,7 @@
         "supported-maven-versions": "[3.6.2,)",
         "proposed-maven-version": "3.8.8",
         "maven-wrapper-version": "3.2.0",
-        "gradle-wrapper-version": "8.0.2"
+        "gradle-wrapper-version": "8.1"
       }
     },
     "codestarts-artifacts": [
diff --git a/independent-projects/tools/pom.xml b/independent-projects/tools/pom.xml
index 74c66ebe8ae7a..003456dfc2dec 100644
--- a/independent-projects/tools/pom.xml
+++ b/independent-projects/tools/pom.xml
@@ -42,7 +42,7 @@
         <!-- These properties are used by CreateProjectMojo to add the Maven Wrapper -->
         <proposed-maven-version>3.8.8</proposed-maven-version>
         <maven-wrapper.version>3.2.0</maven-wrapper.version>
-        <gradle-wrapper.version>8.0.2</gradle-wrapper.version>
+        <gradle-wrapper.version>8.1</gradle-wrapper.version>
 
         <!-- These properties are needed in order for them to be resolvable by the generated projects -->
         <!-- Quarkus uses jboss-parent and it comes with 3.8.1-jboss-1, we don't want that in the templates -->
diff --git a/integration-tests/gradle/gradle.properties b/integration-tests/gradle/gradle.properties
index e35f70a5669de..cfd6f9d25cf4f 100644
--- a/integration-tests/gradle/gradle.properties
+++ b/integration-tests/gradle/gradle.properties
@@ -1,4 +1,12 @@
 version=999-SNAPSHOT
 
 # Idle timeout in milliseconds
-org.gradle.daemon.idletimeout=10000
+systemProp.org.gradle.daemon.idletimeout=10000
+
+# Override Java default heap size calculation for the Quarkus Gradle plugin worker processes, important for CI
+systemProp.quarkus.gradle-worker.max-heap=256m
+
+# Use the miniumu Java heap sizes for a Gradle daemon, important for CI
+org.gradle.jvmargs=-Xms128m -Xmx256m \
+  -Dfile.encoding=UTF-8 \
+  -Duser.language=en -Duser.country=US -Duser.variant=
diff --git a/integration-tests/gradle/gradle/wrapper/gradle-wrapper.properties b/integration-tests/gradle/gradle/wrapper/gradle-wrapper.properties
index a2efda8857b1b..d5e7b2ac5e417 100644
--- a/integration-tests/gradle/gradle/wrapper/gradle-wrapper.properties
+++ b/integration-tests/gradle/gradle/wrapper/gradle-wrapper.properties
@@ -1,7 +1,8 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionSha256Sum=47a5bfed9ef814f90f8debcbbb315e8e7c654109acd224595ea39fca95c5d4da
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-all.zip
+# https://gradle.org/release-checksums/
+distributionSha256Sum=2cbafcd2c47a101cb2165f636b4677fac0b954949c9429c1c988da399defe6a9
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-all.zip
 networkTimeout=10000
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/integration-tests/gradle/pom.xml b/integration-tests/gradle/pom.xml
index 40d9246502ea7..c85a73c94a045 100644
--- a/integration-tests/gradle/pom.xml
+++ b/integration-tests/gradle/pom.xml
@@ -430,13 +430,13 @@
             </build>
         </profile>
         <profile>
-            <id>jdk19-workarounds</id>
+            <id>jdk20-workarounds</id>
             <activation>
-                <jdk>[19,)</jdk>
+                <jdk>[20,)</jdk>
             </activation>
             <properties>
                 <!-- auto-exclude tests that are known to fail on JDK 19 or above (tagged via @Tag("failsOnJDK19")) -->
-                <excludedGroups>failsOnJDK19</excludedGroups>
+                <excludedGroups>failsOnJDK20</excludedGroups>
             </properties>
         </profile>
     </profiles>
diff --git a/integration-tests/gradle/src/main/resources/test-resources-vs-main-resources/src/main/java/org/acme/Config.java b/integration-tests/gradle/src/main/resources/test-resources-vs-main-resources/src/main/java/org/acme/Config.java
index f92b0bd9b98a3..9e6b13f177070 100644
--- a/integration-tests/gradle/src/main/resources/test-resources-vs-main-resources/src/main/java/org/acme/Config.java
+++ b/integration-tests/gradle/src/main/resources/test-resources-vs-main-resources/src/main/java/org/acme/Config.java
@@ -1,8 +1,8 @@
 package org.acme;
 
-import io.quarkus.arc.config.ConfigProperties;
+import io.smallrye.config.ConfigMapping;
 
-@ConfigProperties(prefix = "example")
-public class Config {
-    public String message;
+@ConfigMapping(prefix = "example")
+public interface Config {
+    public String message();
 }
diff --git a/integration-tests/gradle/src/main/resources/test-resources-vs-main-resources/src/main/java/org/acme/ExampleResource.java b/integration-tests/gradle/src/main/resources/test-resources-vs-main-resources/src/main/java/org/acme/ExampleResource.java
index 35f468a85b354..a17aa6569ab7c 100644
--- a/integration-tests/gradle/src/main/resources/test-resources-vs-main-resources/src/main/java/org/acme/ExampleResource.java
+++ b/integration-tests/gradle/src/main/resources/test-resources-vs-main-resources/src/main/java/org/acme/ExampleResource.java
@@ -15,6 +15,6 @@ public class ExampleResource {
     @GET
     @Produces(MediaType.TEXT_PLAIN)
     public String hello() {
-        return config.message;
+        return config.message();
     }
 }
diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/BuildConfigurationTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/BuildConfigurationTest.java
index 67c522460b6e8..22022d77024fd 100644
--- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/BuildConfigurationTest.java
+++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/BuildConfigurationTest.java
@@ -101,8 +101,6 @@ void verify(String packageType) {
     private void verifyBuild(String override) throws IOException, InterruptedException, URISyntaxException {
         File rootDir = getProjectDir(ROOT_PROJECT_NAME);
         BuildResult buildResult = runGradleWrapper(rootDir, "clean", "quarkusBuild",
-                // Package type is not included in the Gradle cache inputs, see https://github.com/quarkusio/quarkus/issues/30852
-                "--no-build-cache",
                 override != null ? "-Dquarkus.package.type=" + override : "-Dfoo=bar");
         soft.assertThat(buildResult.unsuccessfulTasks()).isEmpty();
 
diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/FastJarFormatWorksTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/FastJarFormatWorksTest.java
index 030a181377fa4..6bfa73d0c71a2 100644
--- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/FastJarFormatWorksTest.java
+++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/FastJarFormatWorksTest.java
@@ -22,9 +22,7 @@ public void testFastJarFormatWorks() throws Exception {
 
         final File projectDir = getProjectDir("test-that-fast-jar-format-works");
 
-        BuildResult result = runGradleWrapper(projectDir, "clean", "build");
-        result.getTasks().forEach((k, v) -> System.err.println("   " + k + " --> " + v));
-        System.err.println(result.getOutput());
+        runGradleWrapper(projectDir, "clean", "build");
 
         final Path quarkusApp = projectDir.toPath().resolve("build").resolve("quarkus-app");
         assertThat(quarkusApp).exists();
diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/GrpcMultiModuleQuarkusBuildTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/GrpcMultiModuleQuarkusBuildTest.java
index 4ae97ee4a8c22..f19fdb2324152 100644
--- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/GrpcMultiModuleQuarkusBuildTest.java
+++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/GrpcMultiModuleQuarkusBuildTest.java
@@ -35,7 +35,7 @@ public void testProtocErrorOutput() throws Exception {
         final Path protoDirectory = new File(projectDir, "application/src/main/proto/").toPath();
         Files.copy(projectDir.toPath().resolve("invalid.proto"), protoDirectory.resolve("invalid.proto"));
         try {
-            final BuildResult buildResult = runGradleWrapper(projectDir, ":application:quarkusBuild", "--info");
+            final BuildResult buildResult = runGradleWrapper(true, projectDir, ":application:quarkusBuild", "--info");
             assertTrue(buildResult.getOutput().contains("invalid.proto:5:1: Missing field number."));
         } finally {
             Files.delete(protoDirectory.resolve("invalid.proto"));
diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/JandexMultiModuleTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/JandexMultiModuleTest.java
index 5fd64106e7697..20f71b1d5f9ba 100644
--- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/JandexMultiModuleTest.java
+++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/JandexMultiModuleTest.java
@@ -10,11 +10,14 @@ public class JandexMultiModuleTest extends QuarkusGradleWrapperTestBase {
 
     @Test
     public void testBasicMultiModuleBuildKordamp() throws Exception {
+        // Kordamp Jandex plugin's not compatible w/ the Gradle configuration cache
+        gradleConfigurationCache(false);
         jandexTest("jandex-basic-multi-module-project-kordamp", ":common:jandex");
     }
 
     @Test
     public void testBasicMultiModuleBuildJandex() throws Exception {
+        gradleConfigurationCache(true);
         jandexTest("jandex-basic-multi-module-project-vlsi", ":common:processJandexIndex");
     }
 
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
index 7a51b0246a45c..cfd18d149b450 100644
--- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleDevToolsTestBase.java
+++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleDevToolsTestBase.java
@@ -29,6 +29,7 @@ static void disableDevToolsTestConfig() {
 
     @Override
     protected void setupTestCommand() {
+        gradleNoWatchFs(false);
         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 612d7f498e5ed..3ac4c82177109 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
@@ -11,6 +11,8 @@
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
+import org.assertj.core.api.Assertions;
+
 public class QuarkusGradleWrapperTestBase extends QuarkusGradleTestBase {
 
     private static final String GRADLE_WRAPPER_WINDOWS = "gradlew.bat";
@@ -19,19 +21,47 @@ public class QuarkusGradleWrapperTestBase extends QuarkusGradleTestBase {
 
     private Map<String, String> systemProps;
 
+    private boolean configurationCacheEnable = true;
+    private boolean noWatchFs = true;
+
     protected void setupTestCommand() {
 
     }
 
+    /**
+     * Gradle's configuration cache is enabled by default for all tests. This option can be used to disable the
+     * configuration test.
+     */
+    protected void gradleConfigurationCache(boolean configurationCacheEnable) {
+        this.configurationCacheEnable = configurationCacheEnable;
+    }
+
+    /**
+     * Gradle is run by default with {@code --no-watch-fs} to reduce I/O load during tests. Some tests might run into issues
+     * with this option.
+     */
+    protected void gradleNoWatchFs(boolean noWatchFs) {
+        this.noWatchFs = noWatchFs;
+    }
+
     public BuildResult runGradleWrapper(File projectDir, String... args) throws IOException, InterruptedException {
+        return runGradleWrapper(false, projectDir, args);
+    }
+
+    public BuildResult runGradleWrapper(boolean expectError, File projectDir, String... args)
+            throws IOException, InterruptedException {
         setupTestCommand();
         List<String> command = new ArrayList<>();
         command.add(getGradleWrapperCommand());
         addSystemProperties(command);
         command.add("-Dorg.gradle.console=plain");
-        command.add("-Dorg.gradle.daemon=false");
-        command.add("--configuration-cache");
+        if (configurationCacheEnable) {
+            command.add("--configuration-cache");
+        }
         command.add("--stacktrace");
+        if (noWatchFs) {
+            command.add("--no-watch-fs");
+        }
         command.add("--info");
         command.add("--daemon");
         command.addAll(Arrays.asList(args));
@@ -39,13 +69,18 @@ public BuildResult runGradleWrapper(File projectDir, String... args) throws IOEx
         File logOutput = new File(projectDir, "command-output.log");
 
         System.out.println("$ " + String.join(" ", command));
-        Process p = new ProcessBuilder()
+        ProcessBuilder pb = new ProcessBuilder()
                 .directory(projectDir)
                 .command(command)
                 .redirectInput(ProcessBuilder.Redirect.INHERIT)
-                .redirectError(logOutput)
                 .redirectOutput(logOutput)
-                .start();
+                // Should prevent "fragmented" output (parts of stdout and stderr interleaved)
+                .redirectErrorStream(true);
+        if (System.getenv("JAVA_HOME") == null) {
+            // This helps running the tests in IntelliJ w/o configuring an explicit JAVA_HOME env var.
+            pb.environment().put("JAVA_HOME", System.getProperty("java.home"));
+        }
+        Process p = pb.start();
 
         //long timeout for native tests
         //that may also need to download docker
@@ -55,8 +90,14 @@ public BuildResult runGradleWrapper(File projectDir, String... args) throws IOEx
         }
         final BuildResult commandResult = BuildResult.of(logOutput);
         int exitCode = p.exitValue();
-        if (exitCode != 0) {
+
+        // The test failed, if the Gradle build exits with != 0 and the tests expects no failure, or if the test
+        // expects a failure and the exit code is 0.
+        if (expectError == (exitCode == 0)) {
+            // Only print the output, if the test does not expect a failure.
             printCommandOutput(projectDir, command, commandResult, exitCode);
+            // Fail hard, if the test does not expect a failure.
+            Assertions.fail("Gradle build failed with exit code %d", exitCode);
         }
         return commandResult;
     }
diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/BasicKotlinApplicationModuleDevModeTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/BasicKotlinApplicationModuleDevModeTest.java
index 56c7dafba0f16..80d3ca4602e14 100644
--- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/BasicKotlinApplicationModuleDevModeTest.java
+++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/BasicKotlinApplicationModuleDevModeTest.java
@@ -6,7 +6,7 @@
 
 import com.google.common.collect.ImmutableMap;
 
-@org.junit.jupiter.api.Tag("failsOnJDK19")
+@org.junit.jupiter.api.Tag("failsOnJDK20")
 public class BasicKotlinApplicationModuleDevModeTest extends QuarkusDevGradleTestBase {
 
     @Override
diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/MultiModuleKotlinProjectDevModeTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/MultiModuleKotlinProjectDevModeTest.java
index a59a5d9c0004c..a86c659a42380 100644
--- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/MultiModuleKotlinProjectDevModeTest.java
+++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/MultiModuleKotlinProjectDevModeTest.java
@@ -2,12 +2,9 @@
 
 import static org.assertj.core.api.Assertions.assertThat;
 
-import org.junit.jupiter.api.Disabled;
-
 import com.google.common.collect.ImmutableMap;
 
-@Disabled
-@org.junit.jupiter.api.Tag("failsOnJDK19")
+@org.junit.jupiter.api.Tag("failsOnJDK20")
 public class MultiModuleKotlinProjectDevModeTest extends QuarkusDevGradleTestBase {
 
     @Override
diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/MultiSourceProjectDevModeTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/MultiSourceProjectDevModeTest.java
index f9420d4a123c7..2ed7b5895a71d 100644
--- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/MultiSourceProjectDevModeTest.java
+++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/MultiSourceProjectDevModeTest.java
@@ -4,7 +4,7 @@
 
 import com.google.common.collect.ImmutableMap;
 
-@org.junit.jupiter.api.Tag("failsOnJDK19")
+@org.junit.jupiter.api.Tag("failsOnJDK20")
 public class MultiSourceProjectDevModeTest extends QuarkusDevGradleTestBase {
 
     @Override
diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/QuarkusDevGradleTestBase.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/QuarkusDevGradleTestBase.java
index fc62794cebc42..2602e2e0e502f 100644
--- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/QuarkusDevGradleTestBase.java
+++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/QuarkusDevGradleTestBase.java
@@ -7,6 +7,9 @@
 import java.io.File;
 import java.io.IOException;
 import java.nio.file.Files;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -14,6 +17,8 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import org.apache.commons.io.FileUtils;
 import org.junit.jupiter.api.Test;
@@ -27,24 +32,56 @@ public abstract class QuarkusDevGradleTestBase extends QuarkusGradleWrapperTestB
     private Future<?> quarkusDev;
     protected File projectDir;
 
+    @Override
+    protected void setupTestCommand() {
+        gradleNoWatchFs(false);
+    }
+
     @Test
     public void main() throws Exception {
-
         projectDir = getProjectDir();
         beforeQuarkusDev();
         ExecutorService executor = null;
         AtomicReference<BuildResult> buildResult = new AtomicReference<>();
+        List<String> processesBeforeTest = dumpProcesses();
+        List<String> processesAfterTest = Collections.emptyList();
         try {
-            executor = Executors.newSingleThreadExecutor();
-            quarkusDev = executor.submit(() -> {
-                try {
-                    buildResult.set(build());
-                } catch (Exception e) {
-                    throw new IllegalStateException("Failed to build the project", e);
+            try {
+                executor = Executors.newSingleThreadExecutor();
+                quarkusDev = executor.submit(() -> {
+                    try {
+                        buildResult.set(build());
+                    } catch (Exception e) {
+                        throw new IllegalStateException("Failed to build the project", e);
+                    }
+                });
+                testDevMode();
+            } finally {
+                processesAfterTest = dumpProcesses();
+
+                if (quarkusDev != null) {
+                    quarkusDev.cancel(true);
                 }
-            });
-            testDevMode();
+                if (executor != null) {
+                    executor.shutdownNow();
+                }
+
+                // Kill all processes that were (indirectly) spawned by the current process.
+                List<ProcessHandle> childProcesses = DevModeTestUtils.killDescendingProcesses();
+
+                DevModeTestUtils.awaitUntilServerDown();
+
+                // sanity: forcefully terminate left-over processes
+                childProcesses.forEach(ProcessHandle::destroyForcibly);
+            }
         } catch (Exception | AssertionError e) {
+            System.err.println("PROCESSES BEFORE TEST:");
+            processesBeforeTest.forEach(System.err::println);
+            System.err.println("PROCESSES AFTER TEST (BEFORE CLEANUP):");
+            processesAfterTest.forEach(System.err::println);
+            System.err.println("PROCESSES AFTER CLEANUP:");
+            dumpProcesses().forEach(System.err::println);
+
             if (buildResult.get() != null) {
                 System.err.println("BELOW IS THE CAPTURED LOGGING OF THE FAILED GRADLE TEST PROJECT BUILD");
                 System.err.println(buildResult.get().getOutput());
@@ -65,24 +102,25 @@ public void main() throws Exception {
             }
             throw e;
         } finally {
-            if (quarkusDev != null) {
-                quarkusDev.cancel(true);
-            }
-            if (executor != null) {
-                executor.shutdownNow();
-            }
-
-            // Kill all processes that were (indirectly) spawned by the current process.
-            DevModeTestUtils.killDescendingProcesses();
-
-            DevModeTestUtils.awaitUntilServerDown();
-
             if (projectDir != null && projectDir.isDirectory()) {
                 FileUtils.deleteQuietly(projectDir);
             }
         }
     }
 
+    public static List<String> dumpProcesses() {
+        // ProcessHandle.Info.command()/arguments()/commandLine() are always empty on Windows:
+        // https://bugs.openjdk.java.net/browse/JDK-8176725
+        ProcessHandle current = ProcessHandle.current();
+        return Stream.concat(Stream.of(current), current.descendants()).map(p -> {
+            ProcessHandle.Info i = p.info();
+            return String.format("PID %8d (%8d) started:%s CPU:%s - %s", p.pid(),
+                    p.parent().map(ProcessHandle::pid).orElse(-1L),
+                    i.startInstant().orElse(null), i.totalCpuDuration().orElse(null),
+                    i.commandLine().orElse("<command line not available>"));
+        }).collect(Collectors.toList());
+    }
+
     protected BuildResult build() throws Exception {
         return runGradleWrapper(projectDir, buildArguments());
     }
@@ -117,7 +155,7 @@ protected String getHttpResponse() {
     }
 
     protected String getHttpResponse(String path) {
-        return getHttpResponse(path, 1, TimeUnit.MINUTES);
+        return getHttpResponse(path, devModeTimeoutSeconds(), TimeUnit.SECONDS);
     }
 
     protected String getHttpResponse(String path, long timeout, TimeUnit tu) {
@@ -141,7 +179,16 @@ protected void replace(String srcFile, Map<String, String> tokens) {
     }
 
     protected void assertUpdatedResponseContains(String path, String value) {
-        assertUpdatedResponseContains(path, value, 1, TimeUnit.MINUTES);
+        assertUpdatedResponseContains(path, value, devModeTimeoutSeconds(), TimeUnit.SECONDS);
+    }
+
+    protected int devModeTimeoutSeconds() {
+        // It's a wild guess, but maybe Windows is just slower - at least: a successful Gradle-CI-jobs on Windows is
+        // 2.5x slower than the same Gradle-CI-job on Linux.
+        if (System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("windows")) {
+            return 90;
+        }
+        return 60;
     }
 
     protected void assertUpdatedResponseContains(String path, String value, long waitAtMost, TimeUnit timeUnit) {
diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/extension/ExtensionUnitTestTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/extension/ExtensionUnitTestTest.java
index e664f3ffe50db..946138dc2cb3d 100644
--- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/extension/ExtensionUnitTestTest.java
+++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/extension/ExtensionUnitTestTest.java
@@ -13,6 +13,8 @@ public class ExtensionUnitTestTest extends QuarkusGradleWrapperTestBase {
 
     @Test
     public void shouldRunTestWithSuccess() throws Exception {
+        gradleConfigurationCache(false);
+
         File projectDir = getProjectDir("extensions/simple-extension");
 
         BuildResult buildResult = runGradleWrapper(projectDir, "clean", ":deployment:test", "--no-build-cache");
diff --git a/test-framework/devmode-test-utils/src/main/java/io/quarkus/test/devmode/util/DevModeTestUtils.java b/test-framework/devmode-test-utils/src/main/java/io/quarkus/test/devmode/util/DevModeTestUtils.java
index 55c7315b2ea2b..22c21602bc37d 100644
--- a/test-framework/devmode-test-utils/src/main/java/io/quarkus/test/devmode/util/DevModeTestUtils.java
+++ b/test-framework/devmode-test-utils/src/main/java/io/quarkus/test/devmode/util/DevModeTestUtils.java
@@ -9,25 +9,36 @@
 import java.net.URL;
 import java.nio.charset.StandardCharsets;
 import java.time.Instant;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Supplier;
+import java.util.stream.Collectors;
 
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.IOUtils;
 
 public class DevModeTestUtils {
 
-    public static void killDescendingProcesses() {
+    public static List<ProcessHandle> killDescendingProcesses() {
         // Warning: Do not try to evaluate ProcessHandle.Info.arguments() or .commandLine() as those are always empty on Windows:
         // https://bugs.openjdk.java.net/browse/JDK-8176725
-        ProcessHandle.current().descendants()
+        //
+        // Intentionally collecting the ProcessHandles before calling .destroy(), because it seemed that, at least on
+        // Windows, not all processes were properly killed, leaving (some) processes around, causing following dev-mode
+        // tests to time-out.
+        List<ProcessHandle> childProcesses = ProcessHandle.current().descendants()
                 // destroy younger descendants first
                 .sorted((ph1, ph2) -> ph2.info().startInstant().orElse(Instant.EPOCH)
                         .compareTo(ph1.info().startInstant().orElse(Instant.EPOCH)))
-                .forEach(ProcessHandle::destroy);
+                .collect(Collectors.toList());
+
+        childProcesses.forEach(ProcessHandle::destroy);
+
+        // Returning all child processes for callers that want to do a "kill -9"
+        return childProcesses;
     }
 
     public static void filter(File input, Map<String, String> variables) throws IOException {