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 :
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 javax
→ jakarta
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.
Propiedad de configuración fijada en tiempo de compilación - Todas las demás propiedades de configuración son anulables en tiempo de ejecución
Configuration property |
Tipo |
Por defecto |
||
---|---|---|---|---|
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.
Environment variable: 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.
Environment variable: 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: 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 Note that for technical reasons this is not supported when running with JBang. Environment variable: 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));
}