Class Loading Reference

This document explains the Quarkus class loading architecture. It is intended for extension authors and advanced users who want to understand exactly how Quarkus works.

The Quarkus class loading architecture is slightly different depending on the mode that the application is run in.

When running a production application using the fast-jar package type (which is the default), almost all dependencies are loaded via the io.quarkus.bootstrap.runner.RunnerClassLoader which indexes class at build time, while a small set of dependencies is loaded from the system ClassLoader.

When running a production application using the legacy-jar package type everything is loaded in the system ClassLoader, so it is a completely flat classpath.

The flat classpath strategy is also used for GraalVM native images, since GraalVM does not really support multiple ClassLoaders.

For all other use cases (e.g. tests, dev mode, and building the application) Quarkus uses the class loading architecture outlined in following section.

Bootstrapping Quarkus

All Quarkus applications are created by the QuarkusBootstrap class in the independent-projects/bootstrap module. This class is used to resolve all the relevant dependencies (both deployment and runtime) that are needed for the Quarkus application. The end result of this process is a CuratedApplication, which contains all the class loading information for the application.

The CuratedApplication can then be used to create an AugmentAction instance, which can create production application and start/restart runtime ones. This application instance exists within an isolated ClassLoader, it is not necessary to have any of the Quarkus deployment classes on the class path as the curate process will resolve them for you.

This bootstrap process should be the same no matter how Quarkus is launched, just with different parameters passed in.

Current Run Modes

At the moment we have the following use cases for bootstrapping Quarkus:

  • Maven creating production application

  • Maven dev mode

  • Gradle creating a production application

  • Gradle dev mode

  • QuarkusTest (Maven, Gradle and IDE)

  • QuarkusUnitTest (Maven, Gradle and IDE)

  • QuarkusDevModeTest (Maven, Gradle and IDE)

  • Arquillian Adaptor

One of the goals of this refactor is to have all these different run modes boot Quarkus in fundamentally the same way.

Notes on Transformer Safety

A ClassLoader is said to be 'transformer safe' if it is safe to load classes in the class loader before the transformers are ready. Once a class has been loaded it cannot be changed, so if a class is loaded before the transformers have been prepared this will prevent the transformation from working. Loading classes in a transformer safe ClassLoader will not prevent the transformation, as the loaded class is not used at runtime.

ClassLoader Implementations

Quarkus has a number of ClassLoaders. This shows the relationship between them :

Quarkus ClassLoader hierarchy

Here are the roles of each ClassLoader:

Base ClassLoader

This is usually the normal JVM System ClassLoader. In some environments such as Maven it may be different. This ClassLoader is used to load the bootstrap classes, and other ClassLoader instances will delegate the loading of JDK classes to it.

Augmentation ClassLoader

This loads all the -deployment artifacts and their dependencies, as well as other user dependencies. It does not load the application root or any hot deployed code. This ClassLoader is persistent, even if the application restarts it will remain (which is why it cannot load application classes that may be hot deployed). Its parent is the base ClassLoader, and it is transformer safe.

At present this can be configured to delegate to the Base ClassLoader, however the plan is for this option to go away and always have this as an isolated ClassLoader. Making this an isolated ClassLoader is complicated as it means that all the builder classes are isolated, which means that use cases that want to customise the build chains are slightly more complex.

Deployment ClassLoader

This can load all application classes. Its parent is the Augmentation ClassLoader, so it can also load all deployment classes.

This ClassLoader is non-persistent, it will be re-created when the application is started, and is isolated. This ClassLoader is the context ClassLoader that is used when running the build steps. It is also transformer safe.

Base Runtime ClassLoader

This loads all the runtime extension dependencies, as well as other user dependencies (note that this may include duplicate copies of classes also loaded by the Augmentation ClassLoader). It does not load the application root or any hot deployed code. This ClassLoader is persistent, even if the application restarts it will remain (which is why it cannot load application classes that may be hot deployed). Its parent is the base ClassLoader.

This loads code that is not hot-reloadable, but it does support transformation (although once the class is loaded this transformation is no longer possible). This means that only transformers registered in the first application start will take effect, however as these transformers are expected to be idempotent this should not cause problems. An example of the sort of transformation that might be required here is a Panache entity packaged in an external jar. This class needs to be transformed to have its static methods implemented, however this transformation only happens once, so restarts use the copy of the class that was created on the first start.

This ClassLoader is isolated from the Augment and Deployment ClassLoaders. This means that it is not possible to set values in a static field in the deployment side, and expect to read it at runtime. This allows dev and test applications to behave more like a production application (production applications are isolated in that they run in a whole new JVM).

This also means that the runtime version can be linked against a different set of dependencies, e.g. the hibernate version used at deployment time might want to include ByteBuddy, while the version used at runtime does not.

Runtime Class Loader

This ClassLoader is used to load the application classes and other hot deployable resources. Its parent is the base runtime ClassLoader, and it is recreated when the application is restarted.

Isolated ClassLoaders

The runtime ClassLoader is always isolated. This means that it will have its own copies of almost every class from the resolved dependency list. The exception to this are:

  • JDK classes

  • Classes from artifacts that extensions have marked as parent first (more on this later).

Parent First Dependencies

There are some classes that should not be loaded in an isolated manner, but that should always be loaded by the system ClassLoader (or whatever ClassLoader is responsible for bootstrapping Quarkus). Most extensions do not need to worry about this, however there are a few cases where this is necessary:

  • Some logging related classes, as logging must be loaded by the system ClassLoader

  • Quarkus bootstrap itself

If this is required it can be configured in the quarkus-extension-maven-plugin. Note that if you mark a dependency as parent first then all of its dependencies must also be parent first, or a LinkageError can occur.

<plugin>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-extension-maven-plugin</artifactId>
    <configuration>
        <parentFirstArtifacts>
            <parentFirstArtifact>io.quarkus:quarkus-bootstrap-core</parentFirstArtifact>
            <parentFirstArtifact>io.quarkus:quarkus-development-mode-spi</parentFirstArtifact>
            <parentFirstArtifact>org.jboss.logmanager:jboss-logmanager</parentFirstArtifact>
            <parentFirstArtifact>org.jboss.logging:jboss-logging</parentFirstArtifact>
            <parentFirstArtifact>org.ow2.asm:asm</parentFirstArtifact>
        </parentFirstArtifacts>
    </configuration>
</plugin>

Banned Dependencies

There are some dependencies that we can be sure we do not want. This generally happens when a dependency has had a name change (e.g. smallrye-config changing groups from org.smallrye to org.smallrye.config, the javaxjakarta rename). This can cause problems, as if these artifacts end up in the dependency tree out of date classes can be loaded that are not compatible with Quarkus. To deal with this, extensions can specify artifacts that should never be loaded. This is done by modifying the quarkus-extension-maven-plugin config in the pom (which generates the quarkus-extension.properties file). Simply add an excludedArtifacts section as shown below:

<plugin>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-extension-maven-plugin</artifactId>
    <configuration>
        <excludedArtifacts>
            <excludedArtifact>io.smallrye:smallrye-config</excludedArtifact>
            <excludedArtifact>javax.enterprise:cdi-api</excludedArtifact>
        </excludedArtifacts>
    </configuration>
</plugin>

This should only be done if the extension depends on a newer version of these artifacts. If the extension does not bring in a replacement artifact as a dependency then classes the application needs might end up missing.

Configuring Class Loading

It is possible to configure some aspects of class loading in dev and test mode. This can be done using application.properties. Note that class loading config is different to normal config, in that it does not use the standard Quarkus config mechanisms (as it is needed too early), so only supports application.properties. The following options are supported.

Configuration property fixed at build time - All other configuration properties are overridable at runtime

Configuration property

Type

Default

Artifacts that are loaded in a parent first manner. This can be used to work around issues where a given class needs to be loaded by the system ClassLoader. Note that if you make a library parent first all its dependencies should generally also be parent first.

Artifacts should be configured as a comma separated list of artifact ids, with the group, artifact-id and optional classifier separated by a colon.

This config property can only be set in application.properties

Environment variable: QUARKUS_CLASS_LOADING_PARENT_FIRST_ARTIFACTS

Show more

list of string

Artifacts that are loaded in the runtime ClassLoader in dev mode, so they will be dropped and recreated on change.

This is an advanced option, it should only be used if you have a problem with libraries holding stale state between reloads. Note that if you use this any library that depends on the listed libraries will also need to be reloadable.

This setting has no impact on production builds.

Artifacts should be configured as a comma separated list of artifact ids, with the group, artifact-id and optional classifier separated by a colon.

This config property can only be set in application.properties

Environment variable: QUARKUS_CLASS_LOADING_RELOADABLE_ARTIFACTS

Show more

string

Artifacts that will never be loaded by the class loader, and will not be packed into the final application. This allows you to explicitly remove artifacts from your application even though they may be present on the class path.

Environment variable: QUARKUS_CLASS_LOADING_REMOVED_ARTIFACTS

Show more

list of string

Resources that should be removed/hidden from dependencies.

This allows for classes and other resources to be removed from dependencies, so they are not accessible to the application. This is a map of artifact id (in the form group:artifact) to a list of resources to be removed.

When running in dev and test mode these resources are hidden from the ClassLoader, when running in production mode these files are removed from the jars that contain them.

Note that if you want to remove a class you need to specify the class file name. e.g. to remove com.acme.Foo you would specify com/acme/Foo.class.

Note that for technical reasons this is not supported when running with JBang.

Environment variable: QUARKUS_CLASS_LOADING_REMOVED_RESOURCES

Show more

Map<String,Set<String>>

Hiding/Removing classes and resources from dependencies

It is possible to hide/remove classes and resources from dependencies. This is an advanced option, but it can be useful at times. This is done via the quarkus.class-loading.removed-resources config key, for example:

quarkus.class-loading.removed-resources."io.quarkus\:quarkus-integration-test-shared-library"=io/quarkus/it/shared/RemovedResource.class

This will remove the RemovedResource.class file from the io.quarkus:quarkus-integration-test-shared-library artifact.

Even though this option is a class loading option it will also affect the generated application, so when the application is created removed resources will not be accessible.

Reading Class Bytecode

It is important to use the correct ClassLoader. The recommended approach is to get it by calling the Thread.currentThread().getContextClassLoader() method.

Example:

@BuildStep
GeneratedClassBuildItem instrument(final CombinedIndexBuildItem index) {
    final String classname = "com.example.SomeClass";
    final ClassLoader cl = Thread.currentThread().getContextClassLoader();
    final byte[] originalBytecode = IoUtil.readClassAsBytes(cl, classname);
    final byte[] enhancedBytecode = ... // class instrumentation from originalBytecode
    return new GeneratedClassBuildItem(true, classname, enhancedBytecode));
}

Related content