Skip to content

ClassNotFoundException with certain Gradle multi-project setup and dev mode #48100

@reaver585

Description

@reaver585

Describe the bug

Howdy everyone.
Imagine the following project setup (with Gradle):

3 projects on the top level

  • :runner -> this is a minimal Quarkus app with REST extension and a single stub endpoint, and it depends on
  • :project-a, which has a single class, and this project depends on
  • :project-b, which has a single class and no additional dependencies

Additionally, :project-a does configure the jar task in the following way:

jar {
    archiveClassifier = "something"
    archiveBaseName = "project-a"
}

Expected behavior

My expectation is that when I call an endpoint from the :runner project, it does not throw and does the work it needs to do.

Actual behavior

When the :runner is run in dev mode, and the endpoint is called, I get:

2025-05-27 23:06:03,346 ERROR [io.qua.ver.htt.run.QuarkusErrorHandler] (executor-thread-1) HTTP Request to /hello failed, error id: 9064607e-6c5e-45a4-83a2-d3b31c105faa-1: java.lang.NoClassDefFoundError: io/leaf/SomeCLass
	at io.blob.Intermediate.someMethod(Intermediate.java:8)
	at io.example.ExampleResource.hello(ExampleResource.java:16)
	at io.example.ExampleResource$quarkusrestinvoker$hello_df324e1539083188359af68039a7afafb7b77cdb.invoke(Unknown Source)
	at org.jboss.resteasy.reactive.server.handlers.InvocationHandler.handle(InvocationHandler.java:29)
	at io.quarkus.resteasy.reactive.server.runtime.QuarkusResteasyReactiveRequestContext.invokeHandler(QuarkusResteasyReactiveRequestContext.java:141)
	at org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.run(AbstractResteasyReactiveContext.java:147)
	at io.quarkus.vertx.core.runtime.VertxCoreRecorder$15.runWith(VertxCoreRecorder.java:637)
	at org.jboss.threads.EnhancedQueueExecutor$Task.doRunWith(EnhancedQueueExecutor.java:2675)
	at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2654)
	at org.jboss.threads.EnhancedQueueExecutor.runThreadBody(EnhancedQueueExecutor.java:1627)
	at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1594)
	at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:11)
	at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:11)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.base/java.lang.Thread.run(Thread.java:1583)
Caused by: java.lang.ClassNotFoundException: io.leaf.SomeCLass
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526)
	at io.quarkus.bootstrap.classloading.QuarkusClassLoader.loadClass(QuarkusClassLoader.java:576)
	at io.quarkus.bootstrap.classloading.QuarkusClassLoader.loadClass(QuarkusClassLoader.java:523)
	... 15 more

io.leaf.SomeCLass is located in :project-b.

How to Reproduce?

A minimal reproducer can be found in this public repo: https://github.com/reaver585/quarkus-jar-classloading

To reproduce:

  1. Clone the repo.
  2. ./gradlew --console=plain :runner:quarkusDev
  3. curl -X GET --location "http://localhost:8080/hello"
  4. Observe the exception in the logs.

Output of uname -a or ver

Darwin TW-MBP-M2 24.4.0 Darwin Kernel Version 24.4.0: Fri Apr 11 18:33:39 PDT 2025; root:xnu-11417.101.15~117/RELEASE_ARM64_T6020 arm64

Output of java -version

openjdk version "21.0.7" 2025-04-15 LTS OpenJDK Runtime Environment Temurin-21.0.7+6 (build 21.0.7+6-LTS) OpenJDK 64-Bit Server VM Temurin-21.0.7+6 (build 21.0.7+6-LTS, mixed mode, sharing)

Quarkus version or git rev

3.22.1

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

Gradle 8.13

Additional information

The issue stops manifesting if:

  • I comment out the jar task completely
  • I comment out the archiveClassifier = "something" line in the jar config.

So my hunch is that there is some logic that assigns my classes to the wrong classloader based on the presence of archive classifier for the jar. It causes io.blob.Intermediate class in :project-a to be loaded via base runtime classloader which then cannot see the io.leaf.SomeCLass from :project-b.

If this is indeed a bug, I wouldn't mind help you fix it if you point me in the right direction.

Thanks in advance.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions