0% found this document useful (0 votes)
188 views72 pages

Create Custom Quarkus Extensions Guide

The document discusses writing extensions for Quarkus that add new developer-focused behaviors to the core offering. Extensions have two parts - buildtime augmentation that processes metadata and generates bytecode, and runtime containers. Most work is done at buildtime to reduce startup time and memory usage. Extensions should favor buildtime work, expose configuration through SmallRye Config, and expose components via CDI beans.

Uploaded by

Brubaker Brubake
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
188 views72 pages

Create Custom Quarkus Extensions Guide

The document discusses writing extensions for Quarkus that add new developer-focused behaviors to the core offering. Extensions have two parts - buildtime augmentation that processes metadata and generates bytecode, and runtime containers. Most work is done at buildtime to reduce startup time and memory usage. Extensions should favor buildtime work, expose configuration through SmallRye Config, and expose components via CDI beans.

Uploaded by

Brubaker Brubake
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 72

28/2/24, 11:16 Writing Your Own Extension - Quarkus

The English version of quarkus.io is the official project site. Translated


sites are community supported on a best-effort basis.

 volver al índice 3.7.3 - Latest 

WRITING YOUR OWN


EXTENSION
Quarkus extensions add a new developer focused behavior to the core
offering, and consist of two distinct parts, buildtime augmentation and
runtime container. The augmentation part is responsible for all metadata
processing, such as reading annotations, XML descriptors etc. The output of
this augmentation phase is recorded bytecode which is responsible for
directly instantiating the relevant runtime services.

This means that metadata is only processed once at build time, which both
saves on startup time, and also on memory usage as the classes etc that are
used for processing are not loaded (or even present) in the runtime JVM.

This is an in-depth documentation, see the building my


 first extension if you need an introduction.

1. Extension philosophy
This section is a work in progress and gathers the philosophy under which
extensions should be designed and written.

1.1. Why an extension framework


Quarkus’s mission is to transform your entire application including the
libraries it uses, into an artifact that uses significantly less resources than
traditional approaches. These can then be used to build native applications
using GraalVM. To do this you need to analyze and understand the full
"closed world" of the application. Without the full and complete context, the
best that can be achieved is partial and limited generic support. By using the
Quarkus extension approach, we can bring Java applications in line with
memory footprint constrained environments like Kubernetes or cloud
platforms.

The Quarkus extension framework results in significantly improved resource


utilization even when GraalVM is not used (e.g. in HotSpot). Let’s list the
actions an extension performs:

Gather build time metadata and generate code

https://es.quarkus.io/guides/writing-extensions 1/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
This part has nothing to do with GraalVM, it is how Quarkus starts
frameworks “at build time”
The extension framework facilitates reading metadata, scanning
classes as well as generating classes as needed
A small part of the extension work is executed at runtime via the
generated classes, while the bulk of the work is done at build
time (called deployment time)
Enforce opinionated and sensible defaults based on the close world
view of the application (e.g. an application with no @Entity does not
need to start Hibernate ORM)
An extension hosts Substrate VM code substitution so that libraries
can run on GraalVM
Most changes are pushed upstream to help the underlying library
run on GraalVM
Not all changes can be pushed upstream, extensions host
Substrate VM substitutions - which is a form of code patching - so
that libraries can run
Host Substrate VM code substitution to help dead code elimination
based on the application needs
This is application dependent and cannot really be shared in the
library itself
For example, Quarkus optimizes the Hibernate code because it
knows it only needs a specific connection pool and cache
provider
Send metadata to GraalVM for example classes in need of reflection
This information is not static per library (e.g. Hibernate) but the
framework has the semantic knowledge and knows which classes
need to have reflection (for example @Entity classes)

1.2. Favor build time work over runtime work


As much as possible favor doing work at build time (deployment part of the
extension) as opposed to let the framework do work at startup time
(runtime). The more is done there, the smaller Quarkus applications using
that extension will be and the faster they will load.

1.3. How to expose configuration


Quarkus simplifies the most common usages. This means that its defaults
might be different from the library it integrates.

To make the simple experience easiest, unify the configuration in


application.properties via SmallRye Config. Avoid library specific
configuration files, or at least make them optional: e.g. persistence.xml
for Hibernate ORM is optional.

Extensions should see the configuration holistically as a Quarkus application


instead of focusing on the library experience. For example
quarkus.database.url and friends are shared between extensions as
defining a database access is a shared task (instead of a hibernate.
property for example). The most useful configuration options should be
exposed as quarkus.[extension]. instead of the natural namespace of
the library. Less common properties can live in the library namespace.

https://es.quarkus.io/guides/writing-extensions 2/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
To fully enable the close world assumptions that Quarkus can optimize best,
it is better to consider configuration options as build time settled vs
overridable at runtime. Of course properties like host, port, password
should be overridable at runtime. But many properties like enable caching
or setting the JDBC driver can safely require a rebuild of the application.

1.3.1. Static Init Config

If the extension provides additional Config Sources and if these are required
during Static Init, these must be registered with
StaticInitConfigBuilderBuildItem. Configuration in Static Init does
not scan for additional sources to avoid double initialization at application
startup time.

1.4. Expose your components via CDI


Since CDI is the central programming model when it comes to component
composition, frameworks and extensions should expose their components
as beans that are easily consumable by user applications. For example,
Hibernate ORM exposes EntityManagerFactory and EntityManager
beans, the connection pool exposes DataSource beans etc. Extensions
must register these bean definitions at build time.

1.4.1. Beans backed by classes

An extension can produce an AdditionalBeanBuildItem to instruct the


container to read a bean definition from a class as if it was part of the
original application:

Bean Class Registered by AdditionalBeanBuildItem

@Singleton 1 
public class Echo {

public String echo(String val) {


return val;
}
}

If a bean registered by an AdditionalBeanBuildItem does not


1
specify a scope then @Dependent is assumed.

All other beans can inject such a bean:

Bean Injecting a Bean Produced by an AdditionalBeanBuildItem

@Path("/hello") 
public class ExampleResource {

@Inject
Echo echo;

@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello(String foo) {

https://es.quarkus.io/guides/writing-extensions 3/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
return echo.echo(foo);
}
}

And vice versa - the extension bean can inject application beans and beans
provided by other extensions:

Extension Bean Injection Example

@Singleton 
public class Echo {

@Inject
DataSource dataSource; 1

@Inject
Instance<List<String>> listsOfStrings; 2

//...
}

1 Inject a bean provided by other extension.

2 Inject all beans matching the type List<String>.

1.4.2. Bean initialization

Some components may require additional initialization based on


information collected during augmentation. The most straightforward
solution is to obtain a bean instance and call a method directly from a build
step. However, it is illegal to obtain a bean instance during the
augmentation phase. The reason is that the CDI container is not started yet.
It’s started during the Static init bootstrap phase.

BUILD_AND_RUN_TIME_FIXED and RUN_TIME config

 roots can be injected in any bean. RUN_TIME config


roots should only be injected after the bootstrap though.

It is possible to invoke a bean method from a recorder method though. If


you need to access a bean in a @Record(STATIC_INIT) build step then is
must either depend on the BeanContainerBuildItem or wrap the logic in
a BeanContainerListenerBuildItem. The reason is simple - we need to
make sure the CDI container is fully initialized and started. However, it is
safe to expect that the CDI container is fully initialized and running in a
@Record(RUNTIME_INIT) build step. You can obtain a reference to the
container via CDI.current() or Quarkus-specific Arc.container().

Don’t forget to make sure the bean state guarantees the


 visibility, e.g. via the volatile keyword.

 There is one significant drawback of this "late


initialization" approach. An uninitialized bean may be
accessed by other extensions or application components

https://es.quarkus.io/guides/writing-extensions 4/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
that are instantiated during bootstrap. We’ll cover a
more robust solution in the Synthetic beans.

1.4.3. Default beans

A very useful pattern of creating such beans but also giving application code
the ability to easily override some beans with custom implementations, is to
use the @DefaultBean that Quarkus provides. This is best explained with
an example.

Let us assume that the Quarkus extension needs to provide a Tracer bean
which application code is meant to inject into its own beans.

@Dependent 
public class TracerConfiguration {

@Produces
public Tracer tracer(Reporter reporter,
Configuration configuration) {
return new Tracer(reporter, configuration);
}

@Produces
@DefaultBean
public Configuration configuration() {
// create a Configuration
}

@Produces
@DefaultBean
public Reporter reporter(){
// create a Reporter
}
}

If for example application code wants to use Tracer, but also needs to use
a custom Reporter bean, such a requirement could easily be done using
something like:

@Dependent 
public class CustomTracerConfiguration {

@Produces
public Reporter reporter(){
// create a custom Reporter
}
}

1.4.4. How to Override a Bean Defined by a Library/Quarkus


Extension that doesn’t use @DefaultBean

Although @DefaultBean is the recommended approach, it is also possible


for application code to override beans provided by an extension by marking
beans as a CDI @Alternative and including @Priority annotation. Let’s

https://es.quarkus.io/guides/writing-extensions 5/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
show a simple example. Suppose we work on an imaginary "quarkus-
parser" extension and we have a default bean implementation:

@Dependent 
class Parser {

String[] parse(String expression) {


return expression.split("::");
}
}

And our extension also consumes this parser:

@ApplicationScoped 
class ParserService {

@Inject
Parser parser;

//...
}

Now, if a user or even some other extension needs to override the default
implementation of the Parser the simplest solution is to use CDI
@Alternative + @Priority:

@Alternative 1 
@Priority(1) 2
@Singleton
class MyParser extends Parser {

String[] parse(String expression) {


// my super impl...
}
}

1 MyParser is an alternative bean.

Enables the alternative. The priority could be any number to


2 override the default bean but if there are multiple alternatives the
highest priority wins.

CDI alternatives are only considered during injection and


type-safe resolution. For example the default
 implementation would still receive observer
notifications.

1.4.5. Synthetic beans

Sometimes it is very useful to be able to register a synthetic bean. Bean


attributes of a synthetic bean are not derived from a java class, method or
field. Instead, the attributes are specified by an extension.

https://es.quarkus.io/guides/writing-extensions 6/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus

Since the CDI container does not control the


instantiation of a synthetic bean the dependency
injection and other services (such as interceptors) are
 not supported. In other words, it’s up to the extension to
provide all required services to a synthetic bean
instance.

There are several ways to register a synthetic bean in Quarkus. In this


chapter, we will cover a use case that can be used to initialize extension
beans in a safe manner (compared to Bean initialization).

The SyntheticBeanBuildItem can be used to register a synthetic bean:

whose instance can be easily produced through a recorder,


to provide a "context" bean that holds all the information collected
during augmentation so that the real components do not need any
"late initialization" because they can inject the context bean directly.

Instance Produced Through Recorder

@BuildStep 
@Record(STATIC_INIT)
SyntheticBeanBuildItem syntheticBean(TestRecorder
recorder) {
return
SyntheticBeanBuildItem.configure(Foo.class).scope(Singl
eton.class)

.runtimeValue(recorder.createFoo("parameters are
recorder in the bytecode")) 1
.done();
}

The string value is recorded in the bytecode and used to initialize


1
the instance of Foo.

"Context" Holder

@BuildStep 
@Record(STATIC_INIT)
SyntheticBeanBuildItem syntheticBean(TestRecorder
recorder) {
return
SyntheticBeanBuildItem.configure(TestContext.class).sco
pe(Singleton.class)

.runtimeValue(recorder.createContext("parameters are
recorder in the bytecode")) 1
.done();
}

1 The "real" components can inject the TestContext directly.

https://es.quarkus.io/guides/writing-extensions 7/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus

1.5. Some types of extensions


There exist multiple stereotypes of extension, let’s list a few.

Bare library running

This is the less sophisticated extension. It consists of a set of patches


to make sure a library runs on GraalVM. If possible, contribute these
patches upstream, not in extensions. Second best is to write Substrate
VM substitutions, which are patches applied during native image
compilation.

Get a framework running

A framework at runtime typically reads configuration, scan the


classpath and classes for metadata (annotations, getters etc.), build a
metamodel on top of which it runs, find options via the service loader
pattern, prepare invocation calls (reflection), proxy interfaces, etc.
These operations should be done at build time and the metamodel be
passed to the recorder DSL that will generate classes that will be
executed at runtime and boot the framework.

Get a CDI portable extension running

The CDI portable extension model is very flexible. Too flexible to


benefit from the build time boot promoted by Quarkus. Most
extension we have seen do not make use of these extreme flexibility
capabilities. The way to port a CDI extension to Quarkus is to rewrite it
as a Quarkus extension which will define the various beans at build
time (deployment time in extension parlance).

2. Technical aspect
2.1. Three Phases of Bootstrap and Quarkus
Philosophy
There are three distinct bootstrap phases of a Quarkus app:

Augmentation

This is the first phase, and is done by the Build Step Processors. These
processors have access to Jandex annotation information and can
parse any descriptors and read annotations, but should not attempt to
load any application classes. The output of these build steps is some
recorded bytecode, using an extension of the ObjectWeb ASM project
called Gizmo(ext/gizmo), that is used to actually bootstrap the
application at runtime. Depending on the
io.quarkus.deployment.annotations.ExecutionTime value of
the @io.quarkus.deployment.annotations.Record annotation
associated with the build step, the step may be run in a different JVM
based on the following two modes.

Static Init

If bytecode is recorded with @Record(STATIC_INIT) then it will be


executed from a static init method on the main class. For a native
executable build, this code is executed in a normal JVM as part of the
native build process, and any retained objects that are produced in

https://es.quarkus.io/guides/writing-extensions 8/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
this stage will be directly serialized into the native executable via an
image mapped file. This means that if a framework can boot in this
phase then it will have its booted state directly written to the image,
and so the boot code does not need to be executed when the image is
started.
There are some restrictions on what can be done in this stage as the
Substrate VM disallows some objects in the native executable. For
example you should not attempt to listen on a port or start threads in
this phase. In addition, it is disallowed to read run time configuration
during static initialization.
In non-native pure JVM mode, there is no real difference between
Static and Runtime Init, except that Static Init is always executed first.
This mode benefits from the same build phase augmentation as native
mode as the descriptor parsing and annotation scanning are done at
build time and any associated class/framework dependencies can be
removed from the build output jar. In servers like WildFly, deployment
related classes such as XML parsers hang around for the life of the
application, using up valuable memory. Quarkus aims to eliminate this,
so that the only classes loaded at runtime are actually used at runtime.
As an example, the only reason that a Quarkus application should load
an XML parser is if the user is using XML in their application. Any XML
parsing of configuration should be done in the Augmentation phase.

Runtime Init

If bytecode is recorded with @Record(RUNTIME_INIT) then it is


executed from the application’s main method. This code will be run on
native executable boot. In general as little code as possible should be
executed in this phase, and should be restricted to code that needs to
open ports etc.
Pushing as much as possible into the @Record(STATIC_INIT) phase
allows for two different optimizations:

1. In both native executable and pure JVM mode this allows the app to
start as fast as possible since processing was done during build time.
This also minimizes the classes/native code needed in the application
to pure runtime related behaviors.
2. Another benefit with native executable mode is that Substrate can
more easily eliminate features that are not used. If features are
directly initialized via bytecode, Substrate can detect that a method is
never called and eliminate that method. If config is read at runtime,
Substrate cannot reason about the contents of the config and so
needs to keep all features in case they are required.

2.2. Project setup


Your extension project should be setup as a multi-module project with two
submodules:

1. A deployment time submodule that handles the build time processing


and bytecode recording.
2. A runtime submodule that contains the runtime behavior that will
provide the extension behavior in the native executable or runtime
JVM.

Your runtime artifact should depend on io.quarkus:quarkus-core, and


possibly the runtime artifacts of other Quarkus modules if you want to use

https://es.quarkus.io/guides/writing-extensions 9/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
functionality provided by them. Your deployment time module should
depend on io.quarkus:quarkus-core-deployment, your runtime
artifact, and possibly the deployment artifacts of other Quarkus modules if
you want to use functionality provided by them.
Under no circumstances can the runtime module
depend on a deployment artifact. This would result in
 pulling all the deployment time code into runtime scope,
which defeats the purpose of having the split.

2.2.1. Using Maven

You will need to include the io.quarkus:quarkus-extension-maven-


plugin and configure the maven-compiler-plugin to detect the
quarkus-extension-processor annotation processor to collect and
generate the necessary Quarkus extension metadata for the extension
artifacts, if you are using the Quarkus parent pom it will automatically
inherit the correct configuration.

You may want to use the create-extension mojo of


 io.quarkus.platform:quarkus-maven-plugin to
create these Maven modules - see the next section.

By convention the deployment time artifact has the -


 deployment suffix, and the runtime artifact has no
suffix (and is what the end user adds to their project).

<dependencies> 
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-core</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-maven-
plugin</artifactId>
<!-- Executions configuration can be
inherited from quarkus-build-parent -->
<executions>
<execution>
<goals>
<goal>extension-
descriptor</goal>
</goals>
<configuration>

<deployment>${project.groupId}:${project.artifactId}-
deployment:${project.version}</deployment>
</configuration>
</execution>
</executions>
</plugin>

https://es.quarkus.io/guides/writing-extensions 10/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-
plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-
processor</artifactId>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>

The above maven-compiler-plugin configuration


 requires version 3.5+.

You will also need to configure the maven-compiler-plugin of the


deployment module to detect the quarkus-extension-processor
annotation processor.

<dependencies> 
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-core-
deployment</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-
plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-
processor</artifactId>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>

2.2.1.1. Create new Quarkus Core extension modules using


Maven

Quarkus provides create-extension Maven Mojo to initialize your


extension project.

https://es.quarkus.io/guides/writing-extensions 11/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
It will try to auto-detect its options:

from quarkus (Quarkus Core) or quarkus/extensions directory, it


will use the 'Quarkus Core' extension layout and defaults.
with -DgroupId=io.quarkiverse.[extensionId], it will use the
'Quarkiverse' extension layout and defaults.
in other cases it will use the 'Standalone' extension layout and
defaults.
we may introduce other layout types in the future.

You may not specify any parameter to use the


interactive mode: mvn
 io.quarkus.platform:quarkus-maven-
plugin:3.7.3:create-extension -N

As and example, let’s add a new extension called my-ext to the Quarkus
source tree:

git clone https://github.com/quarkusio/quarkus.git 


cd quarkus
mvn io.quarkus.platform:quarkus-maven-
plugin:3.7.3:create-extension -N \
-DextensionId=my-ext \
-DextensionName="My Extension" \
-DextensionDescription="Do something useful."

By default, the groupId, version, quarkusVersion,


 namespaceId, and namespaceName will be consistent
with other Quarkus core extensions.

The extension description is important as it is displayed


 on https://code.quarkus.io/, when listing extensions with
the Quarkus CLI, etc.

The above sequence of commands does the following:

Creates four new Maven modules:


quarkus-my-ext-parent in the extensions/my-ext directory
quarkus-my-ext in the extensions/my-ext/runtime
directory
quarkus-my-ext-deployment in the extensions/my-
ext/deployment directory; a basic MyExtProcessor class is
generated in this module.
quarkus-my-ext-integration-test in the integration-
tests/my-ext/deployment directory; an empty Jakarta REST
Resource class and two test classes (for JVM mode and native
mode) are generated in this module.
Links these three modules where necessary:
quarkus-my-ext-parent is added to the <modules> of
quarkus-extensions-parent
quarkus-my-ext is added to the <dependencyManagement> of
the Quarkus BOM (Bill of Materials) bom/application/pom.xml

https://es.quarkus.io/guides/writing-extensions 12/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
quarkus-my-ext-deployment is added to the
<dependencyManagement> of the Quarkus BOM (Bill of
Materials) bom/application/pom.xml
quarkus-my-ext-integration-test is added to the
<modules> of quarkus-integration-tests-parent

You also have to fill the quarkus-extension.yaml


template file that describe your extension inside the
 runtime module src/main/resources/META-INF
folder.

This is the quarkus-extension.yaml template of the quarkus-agroal


extension, you can use it as an example:

name: "Agroal - Database connection pool" 1 


metadata:
keywords: 2
- "agroal"
- "database-connection-pool"
- "datasource"
- "jdbc"
guide: "https://quarkus.io/guides/datasource" 3
categories: 4
- "data"
status: "stable" 5

1 the name of the extension that will be displayed to users

keywords that can be used to find the extension in the extension


2
catalog

3 link to the extension’s guide or documentation

categories under which the extension should appear on


4 code.quarkus.io, could be omitted, in which case the extension will
still be listed but not under any specific category

maturity status, which could be stable, preview or


5
experimental, evaluated by extension maintainers

The name parameter of the mojo is optional. If you do


not specify it on the command line, the plugin will derive
 it from extensionId by replacing dashes with spaces
and uppercasing each token. So you may consider
omitting explicit name in some cases.

Please refer to CreateExtensionMojo JavaDoc for all the available options of


the mojo.

2.2.2. Using Gradle

You will need to apply the io.quarkus.extension plugin in the runtime


module of your extension project. The plugin includes the
extensionDescriptor task that will generate META-INF/quarkus-
extension.properties and META-INF/quarkus-extension.yml files.
The plugin also enables the io.quarkus:quarkus-extension-

https://es.quarkus.io/guides/writing-extensions 13/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
processor annotation processor in both deployment and runtime
modules to collect and generate the rest of the Quarkus extension
metadata. The name of the deployment module can be configured in the
plugin by setting the deploymentModule property. The property is set to
deployment by default:

plugins { 
id 'java'
id 'io.quarkus.extension'
}

quarkusExtension {
deploymentModule = 'deployment'
}

dependencies {
implementation platform('io.quarkus:quarkus-
bom:3.7.3')
}

This plugin is still experimental, it does not validate the


 extension dependencies as the equivalent Maven plugin
does.

2.3. Build Step Processors


Work is done at augmentation time by build steps which produce and
consume build items. The build steps found in the deployment modules that
correspond to the extensions in the project build are automatically wired
together and executed to produce the final build artifact(s).

2.3.1. Build steps

A build step is a non-static method which is annotated with the


@io.quarkus.deployment.annotations.BuildStep annotation. Each
build step may consume items that are produced by earlier stages, and
produce items that can be consumed by later stages. Build steps are
normally only run when they produce a build item that is ultimately
consumed by another step.

Build steps are normally placed on plain classes within an extension’s


deployment module. The classes are automatically instantiated during the
augment process and utilize injection.

2.3.2. Build items

Build items are concrete, final subclasses of the abstract


io.quarkus.builder.item.BuildItem class. Each build item represents
some unit of information that must be passed from one stage to another.
The base BuildItem class may not itself be directly subclassed; rather,
there are abstract subclasses for each of the kinds of build item subclasses
that may be created: simple, multi, and empty.

Think of build items as a way for different extensions to communicate with


one another. For example, a build item can:

https://es.quarkus.io/guides/writing-extensions 14/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
expose the fact that a database configuration exists
consume that database configuration (e.g. a connection pool extension
or an ORM extension)
ask an extension to do work for another extension: e.g. an extension
wanting to define a new CDI bean and asking the ArC extension to do
so

This is a very flexible mechanism.

BuildItem instances should be immutable, as the


producer/consumer model does not allow for mutation
 to be correctly ordered. This is not enforced but failure
to adhere to this rule can result in race conditions.

Build steps are executed if and only if they produce build


items that are (transitively) needed by other build steps.
Make sure your build step produces a build item,
 otherwise you should probably produce either
ValidationErrorBuildItem for build validations, or
ArtifactResultBuildItem for generated artifacts.

2.3.2.1. Simple build items

Simple build items are final classes which extend


io.quarkus.builder.item.SimpleBuildItem. Simple build items may
only be produced by one step in a given build; if multiple steps in a build
declare that they produce the same simple build item, an error is raised.
Any number of build steps may consume a simple build item. A build step
which consumes a simple build item will always run after the build step
which produced that item.

Example of a single build item

/** 
* The build item which represents the Jandex index of
the application,
* and would normally be used by many build steps to
find usages
* of annotations.
*/
public final class ApplicationIndexBuildItem extends
SimpleBuildItem {

private final Index index;

public ApplicationIndexBuildItem(Index index) {


this.index = index;
}

public Index getIndex() {


return index;
}
}

https://es.quarkus.io/guides/writing-extensions 15/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus

2.3.2.2. Multi build items

Multiple or "multi" build items are final classes which extend


io.quarkus.builder.item.MultiBuildItem. Any number of multi build
items of a given class may be produced by any number of steps, but any
steps which consume multi build items will only run after every step which
can produce them has run.

Example of a multiple build item

public final class ServiceWriterBuildItem extends 


MultiBuildItem {
private final String serviceName;
private final List<String> implementations;

public ServiceWriterBuildItem(String serviceName,


String... implementations) {
this.serviceName = serviceName;
// Make sure it's immutable
this.implementations =
Collections.unmodifiableList(
Arrays.asList(
implementations.clone()
)
);
}

public String getServiceName() {


return serviceName;
}

public List<String> getImplementations() {


return implementations;
}
}

Example of multiple build item usage

/** 
* This build step produces a single multi build item
that declares two
* providers of one configuration-related service.
*/
@BuildStep
public ServiceWriterBuildItem registerOneService() {
return new ServiceWriterBuildItem(
Converter.class.getName(),
MyFirstConfigConverterImpl.class.getName(),
MySecondConfigConverterImpl.class.getName()
);
}

/**
* This build step produces several multi build items
that declare multiple
* providers of multiple configuration-related
services.
*/
@BuildStep

https://es.quarkus.io/guides/writing-extensions 16/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
public void registerSeveralServices(
BuildProducer<ServiceWriterBuildItem>
providerProducer
) {
providerProducer.produce(new
ServiceWriterBuildItem(
Converter.class.getName(),
MyThirdConfigConverterImpl.class.getName(),
MyFourthConfigConverterImpl.class.getName()
));
providerProducer.produce(new
ServiceWriterBuildItem(
ConfigSource.class.getName(),
MyConfigSourceImpl.class.getName()
));
}

/**
* This build step aggregates all the produced service
providers
* and outputs them as resources.
*/
@BuildStep
public void produceServiceFiles(
List<ServiceWriterBuildItem> items,
BuildProducer<GeneratedResourceBuildItem>
resourceProducer
) throws IOException {
// Aggregate all the providers

Map<String, Set<String>> map = new HashMap<>();


for (ServiceWriterBuildItem item : items) {
String serviceName = item.getName();
for (String implName :
item.getImplementations()) {
map.computeIfAbsent(
serviceName,
(k, v) -> new LinkedHashSet<>()
).add(implName);
}
}

// Now produce the resource(s) for the SPI files


for (Map.Entry<String, Set<String>> entry :
map.entrySet()) {
String serviceName = entry.getKey();
try (ByteArrayOutputStream os = new
ByteArrayOutputStream()) {
try (OutputStreamWriter w = new
OutputStreamWriter(os, StandardCharsets.UTF_8)) {
for (String implName :
entry.getValue()) {
w.write(implName);
w.write(System.lineSeparator());
}
w.flush();
}
resourceProducer.produce(
new GeneratedResourceBuildItem(
"META-INF/services/" + serviceName,
os.toByteArray()

https://es.quarkus.io/guides/writing-extensions 17/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
)
);
}
}
}

2.3.2.3. Empty build items

Empty build items are final (usually empty) classes which extend
io.quarkus.builder.item.EmptyBuildItem. They represent build
items that don’t actually carry any data, and allow such items to be
produced and consumed without having to instantiate empty classes. They
cannot themselves be instantiated.

As they cannot be instantiated, they cannot be injected


by any means, nor be returned by a build step (or via a
BuildProducer). To produce an empty build item you
 must annotate the build step with
@Produce(MyEmptyBuildItem.class) and to
consume it by @Consume(MyEmptyBuildItem.class).

Example of an empty build item

public final class NativeImageBuildItem extends 


EmptyBuildItem {
// empty
}

Empty build items can represent "barriers" which can impose ordering
between steps. They can also be used in the same way that popular build
systems use "pseudo-targets", which is to say that the build item can
represent a conceptual goal that does not have a concrete representation.

Example of usage of an empty build item in a "pseudo-target" style

/** 
* Contrived build step that produces the native image
on disk. The main augmentation
* step (which is run by Maven or Gradle) would be
declared to consume this empty item,
* causing this step to be run.
*/
@BuildStep
@Produce(NativeImageBuildItem.class)
void produceNativeImage() {
// ...
// (produce the native image)
// ...
}

Example of usage of an empty build item in a "barrier" style

/** 
* This would always run after {@link
#produceNativeImage()} completes, producing

https://es.quarkus.io/guides/writing-extensions 18/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
* an instance of {@code SomeOtherBuildItem}.
*/
@BuildStep
@Consume(NativeImageBuildItem.class)
SomeOtherBuildItem secondBuildStep() {
return new SomeOtherBuildItem("foobar");
}

2.3.2.4. Validation Error build items

They represent build items with validation errors that make the build fail.
These build items are consumed during the initialization of the CDI
container.

Example of usage of an validation error build item in a "pseudo-target" style

@BuildStep 
void checkCompatibility(Capabilities capabilities,
BuildProducer<ValidationErrorBuildItem>
validationErrors) {
if
(capabilities.isMissing(Capability.RESTEASY_REACTIVE)
&&
capabilities.isMissing(Capability.RESTEASY_CLASSIC)) {
validationErrors.produce(new
ValidationErrorBuildItem(
new ConfigurationException("Cannot use
both RESTEasy Classic and Reactive extensions at the
same time")));
}
}

2.3.2.5. Artifact Result build items

They represent build items containing the runnable artifact generated by


the build, such as an uberjar or thin jar. These build items can also be used
to always execute a build step without needing to produce anything.

Example of build step that is always executed in a "pseudo-target" style

@BuildStep 
@Produce(ArtifactResultBuildItem.class)
void runBuildStepThatProducesNothing() {
// ...
}

2.3.3. Injection

Classes which contain build steps support the following types of injection:

Constructor parameter injection


Field injection
Method parameter injection (for build step methods only)

https://es.quarkus.io/guides/writing-extensions 19/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
Build step classes are instantiated and injected for each build step
invocation, and are discarded afterwards. State should only be
communicated between build steps by way of build items, even if the steps
are on the same class.

Final fields are not considered for injection, but can be


 populated by way of constructor parameter injection if
desired. Static fields are never considered for injection.

The types of values that can be injected include:

Build items produced by previous build steps


Build producers to produce items for subsequent build steps
Configuration Mapping types
Template objects for bytecode recording

Objects which are injected into a build step method or


 its class must not be used outside that method’s
execution.

Injection is resolved at compile time via an annotation


processor, and the resulting code does not have
 permission to inject private fields or invoke private
methods.

2.3.4. Producing values

A build step may produce values for subsequent steps in several possible
ways:

By returning a simple build item or multi build item instance


By returning a List of a multi build item class
By injecting a BuildProducer of a simple or multi build item class
By annotating the method with
@io.quarkus.deployment.annotations.Produce, giving the class
name of an empty build item

If a simple build item is declared on a build step, it must be produced during


that build step, otherwise an error will result. Build producers, which are
injected into steps, must not be used outside that step.

Note that a @BuildStep method will only be called if it produces something


that another consumer or the final output requires. If there is no consumer
for a particular item then it will not be produced. What is required will
depend on the final target that is being produced. For example, when
running in developer mode the final output will not ask for GraalVM-specific
build items such as ReflectiveClassBuildItem, so methods that only
produce these items will not be invoked.

2.3.5. Consuming values

A build step may consume values from previous steps in the following ways:

https://es.quarkus.io/guides/writing-extensions 20/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
By injecting a simple build item
By injecting an Optional of a simple build item class
By injecting a List of a multi build item class
By annotating the method with
@io.quarkus.deployment.annotations.Consume, giving the class
name of an empty build item

Normally it is an error for a step which is included to consume a simple


build item that is not produced by any other step. In this way, it is
guaranteed that all the declared values will be present and non-null when
a step is run.

Sometimes a value isn’t necessary for the build to complete, but might
inform some behavior of the build step if it is present. In this case, the value
can be optionally injected.

Multi build values are always considered optional. If not


 present, an empty list will be injected.

2.3.5.1. Weak value production

Normally a build step is included whenever it produces any build item which
is in turn consumed by any other build step. In this way, only the steps
necessary to produce the final artifact(s) are included, and steps which
pertain to extensions which are not installed or which only produce build
items which are not relevant for the given artifact type are excluded.

For cases where this is not desired behavior, the


@io.quarkus.deployment.annotations.Weak annotation may be used.
This annotation indicates that the build step should not automatically be
included solely on the basis of producing the annotated value.

Example of producing a build item weakly

/** 
* This build step is only run if something consumes
the ExecutorClassBuildItem.
*/
@BuildStep
void createExecutor(
@Weak BuildProducer<GeneratedClassBuildItem>
classConsumer,
BuildProducer<ExecutorClassBuildItem>
executorClassConsumer
) {
ClassWriter cw = new
ClassWriter(Gizmo.ASM_API_VERSION);
String className =
generateClassThatCreatesExecutor(cw); 1
classConsumer.produce(new
GeneratedClassBuildItem(true, className,
cw.toByteArray()));
executorClassConsumer.produce(new
ExecutorClassBuildItem(className));
}

https://es.quarkus.io/guides/writing-extensions 21/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus

This method (not provided in this example) would generate the


1
class using the ASM API.

Certain types of build items are generally always consumed, such as


generated classes or resources. An extension might produce a build item
along with a generated class to facilitate the usage of that build item. Such a
build step would use the @Weak annotation on the generated class build
item, while normally producing the other build item. If the other build item
is ultimately consumed by something, then the step would run and the class
would be generated. If nothing consumes the other build item, the step
would not be included in the build process.

In the example above, GeneratedClassBuildItem would only be


produced if ExecutorClassBuildItem is consumed by some other build
step.

Note that when using bytecode recording, the implicitly generated class can
be declared to be weak by using the optional attribute of the
@io.quarkus.deployment.annotations.Record annotation.

Example of using a bytecode recorder where the generated class is weakly


produced

/** 
* This build step is only run if something consumes
the ExecutorBuildItem.
*/
@BuildStep
@Record(value = ExecutionTime.RUNTIME_INIT, optional =
true) 1
ExecutorBuildItem createExecutor( 2
ExecutorRecorder recorder,
ThreadPoolConfig threadPoolConfig
) {

return new ExecutorBuildItem(


recorder.setupRunTime(
shutdownContextBuildItem,
threadPoolConfig,
launchModeBuildItem.getLaunchMode()
)
);
}

1 Note the optional attribute.

This example is using recorder proxies; see the section on bytecode


2
recording for more information.

2.3.6. Application Archives

The @BuildStep annotation can also register marker files that determine
which archives on the class path are considered to be 'Application Archives',
and will therefore get indexed. This is done via the
applicationArchiveMarkers. For example the ArC extension registers

https://es.quarkus.io/guides/writing-extensions 22/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
META-INF/beans.xml, which means that all archives on the class path with
a beans.xml file will be indexed.

2.3.7. Using Thread’s Context Class Loader

The build step will be run with a TCCL that can load user classes from the
deployment in a transformer-safe way. This class loader only lasts for the
life of the augmentation, and is discarded afterwards. The classes will be
loaded again in a different class loader at runtime. This means that loading
a class during augmentation does not stop it from being transformed when
running in the development/test mode.

2.3.8. Adding external JARs to the indexer with


IndexDependencyBuildItem

The index of scanned classes will not automatically include your external
class dependencies. To add dependencies, create a @BuildStep that
produces IndexDependencyBuildItem objects, for a groupId and
artifactId.

It is important to specify all the required artifacts to be


 added to the indexer. No artifacts are implicitly added
transitively.

The Amazon Alexa extension adds dependent libraries from the Alexa SDK
that are used in Jackson JSON transformations, in order for the reflective
classes to identified and included at BUILD_TIME.

@BuildStep 
void
addDependencies(BuildProducer<IndexDependencyBuildItem>
indexDependency) {
indexDependency.produce(new
IndexDependencyBuildItem("com.amazon.alexa", "ask-
sdk"));
indexDependency.produce(new
IndexDependencyBuildItem("com.amazon.alexa", "ask-sdk-
runtime"));
indexDependency.produce(new
IndexDependencyBuildItem("com.amazon.alexa", "ask-sdk-
model"));
indexDependency.produce(new
IndexDependencyBuildItem("com.amazon.alexa", "ask-sdk-
lambda-support"));
indexDependency.produce(new
IndexDependencyBuildItem("com.amazon.alexa", "ask-sdk-
servlet-support"));
indexDependency.produce(new
IndexDependencyBuildItem("com.amazon.alexa", "ask-sdk-
dynamodb-persistence-adapter"));
indexDependency.produce(new
IndexDependencyBuildItem("com.amazon.alexa", "ask-sdk-
apache-client"));
indexDependency.produce(new
IndexDependencyBuildItem("com.amazon.alexa", "ask-sdk-

https://es.quarkus.io/guides/writing-extensions 23/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
model-runtime"));
}

With the artifacts added to the Jandex indexer, you can now search the
index to identify classes implementing an interface, subclasses of a specific
class, or classes with a target annotation.

For example, the Jackson extension uses code like below to search for
annotations used in JSON deserialization, and add them to the reflective
hierarchy for BUILD_TIME analysis.

DotName JSON_DESERIALIZE = 
DotName.createSimple(JsonDeserialize.class.getName());

IndexView index =
combinedIndexBuildItem.getIndex();

// handle the various @JsonDeserialize cases


for (AnnotationInstance deserializeInstance :
index.getAnnotations(JSON_DESERIALIZE)) {
AnnotationTarget annotationTarget =
deserializeInstance.target();
if (CLASS.equals(annotationTarget.kind())) {
DotName dotName =
annotationTarget.asClass().name();
Type jandexType = Type.create(dotName,
Type.Kind.CLASS);
reflectiveHierarchyClass.produce(new
ReflectiveHierarchyBuildItem(jandexType));
}

2.3.9. Visualizing build step dependencies

It can occasionally be useful to see a visual representation of the


interactions between the various build steps. For such cases, adding -
Dquarkus.builder.graph-output=build.dot when building an
application will result in the creation of the build.dot file in the project’s
root directory. See this for a list of software that can open the file and show
the actual visual representation.

2.4. Configuración
Configuration in Quarkus is based on SmallRye Config. All features provided
by SmallRye Config are also available in Quarkus.

Extensions must use SmallRye Config @ConfigMapping to map the


configuration required by the Extension. This will allow Quarkus to
automatically expose an instance of the mapping to each configuration
phase and generate the configuration documentation.

2.4.1. Config Phases

Configuration mappings are strictly bound by configuration phase, and


attempting to access a configuration mapping from outside its

https://es.quarkus.io/guides/writing-extensions 24/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
corresponding phase will result in an error. They dictate when its contained
keys are read from the configuration, and when they are available to
applications. The phases defined by
io.quarkus.runtime.annotations.ConfigPhase are as follows:

Phase name Read Avail. Read Re-read Notes


& at during during
avail. run static startup
at time init (native
build executable)
time

BUILD_TIME ✓ ✗ ✗ ✗ Appropri
which aff

BUILD_AND_RUN_TIME_FIXED ✓ ✓ ✗ ✗ Appropri
which aff
must be
time cod
config at

BOOTSTRAP ✗ ✓ ✗ ✓ Used wh
configura
obtained
external
Consul),
that syste
configura
Consul’s
level way
using the
Quarkus
(such as
system p
and prod
ConfigS
objects w
subseque
account
when cre
runtime

RUN_TIME ✗ ✓ ✓ ✓ Not avail


read at s

For all cases other than the BUILD_TIME case, the configuration mapping
interface and all the configuration groups and types contained therein must
be located in, or reachable from, the extension’s run time artifact.
Configuration mappings of phase BUILD_TIME may be located in or
reachable from either of the extension’s run time or deployment artifacts.

Bootstrap configuration steps are executed during


runtime-init before any of other runtime steps. This
 means that code executed as part of this step cannot
access anything that gets initialized in runtime init steps
(runtime synthetic CDI beans being one such example).

2.4.2. Configuration Example


https://es.quarkus.io/guides/writing-extensions 25/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus

import io.quarkus.runtime.annotations.ConfigPhase; 
import io.quarkus.runtime.annotations.ConfigRoot;
import io.smallrye.config.ConfigMapping;
import io.smallrye.config.WithDefault;

import java.io.File;
import java.util.logging.Level;

/**
* Logging configuration.
*/
@ConfigMapping(prefix = "quarkus.log") 1
@ConfigRoot(phase = ConfigPhase.RUN_TIME) 2
public interface LogConfiguration {
// ...

/**
* Configuration properties for the logging file
handler.
*/
FileConfig file();

interface FileConfig {
/**
* Enable logging to a file.
*/
@WithDefault("true")
boolean enable();

/**
* The log format.
*/
@WithDefault("%d{yyyy-MM-dd HH:mm:ss,SSS} %h
%N[%i] %-5p [%c{1.}] (%t) %s%e%n")
String format();

/**
* The level of logs to be written into the
file.
*/
@WithDefault("ALL")
Level level();

/**
* The name of the file in which logs will be
written.
*/
@WithDefault("application.log")
File path();
}
}

public class LoggingProcessor { 


// ...

/*
* Logging configuration.
*/

https://es.quarkus.io/guides/writing-extensions 26/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
LogConfiguration config; 3
}

A configuration property name can be split into segments. For example, a


property name like quarkus.log.file.enable can be split into the
following segments:

quarkus - a namespace claimed by Quarkus which is a prefix for


@ConfigMapping interfaces,

log - a name segment which corresponds to the prefix set in the


interface annotated with @ConfigMapping,
file - a name segment which corresponds to the file field in this
class,
enable - a name segment which corresponds to enable field in
FileConfig.

The @ConfigMapping annotation indicates that the interface is a


1 configuration mapping, in this case one which corresponds to a
quarkus.log segment.

The @ConfigRoot annotation indicated to which Config phase, the


2
configuration applies to.

Here the LoggingProcessor injects a LogConfiguration


3
instance automatically by detecting the @ConfigRoot annotation.

A corresponding application.properties for the above example could


be:

quarkus.log.file.enable=true 
quarkus.log.file.level=DEBUG
quarkus.log.file.path=/tmp/debug.log

Since format is not defined in these properties, the default value from
@WithDefault will be used instead.

A configuration mapping name can contain an extra suffix segment for the
case where there are configuration mappings for multiple Config Phases.
Classes which correspond to the BUILD_TIME and
BUILD_AND_RUN_TIME_FIXED may end with BuildTimeConfig or
BuildTimeConfiguration, classes which correspond to the RUN_TIME
phase may end with RuntimeConfig, RunTimeConfig,
RuntimeConfiguration or RunTimeConfiguration while classes which
correspond to the BOOTSTRAP configuration may end with
BootstrapConfig or BootstrapConfiguration.

2.4.3. Configuration Reference Documentation

The configuration is an important part of each extension and therefore


needs to be properly documented. Each configuration property should have
a proper Javadoc comment.

While it is handy to have the documentation available when coding, the


configuration documentation must also be available in the extension guides.
The Quarkus build automatically generates the configuration

https://es.quarkus.io/guides/writing-extensions 27/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
documentation based on the Javadoc comments, but it needs to be explicitly
included in each guide.

2.4.3.1. Writing the documentation

Each configuration property, requires a Javadoc explaining its purpose.

The first sentence should be meaningful and self-


 contained as it is included in the summary table.

While standard Javadoc comments are perfectly fine for simple


documentation (recommended even), AsciiDoc is more suitable for tips,
source code extracts, lists and more:

/** 
* Class name of the Hibernate ORM dialect. The
complete list of bundled dialects is available in the
*
https://docs.jboss.org/hibernate/stable/orm/javadocs/or
g/hibernate/dialect/package-summary.html[Hibernate ORM
JavaDoc].
*
* [NOTE]
* ====
* Not all the dialects are supported in GraalVM native
executables: we currently provide driver extensions for
* PostgreSQL, MariaDB, Microsoft SQL Server and H2.
* ====
*
* @asciidoclet
*/
Optional<String> dialect();

To use AsciiDoc, the Javadoc comment must be annotated with


@asciidoclet tag. This tag serves two purposes: it is used as a marker for
Quarkus generation tool, but it is also used by the javadoc process for the
Javadoc generation.

A more detailed example:

// @formatter:off 
/**
* Name of the file containing the SQL statements to
execute when Hibernate ORM starts.
* Its default value differs depending on the Quarkus
launch mode:
*
* * In dev and test modes, it defaults to
`import.sql`.
* Simply add an `import.sql` file in the root of
your resources directory
* and it will be picked up without having to set
this property.
* Pass `no-file` to force Hibernate ORM to ignore
the SQL import file.
* * In production mode, it defaults to `no-file`.

https://es.quarkus.io/guides/writing-extensions 28/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
* It means Hibernate ORM won't try to execute any
SQL import file by default.
* Pass an explicit value to force Hibernate ORM to
execute the SQL import file.
*
* If you need different SQL statements between dev
mode, test (`@QuarkusTest`) and in production, use
Quarkus
* https://quarkus.io/guides/config#configuration-
profiles[configuration profiles facility].
*
* [source,property]
* .application.properties
* ----
* %dev.quarkus.hibernate-orm.sql-load-script = import-
dev.sql
* %test.quarkus.hibernate-orm.sql-load-script =
import-test.sql
* %prod.quarkus.hibernate-orm.sql-load-script = no-
file
* ----
*
* [NOTE]
* ====
* Quarkus supports `.sql` file with SQL statements or
comments spread over multiple lines.
* Each SQL statement must be terminated by a
semicolon.
* ====
*
* @asciidoclet
*/
// @formatter:on
Optional<String> sqlLoadScript();

For indentation to be respected in the Javadoc comment (list items spread


on multiple lines or indented source code), the automatic Eclipse formatter
must be disabled (the formatter is automatically included in the build), with
the markers // @formatter:off/// @formatter:on. These require
separate comments and a mandatory space after the // marker.

Open blocks (--) are not supported in the AsciiDoc


 documentation. All the other types of blocks (source,
admonitions…​) are supported.

 By default, the documentation generator will use the


hyphenated field name as the key of a java.util.Map.
Use the
io.quarkus.runtime.annotations.ConfigDocMapKey
annotation to override the behaviour.

@ConfigMapping(prefix = "quarkus.some") 
@ConfigRoot
public interface SomeConfig {
/**
* Namespace configuration.
*/

https://es.quarkus.io/guides/writing-extensions 29/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
@WithParentName
@ConfigDocMapKey("cache-name") 1
Map<String, Name> namespace();
}

This will generate a configuration map key named


1 quarkus.some."cache-name" instead of
quarkus.some."namespace".

It is possible to write a textual explanation for the


documentation default value, this is useful when it is
generated: @ConfigDocDefault("explain how this
is generated").

@ConfigDocEnumValue gives a way to explicitly customize


the string displayed in the documentation when listing
accepted values for an enum.

2.4.3.2. Writing section documentation

To generate a configuration section of a given group, use the


@ConfigDocSection annotation:

/** 
* Config group related configuration.
* Amazing introduction here
*/
@ConfigDocSection 1
ConfigGroupConfig configGroup();

This will add a section documentation for the configGroup config


item in the generated documentation. The section title and
introduction will be derived from the javadoc of the configuration
1
item. The first sentence from the javadoc is considered as the
section title and the remaining sentences used as section
introduction.

2.4.3.3. Generating the documentation

To generate the documentation:

Execute ./mvnw -DquicklyDocs


Can be executed globally or in a specific extension directory (e.g.
extensions/mailer).

The documentation is generated in the global


target/asciidoc/generated/config/ located at the root of the project.

2.4.3.4. Including the documentation in the extension guide

To include the generated configuration reference documentation in a guide,


use:

https://es.quarkus.io/guides/writing-extensions 30/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus

include::{generated-dir}/config/quarkus-your- 
extension.adoc[opts=optional, leveloffset=+1]

To include only a specific config group:

include::{generated-dir}/config/hyphenated-config- 
group-class-name-with-runtime-or-deployment-namespace-
replaced-by-config-group-namespace.adoc[opts=optional,
leveloffset=+1]

For example, the io.quarkus.vertx.http.runtime.FormAuthConfig


configuration group will be generated in a file named quarkus-vertx-
http-config-group-form-auth-config.adoc.

A few recommendations:

opts=optional is mandatory to not fail the build if only part of the


configuration documentation has been generated.
The documentation is generated with a title level of 2 (i.e. ==). It may
need an adjustment with leveloffset=+N.
The whole configuration documentation should not be included in the
middle of the guide.

If the guide includes an application.properties example, a tip must be


included just below the code snippet:

[TIP] 
For more information about the extension configuration
please refer to the <<configuration-
reference,Configuration Reference>>.

And at the end of the guide, the extensive configuration documentation:

[[configuration-reference]] 
== Configuration Reference

include::{generated-dir}/config/quarkus-your-
extension.adoc[opts=optional, leveloffset=+1]

All documentation should be generated and validated


 before being committed.

2.5. Conditional Step Inclusion


It is possible to only include a given @BuildStep under certain conditions.
The @BuildStep annotation has two optional parameters: onlyIf and
onlyIfNot. These parameters can be set to one or more classes which
implement BooleanSupplier. The build step will only be included when
the method returns true (for onlyIf) or false (for onlyIfNot).

The condition class can inject configuration mappings as long as they belong
to a build-time phase. Run time configuration is not available for condition
classes.

https://es.quarkus.io/guides/writing-extensions 31/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
The condition class may also inject a value of type
io.quarkus.runtime.LaunchMode. Constructor parameter and field
injection is supported.

An example of a conditional build step

@BuildStep(onlyIf = IsDevMode.class) 
LogCategoryBuildItem enableDebugLogging() {
return new
LogCategoryBuildItem("org.your.quarkus.extension",
Level.DEBUG);
}

static class IsDevMode implements BooleanSupplier {


LaunchMode launchMode;

public boolean getAsBoolean() {


return launchMode == LaunchMode.DEVELOPMENT;
}
}

If you need to make your build step conditional on the


 presence or absence of another extension, you can use
Capabilities for that.

You can also apply a set of conditions to all build steps in a given class with
@BuildSteps:

Class-wide condition for build step with @BuildSteps

@BuildSteps(onlyIf = 
MyDevModeProcessor.IsDevMode.class) 1
class MyDevModeProcessor {

@BuildStep
SomeOutputBuildItem
mainBuildStep(SomeOtherBuildItem input) { 2
return new
SomeOutputBuildItem(input.getValue());
}

@BuildStep
SomeOtherOutputBuildItem
otherBuildStep(SomeOtherInputBuildItem input) { 3
return new
SomeOtherOutputBuildItem(input.getValue());
}

static class IsDevMode implements BooleanSupplier {


LaunchMode launchMode;

public boolean getAsBoolean() {


return launchMode ==
LaunchMode.DEVELOPMENT;
}
}
}

https://es.quarkus.io/guides/writing-extensions 32/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus

This condition will apply to all methods defined in


1
MyDevModeProcessor

2 The main build step will only be executed in dev mode.

3 The other build step will only be executed in dev mode.

2.6. Bytecode Recording


One of the main outputs of the build process is recorded bytecode. This
bytecode actually sets up the runtime environment. For example, in order to
start Undertow, the resulting application will have some bytecode that
directly registers all Servlet instances and then starts Undertow.

As writing bytecode directly is complex, this is instead done via bytecode


recorders. At deployment time, invocations are made on recorder objects
that contain the actual runtime logic, but instead of these invocations
proceeding as normal they are intercepted and recorded (hence the name).
This recording is then used to generate bytecode that performs the same
sequence of invocations at runtime. This is essentially a form of deferred
execution where invocations made at deployment time get deferred until
runtime.

Let’s look at the classic 'Hello World' type example. To do this the Quarkus
way we would create a recorder as follows:

@Recorder 
class HelloRecorder {

public void sayHello(String name) {


System.out.println("Hello" + name);
}

And then create a build step that uses this recorder:

@Record(RUNTIME_INIT) 
@BuildStep
public void helloBuildStep(HelloRecorder recorder) {
recorder.sayHello("World");
}

When this build step is run nothing is printed to the console. This is because
the HelloRecorder that is injected is actually a proxy that records all
invocations. Instead, if we run the resulting Quarkus program we will see
'Hello World' printed to the console.

Methods on a recorder can return a value, which must be proxiable (if you
want to return a non-proxiable item wrap it in
io.quarkus.runtime.RuntimeValue). These proxies may not be invoked
directly, however they can be passed into other recorder methods. This can
be any recorder method, including from other @BuildStep methods, so a
common pattern is to produce BuildItem instances that wrap the results
of these recorder invocations.
https://es.quarkus.io/guides/writing-extensions 33/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
For instance, in order to make arbitrary changes to a Servlet deployment
Undertow has a ServletExtensionBuildItem, which is a
MultiBuildItem that wraps a ServletExtension instance. I can return a
ServletExtension from a recorder in another module, and Undertow will
consume it and pass it into the recorder method that starts Undertow.

At runtime the bytecode will be invoked in the order it is generated. This


means that build step dependencies implicitly control the order that
generated bytecode is run. In the example above we know that the
bytecode that produces a ServletExtensionBuildItem will be run
before the bytecode that consumes it.

The following objects can be passed to recorders:

Primitives
String
Class<?> objects
Objects returned from a previous recorder invocation
Objects with a no-arg constructor and getter/setters for all properties
(or public fields)
Objects with a constructor annotated with @RecordableConstructor
with parameter names that match field names
Any arbitrary object via the
io.quarkus.deployment.recording.RecorderContext#registe
rSubstitution(Class, Class, Class) mechanism
Arrays, Lists and Maps of the above

In cases where some fields of an object to be recorded should be


the value that being at build time should not be reflected at runtim
@IgnoreProperty can be placed on the field.

If the class cannot depend on Quarkus, then Quarkus can use any
 annotation, as long as the extension implements the
io.quarkus.deployment.recording.RecordingAnnotation
SPI.

This same SPI can also be used to provide a custom annotation th


substitute for @RecordableConstructor.

2.6.1. Injecting Configuration into Recorders

Configuration objects with phase RUNTIME or BUILD_AND_RUNTIME_FIXED


can be injected into recorders via constructor injection. Just create a
constructor that takes the configuration objects the recorder needs. If the
recorder has multiple constructors you can annotate the one you want
Quarkus to use with @Inject. If the recorder wants to inject runtime config
but is also used at static init time then it needs to inject a
RuntimeValue<ConfigObject>, this value will only be set when the
runtime methods are being invoked.

2.6.2. RecorderContext

https://es.quarkus.io/guides/writing-extensions 34/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
io.quarkus.deployment.recording.RecorderContext provides some
convenience methods to enhance bytecode recording, this includes the
ability to register creation functions for classes without no-arg constructors,
to register an object substitution (basically a transformer from a non-
serializable object to a serializable one and vice versa), and to create a class
proxy. This interface can be directly injected as a method parameter into
any @Record method.

Calling classProxy with a given fully-qualified class name will create a


Class instance that can be passed into a recorder method, and at runtime
will be substituted with the class whose name was passed in to
classProxy(). However, this method should not be needed in most use
cases because directly loading deployment/application classes at processing
time in build steps is safe. Therefore, this method is deprecated.
Nonetheless, there are some use cases where this method comes in handy,
such as referring to classes that were generated in previous build steps
using GeneratedClassBuildItem.

2.6.3. Runtime Classpath check

Extensions often need a way to determine whether a given class is part of


the application’s runtime classpath. The proper way for an extension to
perform this check is to use
io.quarkus.bootstrap.classloading.QuarkusClassLoader.isClas
sPresentAtRuntime.

2.6.4. Printing step execution time

At times, it can be useful to know how the exact time each startup task
(which is the result of each bytecode recording) takes when the application
is run. The simplest way to determine this information is to launch the
Quarkus application with the -Dquarkus.debug.print-startup-
times=true system property. The output will look something like:

Build step 
LoggingResourceProcessor.setupLoggingRuntimeInit
completed in: 42ms
Build step
ConfigGenerationBuildStep.checkForBuildTimeConfigChange
completed in: 4ms
Build step SyntheticBeansProcessor.initRuntime
completed in: 0ms
Build step ConfigBuildStep.validateConfigProperties
completed in: 1ms
Build step ResteasyStandaloneBuildStep.boot completed
in: 95ms
Build step VertxHttpProcessor.initializeRouter
completed in: 1ms
Build step VertxHttpProcessor.finalizeRouter completed
in: 4ms
Build step LifecycleEventsBuildStep.startupEvent
completed in: 1ms
Build step VertxHttpProcessor.openSocket completed in:
93ms
Build step ShutdownListenerBuildStep.setupShutdown
completed in: 1ms

https://es.quarkus.io/guides/writing-extensions 35/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus

2.7. Contexts and Dependency Injection

2.7.1. Extension Points

As a CDI based runtime, Quarkus extensions often make CDI beans available
as part of the extension behavior. However, Quarkus DI solution does not
support CDI Portable Extensions. Instead, Quarkus extensions can make use
of various Build Time Extension Points.

2.8. Quarkus Dev UI


You can make your extension support the Quarkus Dev UI for a greater
developer experience.

2.9. Extension-defined endpoints


Your extension can add additional, non-application endpoints to be served
alongside endpoints for Health, Metrics, OpenAPI, Swagger UI, etc.

Use a NonApplicationRootPathBuildItem to define an endpoint:

@BuildStep 
RouteBuildItem
myExtensionRoute(NonApplicationRootPathBuildItem
nonApplicationRootPathBuildItem) {
return
nonApplicationRootPathBuildItem.routeBuilder()
.route("custom-endpoint")
.handler(new MyCustomHandler())
.displayOnNotFoundPage()
.build();
}

Note that the path above does not start with a '/', indicating it is a relative
path. The above endpoint will be served relative to the configured non-
application endpoint root. The non-application endpoint root is /q by
default, which means the resulting endpoint will be found at /q/custom-
endpoint.

Absolute paths are handled differently. If the above called


route("/custom-endpoint"), the resulting endpoint will be found at
/custom-endpoint.

If an extension needs nested non-application endpoints:

@BuildStep 
RouteBuildItem
myNestedExtensionRoute(NonApplicationRootPathBuildItem
nonApplicationRootPathBuildItem) {
return
nonApplicationRootPathBuildItem.routeBuilder()
.nestedRoute("custom-endpoint", "deep")
.handler(new MyCustomHandler())
.displayOnNotFoundPage()

https://es.quarkus.io/guides/writing-extensions 36/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
.build();
}

Given a default non-application endpoint root of /q, this will create an


endpoint at /q/custom-endpoint/deep.

Absolute paths also have an impact on nested endpoints. If the above called
nestedRoute("custom-endpoint", "/deep"), the resulting endpoint
will be found at /deep.

Refer to the Quarkus Vertx HTTP configuration reference for details on how
the non-application root path is configured.

2.10. Extension Health Check


Health checks are provided via the quarkus-smallrye-health extension.
It provides both liveness and readiness checks capabilities.

When writing an extension, it’s beneficial to provide health checks for the
extension, that can be automatically included without the developer
needing to write their own.

In order to provide a health check, you should do the following:

Import the quarkus-smallrye-health extension as an optional


dependency in your runtime module so it will not impact the size of
the application if health check is not included.
Create your health check following the SmallRye Health guide. We
advise providing only readiness check for an extension (liveness check
is designed to express the fact that an application is up and needs to
be lightweight).
Import the quarkus-smallrye-health-spi library in your
deployment module.
Add a build step in your deployment module that produces a
HealthBuildItem.
Add a way to disable the extension health check via a config item
quarkus.<extension>.health.enabled that should be enabled by
default.

Following is an example from the Agroal extension that provides a


DataSourceHealthCheck to validate the readiness of a datasource.

@BuildStep 
HealthBuildItem addHealthCheck(AgroalBuildTimeConfig
agroalBuildTimeConfig) {
return new
HealthBuildItem("io.quarkus.agroal.runtime.health.DataS
ourceHealthCheck",
agroalBuildTimeConfig.healthEnabled);
}

2.11. Extension Metrics


The quarkus-micrometer extension and the quarkus-smallrye-
metrics extension provide support for collecting metrics. As a compatibility

https://es.quarkus.io/guides/writing-extensions 37/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
note, the quarkus-micrometer extension adapts the MP Metrics API to
Micrometer library primitives, so the quarkus-micrometer extension can
be enabled without breaking code that relies on the MP Metrics API. Note
that the metrics emitted by Micrometer are different, see the quarkus-
micrometer extension documentation for more information.

The compatibility layer for MP Metrics APIs will move to


 a different extension in the future.

There are two broad patterns that extensions can use to interact with an
optional metrics extension to add their own metrics:

Consumer pattern: An extension declares a


MetricsFactoryConsumerBuildItem and uses that to provide a
bytecode recorder to the metrics extension. When the metrics
extension has initialized, it will iterate over registered consumers to
initialize them with a MetricsFactory. This factory can be used to
declare API-agnostic metrics, which can be a good fit for extensions
that provide an instrumentable object for gathering statistics (e.g.
Hibernate’s Statistics class).
Binder pattern: An extension can opt to use completely different
gathering implementations depending on the metrics system. An
Optional<MetricsCapabilityBuildItem> metricsCapability
build step parameter can be used to declare or otherwise initialize API-
specific metrics based on the active metrics extension (e.g. "smallrye-
metrics" or "micrometer"). This pattern can be combined with the
consumer pattern by using
MetricsFactory::metricsSystemSupported() to test the active
metrics extension within the recorder.

Remember that support for metrics is optional. Extensions can use an


Optional<MetricsCapabilityBuildItem> metricsCapability
parameter in their build step to test for the presence of an enabled metrics
extension. Consider using additional configuration to control behavior of
metrics. Datasource metrics can be expensive, for example, so additional
configuration flags are used enable metrics collection on individual
datasources.

When adding metrics for your extension, you may find yourself in one of the
following situations:

1. An underlying library used by the extension is using a specific Metrics


API directly (either MP Metrics, Micrometer, or some other).
2. An underlying library uses its own mechanism for collecting metrics
and makes them available at runtime using its own API, e.g.
Hibernate’s Statistics class, or Vert.x MetricsOptions.
3. An underlying library does not provide metrics (or there is no library at
all) and you want to add instrumentation.

2.11.1. Case 1: The library uses a metrics library directly

If the library directly uses a metrics API, there are two options:

Use an Optional<MetricsCapabilityBuildItem>
metricsCapability parameter to test which metrics API is
supported (e.g. "smallrye-metrics" or "micrometer") in your build step,
and use that to selectively declare or initialize API-specific beans or
build items.

https://es.quarkus.io/guides/writing-extensions 38/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
Create a separate build step that consumes a MetricsFactory, and
use the MetricsFactory::metricsSystemSupported() method
within the bytecode recorder to initialize required resources if the
desired metrics API is supported (e.g. "smallrye-metrics" or
"micrometer").
Extensions may need to provide a fallback if there is no active metrics
extension or the extension doesn’t support the API required by the library.

2.11.2. Case 2: The library provides its own metric API

There are two examples of a library providing its own metrics API:

The extension defines an instrumentable object as Agroal does with


io.agroal.api.AgroalDataSourceMetrics, or

The extension provides its own abstraction of metrics, as Jaeger does


with io.jaegertracing.spi.MetricsFactory.

2.11.2.1. Observing instrumentable objects

Let’s take the instrumentable object


(io.agroal.api.AgroalDataSourceMetrics) case first. In this case, you
can do the following:

Define a BuildStep that produces a


MetricsFactoryConsumerBuildItem that uses a RUNTIME_INIT or
STATIC_INIT Recorder to define a MetricsFactory consumer. For
example, the following creates a
MetricsFactoryConsumerBuildItem if and only if metrics are
enabled both for Agroal generally, and for a datasource specifically:

@BuildStep 
@Record(ExecutionTime.RUNTIME_INIT)
void registerMetrics(AgroalMetricsRecorder
recorder,
DataSourcesBuildTimeConfig
dataSourcesBuildTimeConfig,

BuildProducer<MetricsFactoryConsumerBuildItem>
datasourceMetrics,

List<AggregatedDataSourceBuildTimeConfigBuildItem>
aggregatedDataSourceBuildTimeConfigs) {

for
(AggregatedDataSourceBuildTimeConfigBuildItem
aggregatedDataSourceBuildTimeConfig :
aggregatedDataSourceBuildTimeConfigs) {
// Create a MetricsFactory consumer to
register metrics for a data source
// IFF metrics are enabled globally and for
the data source
// (they are enabled for each data source
by default if they are also enabled globally)
if
(dataSourcesBuildTimeConfig.metricsEnabled &&

aggregatedDataSourceBuildTimeConfig.getJdbcConfig()
.enableMetrics.orElse(true)) {

https://es.quarkus.io/guides/writing-extensions 39/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
datasourceMetrics.produce(new
MetricsFactoryConsumerBuildItem(

recorder.registerDataSourceMetrics(aggregatedDataSo
urceBuildTimeConfig.getName())));
}
}
}

The associated recorder should use the provided MetricsFactory to


register metrics. For Agroal, this means using the MetricFactory API
to observe io.agroal.api.AgroalDataSourceMetrics methods.
For example:

/* RUNTIME_INIT */ 
public Consumer<MetricsFactory>
registerDataSourceMetrics(String dataSourceName) {
return new Consumer<MetricsFactory>() {
@Override
public void accept(MetricsFactory
metricsFactory) {
String tagValue =
DataSourceUtil.isDefault(dataSourceName) ?
"default" : dataSourceName;
AgroalDataSourceMetrics metrics =
getDataSource(dataSourceName).getMetrics();

// When using MP Metrics, the builder


uses the VENDOR registry by default.

metricsFactory.builder("agroal.active.count")
.description(
"Number of active
connections. These connections are in use and not
available to be acquired.")
.tag("datasource", tagValue)

.buildGauge(metrics::activeCount);
....

The MetricsFactory provides a fluid builder for registration of metrics,


with the final step constructing gauges or counters based on a Supplier or
ToDoubleFunction. Timers can either wrap Callable, Runnable, or
Supplier implementations, or can use a TimeRecorder to accumulate
chunks of time. The underlying metrics extension will create appropriate
artifacts to observe or measure the defined functions.

2.11.2.2. Using a Metrics API-specific implementation

Using metrics-API specific implementations may be preferred in some cases.


Jaeger, for example, defines its own metrics interface,
io.jaegertracing.spi.MetricsFactory, that it uses to define counters
and gauges. A direct mapping from that interface to the metrics system will
be the most efficient. In this case, it is important to isolate these specialized
implementations and to avoid eager classloading to ensure the metrics API
remains an optional, compile-time dependency.

Optional<MetricsCapabilityBuildItem> metricsCapability can


be used in the build step to selectively control initialization of beans or the

https://es.quarkus.io/guides/writing-extensions 40/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
production of other build items. The Jaeger extension, for example, can use
the following to control initialization of specialized Metrics API adapters:
+

/* RUNTIME_INIT */ 
@BuildStep
@Record(ExecutionTime.RUNTIME_INIT)
void setupTracer(JaegerDeploymentRecorder jdr,
JaegerBuildTimeConfig buildTimeConfig, JaegerConfig
jaeger,
ApplicationConfig appConfig,
Optional<MetricsCapabilityBuildItem> metricsCapability)
{

// Indicates that this extension would like the SSL


support to be enabled
extensionSslNativeSupport.produce(new
ExtensionSslNativeSupportBuildItem(Feature.JAEGER.getNa
me()));

if (buildTimeConfig.enabled) {
// To avoid dependency creep, use two separate
recorder methods for the two metrics systems
if (buildTimeConfig.metricsEnabled &&
metricsCapability.isPresent()) {
if
(metricsCapability.get().metricsSupported(MetricsFactor
y.MICROMETER)) {

jdr.registerTracerWithMicrometerMetrics(jaeger,
appConfig);
} else {
jdr.registerTracerWithMpMetrics(jaeger,
appConfig);
}
} else {
jdr.registerTracerWithoutMetrics(jaeger,
appConfig);
}
}
}

A recorder consuming a MetricsFactory can use


MetricsFactory::metricsSystemSupported() can be used to control
initialization of metrics objects during bytecode recording in a similar way.

2.11.3. Case 3: It is necessary to collect metrics within the extension


code

To define your own metrics from scratch, you have two basic options: Use
the generic MetricFactory builders, or follow the binder pattern, and
create instrumentation specific to the enabled metrics extension.

To use the extension-agnostic MetricFactory API, your processor can


define a BuildStep that produces a
MetricsFactoryConsumerBuildItem that uses a RUNTIME_INIT or
STATIC_INIT Recorder to define a MetricsFactory consumer.

+
https://es.quarkus.io/guides/writing-extensions 41/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus

@BuildStep 
@Record(ExecutionTime.RUNTIME_INIT)
MetricsFactoryConsumerBuildItem
registerMetrics(MyExtensionRecorder recorder) {
return new
MetricsFactoryConsumerBuildItem(recorder.registerMetric
s());
}

+ - The associated recorder should use the provided MetricsFactory to


register metrics, for example

final LongAdder extensionCounter = new LongAdder(); 

/* RUNTIME_INIT */
public Consumer<MetricsFactory> registerMetrics() {
return new Consumer<MetricsFactory>() {
@Override
public void accept(MetricsFactory
metricsFactory) {

metricsFactory.builder("my.extension.counter")

.buildGauge(extensionCounter::longValue);
....

Remember that metrics extensions are optional. Keep metrics-related


initialization isolated from other setup for your extension, and structure
your code to avoid eager imports of metrics APIs. Gathering metrics can also
be expensive. Consider using additional extension-specific configuration to
control behavior of metrics if the presence/absence of metrics support isn’t
sufficient.

2.12. Customizing JSON handling from an


extension
Extensions often need to register serializers and/or deserializers for types
the extension provides.

For this, both Jackson and JSON-B extensions provide a way to register
serializer/deserializer from within an extension deployment module.

Keep in mind that not everybody will need JSON, so you need to make it
optional.

If an extension intends to provide JSON related customization, it is strongly


advised to provide customization for both Jackson and JSON-B.

2.12.1. Customizing Jackson

First, add an optional dependency to quarkus-jackson on your


extension’s runtime module.

https://es.quarkus.io/guides/writing-extensions 42/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
<dependency> 
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jackson</artifactId>
<optional>true</optional>
</dependency>

Then create a serializer or a deserializer (or both) for Jackson, an example of


which can be seen in the mongodb-panache extension.

public class ObjectIdSerializer extends 


StdSerializer<ObjectId> {
public ObjectIdSerializer() {
super(ObjectId.class);
}
@Override
public void serialize(ObjectId objectId,
JsonGenerator jsonGenerator, SerializerProvider
serializerProvider)
throws IOException {
if (objectId != null) {

jsonGenerator.writeString(objectId.toString());
}
}
}

Add a dependency to quarkus-jackson-spi on your extension’s


deployment module.

<dependency> 
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jackson-spi</artifactId>
</dependency>

Add a build step to your processor to register a Jackson module via the
JacksonModuleBuildItem. You need to name your module in a unique
way across all Jackson modules.

@BuildStep 
JacksonModuleBuildItem registerJacksonSerDeser() {
return new
JacksonModuleBuildItem.Builder("ObjectIdModule")

.add(io.quarkus.mongodb.panache.jackson.ObjectIdSeriali
zer.class.getName(),

io.quarkus.mongodb.panache.jackson.ObjectIdDeserializer
.class.getName(),
ObjectId.class.getName())
.build();
}

The Jackson extension will then use the produced build item to register a
module within Jackson automatically.

https://es.quarkus.io/guides/writing-extensions 43/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
If you need more customization capabilities than registering a module, you
can produce a CDI bean that implements
io.quarkus.jackson.ObjectMapperCustomizer via an
AdditionalBeanBuildItem. More info about customizing Jackson can be
found on the JSON guide Configuring JSON support

2.12.2. Customizing JSON-B

First, add an optional dependency to quarkus-jsonb on your extension’s


runtime module.

<dependency> 
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jsonb</artifactId>
<optional>true</optional>
</dependency>

Then create a serializer and/or a deserializer for JSON-B, an example of


which can be seen in the mongodb-panache extension.

public class ObjectIdSerializer implements 


JsonbSerializer<ObjectId> {
@Override
public void serialize(ObjectId obj, JsonGenerator
generator, SerializationContext ctx) {
if (obj != null) {
generator.write(obj.toString());
}
}
}

Add a dependency to quarkus-jsonb-spi on your extension’s deployment


module.

<dependency> 
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jsonb-spi</artifactId>
</dependency>

Add a build step to your processor to register the serializer via the
JsonbSerializerBuildItem.

@BuildStep 
JsonbSerializerBuildItem registerJsonbSerializer() {
return new
JsonbSerializerBuildItem(io.quarkus.mongodb.panache.jso
nb.ObjectIdSerializer.class.getName()));
}

The JSON-B extension will then use the produced build item to register your
serializer/deserializer automatically.

If you need more customization capabilities than registering a serializer or a


deserializer, you can produce a CDI bean that implements

https://es.quarkus.io/guides/writing-extensions 44/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
io.quarkus.jsonb.JsonbConfigCustomizer via an
AdditionalBeanBuildItem. More info about customizing JSON-B can be
found on the JSON guide Configuring JSON support

2.13. Integrating with Development Mode


There are various APIS that you can use to integrate with development
mode, and to get information about the current state.

2.13.1. Handling restarts

When Quarkus is starting the


io.quarkus.deployment.builditem.LiveReloadBuildItem is
guaranteed to be present that gives information about this start, in
particular:

Is this a clean start or a live reload


If this is a live reload which changed files / classes triggered the reload

It also provides a global context map you can use to store information
between restarts, without needing to resort to static fields.

2.13.2. Triggering Live Reload

Live reload is generally triggered by an HTTP request, however not all


applications are HTTP applications and some extensions may want to trigger
live reload based on other events. To do this you need to implement
io.quarkus.dev.spi.HotReplacementSetup in your runtime module,
and add a META-
INF/services/io.quarkus.dev.spi.HotReplacementSetup that lists
your implementation.

On startup the setupHotDeployment method will be called, and you can


use the provided io.quarkus.dev.spi.HotReplacementContext to
initiate a scan for changed files.

2.14. Testing Extensions


Testing of Quarkus extensions should be done with the
io.quarkus.test.QuarkusUnitTest JUnit 5 extension. This extension
allows for Arquillian-style tests that test specific functionalities. It is not
intended for testing user applications, as this should be done via
io.quarkus.test.junit.QuarkusTest. The main difference is that
QuarkusTest simply boots the application once at the start of the run,
while QuarkusUnitTest deploys a custom Quarkus application for each
test class.

These tests should be placed in the deployment module, if additional


Quarkus modules are required for testing their deployment modules should
also be added as test scoped dependencies.

Note that QuarkusUnitTest is in the quarkus-junit5-internal


module.

An example test class may look like:

https://es.quarkus.io/guides/writing-extensions 45/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus

package io.quarkus.health.test; 

import static
org.junit.jupiter.api.Assertions.assertEquals;

import java.util.ArrayList;
import java.util.List;

import jakarta.enterprise.inject.Instance;
import jakarta.inject.Inject;

import org.eclipse.microprofile.health.Liveness;
import org.eclipse.microprofile.health.HealthCheck;
import
org.eclipse.microprofile.health.HealthCheckResponse;
import io.quarkus.test.QuarkusUnitTest;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import
org.junit.jupiter.api.extension.RegisterExtension;

import io.restassured.RestAssured;

public class FailingUnitTest {

@RegisterExtension
1
static final QuarkusUnitTest config = new
QuarkusUnitTest()
.setArchiveProducer(() ->

ShrinkWrap.create(JavaArchive.class)
2

.addClasses(FailingHealthCheck.class)

.addAsManifestResource(EmptyAsset.INSTANCE,
"beans.xml")
);

@Inject
3
@Liveness
Instance<HealthCheck> checks;

@Test
public void testHealthServlet() {

RestAssured.when().get("/q/health").then().statusCode(5
03); 4
}

@Test
public void testHealthBeans() {
List<HealthCheck> check = new ArrayList<>();
5
for (HealthCheck i : checks) {
check.add(i);

https://es.quarkus.io/guides/writing-extensions 46/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
}
assertEquals(1, check.size());
assertEquals(HealthCheckResponse.State.DOWN,
check.get(0).call().getState());
}
}

The QuarkusUnitTest extension must be used with a static field.


1
If used with a non-static field, the test application is not started.

This producer is used to build the application to be tested. It uses


2
Shrinkwrap to create a JavaArchive to test

It is possible to inject beans from our test deployment directly into


3
the test case

This method directly invokes the health check Servlet and verifies
4
the response

This method uses the injected health check bean to verify it is


5
returning the expected result

If you want to test that an extension properly fails at build time, use the
setExpectedException method:

package io.quarkus.hibernate.orm; 

import
io.quarkus.runtime.configuration.ConfigurationException
;
import io.quarkus.test.QuarkusUnitTest;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import
org.junit.jupiter.api.extension.RegisterExtension;

public class PersistenceAndQuarkusConfigTest {

@RegisterExtension
static QuarkusUnitTest runner = new
QuarkusUnitTest()

.setExpectedException(ConfigurationException.class)
1
.withApplicationRoot((jar) -> jar
.addAsManifestResource("META-
INF/some-persistence.xml", "persistence.xml")

.addAsResource("application.properties"));

@Test
public void testPersistenceAndConfigTest() {
// should not be called, deployment exception
should happen first:
// it's illegal to have Hibernate configuration
properties in both the
// application.properties and in the

https://es.quarkus.io/guides/writing-extensions 47/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
persistence.xml
Assertions.fail();
}

This tells JUnit that the Quarkus deployment should fail with a
1
specific exception

2.15. Testing hot reload


It is also possible to write tests that verify an extension works correctly in
development mode and can correctly handle updates.

For most extensions this will just work 'out of the box', however it is still a
good idea to have a smoke test to verify that this functionality is working as
expected. To test this we use QuarkusDevModeTest:

public class ServletChangeTestCase { 

@RegisterExtension
final static QuarkusDevModeTest test = new
QuarkusDevModeTest()
.setArchiveProducer(new Supplier<>() {
@Override
public JavaArchive get() {
return
ShrinkWrap.create(JavaArchive.class) 1
.addClass(DevServlet.class)
.addAsManifestResource(new
StringAsset("Hello Resource"), "resources/file.txt");
}
});

@Test
public void testServletChange() throws
InterruptedException {
RestAssured.when().get("/dev").then()
.statusCode(200)
.body(is("Hello World"));

test.modifySourceFile("DevServlet.java", new
Function<String, String>() { 2

@Override
public String apply(String s) {
return s.replace("Hello World", "Hello
Quarkus");
}
});

RestAssured.when().get("/dev").then()
.statusCode(200)
.body(is("Hello Quarkus"));
}

https://es.quarkus.io/guides/writing-extensions 48/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
@Test
public void testAddServlet() throws
InterruptedException {
RestAssured.when().get("/new").then()
.statusCode(404);

test.addSourceFile(NewServlet.class);
3

RestAssured.when().get("/new").then()
.statusCode(200)
.body(is("A new Servlet"));
}

@Test
public void testResourceChange() throws
InterruptedException {
RestAssured.when().get("/file.txt").then()
.statusCode(200)
.body(is("Hello Resource"));

test.modifyResourceFile("META-
INF/resources/file.txt", new Function<String, String>()
{ 4

@Override
public String apply(String s) {
return "A new resource";
}
});

RestAssured.when().get("file.txt").then()
.statusCode(200)
.body(is("A new resource"));
}

@Test
public void testAddResource() throws
InterruptedException {

RestAssured.when().get("/new.txt").then()
.statusCode(404);

test.addResourceFile("META-
INF/resources/new.txt", "New File"); 5

RestAssured.when().get("/new.txt").then()
.statusCode(200)
.body(is("New File"));

}
}

This starts the deployment, your test can modify it as part of the
1 test suite. Quarkus will be restarted between each test method so
every method starts with a clean deployment.

2 This method allows you to modify the source of a class file. The old
source is passed into the function, and the updated source is

https://es.quarkus.io/guides/writing-extensions 49/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
returned.

This method adds a new class file to the deployment. The source
3 that is used will be the original source that is part of the current
project.

4 This method modifies a static resource

5 This method adds a new static resource

2.16. Native Executable Support


There Quarkus provides a lot of build items that control aspects of the
native executable build. This allows for extensions to programmatically
perform tasks such as registering classes for reflection or adding static
resources to the native executable. Some of these build items are listed
below:

io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem

Includes static resources into the native executable.

io.quarkus.deployment.builditem.nativeimage.NativeImageResourceDirectoryBuildItem

Includes directory’s static resources into the native executable.

io.quarkus.deployment.builditem.nativeimage.RuntimeReinitializedClassBuildItem

A class that will be reinitialized at runtime by Substrate. This will result


in the static initializer running twice.

io.quarkus.deployment.builditem.nativeimage.NativeImageSystemPropertyBuildItem

A system property that will be set at native executable build time.

io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBundleBuildItem

Includes a resource bundle in the native executable.

io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem

Registers a class for reflection in Substrate. Constructors are always


registered, while methods and fields are optional.

io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem

A class that will be initialized at runtime rather than build time. This
will cause the build to fail if the class is initialized as part of the native
executable build process, so care must be taken.

io.quarkus.deployment.builditem.nativeimage.NativeImageConfigBuildItem

A convenience feature that allows you to control most of the above


features from a single build item.

io.quarkus.deployment.builditem.NativeImageEnableAllCharsetsBuildItem

Indicates that all charsets should be enabled in native image.

io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem

https://es.quarkus.io/guides/writing-extensions 50/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
A convenient way to tell Quarkus that the extension requires SSL, and
it should be enabled during native image build. When using this
feature, remember to add your extension to the list of extensions that
offer SSL support automatically on the native and ssl guide.

2.17. IDE support tips

2.17.1. Writing Quarkus extensions in Eclipse

The only particular aspect of writing Quarkus extensions in Eclipse is that


APT (Annotation Processing Tool) is required as part of extension builds,
which means you need to:

Install m2e-apt from https://marketplace.eclipse.org/content/m2e-apt


Define this property in your pom.xml:
<m2e.apt.activation>jdt_apt</m2e.apt.activation>,
although if you rely on io.quarkus:quarkus-build-parent you will
get it for free.
If you have the io.quarkus:quarkus-extension-processor
project open at the same time in your IDE (for example, if you have the
Quarkus sources checked out and open in your IDE) you will need to
close that project. Otherwise, Eclipse will not invoke the APT plugin
that it contains.
If you just closed the extension processor project, be sure to do Maven
> Update Project on the other projects in order for Eclipse to pick
up the extension processor from the Maven repository.

2.18. Troubleshooting / Debugging Tips

2.18.1. Inspecting the Generated/Transformed Classes

Quarkus generates a lot of classes during the build phase and in many cases
also transforms existing classes. It is often extremely useful to see the
generated bytecode and transformed classes during the development of an
extension.

If you set the quarkus.package.decompiler.enabled property to true


then Quarkus will download and invoke the Vineflower decompiler and
dump the result in the decompiled directory of the build tool output
(target/decompiled for Maven for example).

This property only works during a normal production


 build (i.e. not for dev mode/tests) and when fast-jar
packaging type is used (the default behavior).

There are also three system properties that allow you to dump the
generated/transformed classes to the filesystem and inspect them later, for
example via a decompiler in your IDE.

quarkus.debug.generated-classes-dir - to dump the generated


classes, such as bean metadata
quarkus.debug.transformed-classes-dir - to dump the
transformed classes, e.g. Panache entities

https://es.quarkus.io/guides/writing-extensions 51/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
quarkus.debug.generated-sources-dir - to dump the ZIG files;
ZIG file is a textual representation of the generated code that is
referenced in the stack traces
These properties are especially useful in the development mode or when
running the tests where the generated/transformed classes are only held in
memory in a class loader.

For example, you can specify the quarkus.debug.generated-classes-


dir system property to have these classes written out to disk for inspection
in the development mode:

./mvnw quarkus:dev -Dquarkus.debug.generated- 


classes-dir=dump-classes

The property value could be either an absolute path,


such as /home/foo/dump on a Linux machine, or a path
relative to the user working directory, i.e. dump
 corresponds to the {user.dir}/target/dump in the
dev mode and {user.dir}/dump when running the
tests.

You should see a line in the log for each class written to the directory:

INFO [io.qua.run.boo.StartupActionImpl] (main) 


Wrote /path/to/my/app/target/dump-
classes/io/quarkus/arc/impl/ActivateRequestContextInter
ceptor_Bean.class

The property is also honored when running tests:

./mvnw clean test -Dquarkus.debug.generated-classes- 


dir=target/dump-generated-classes

Analogously, you can use the quarkus.debug.transformed-classes-


dir and quarkus.debug.generated-sources-dir properties to dump
the relevant output.

2.18.2. Multi-module Maven Projects and the Development Mode

It’s not uncommon to develop an extension in a multi-module Maven


project that also contains an "example" module. However, if you want to run
the example in the development mode then the -DnoDeps system property
must be used in order to exclude the local project dependencies. Otherwise,
Quarkus attempts to monitor the extension classes and this may result in
weird class loading issues.

./mvnw compile quarkus:dev -DnoDeps 

2.18.3. Indexer does not include your external dependency

Remember to add IndexDependencyBuildItem artifacts to your


@BuildStep.

https://es.quarkus.io/guides/writing-extensions 52/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus

2.19. Sample Test Extension


We have an extension that is used to test for regressions in the extension
processing. It is located in
https://github.com/quarkusio/quarkus/tree/main/integration-tests/test-
extension/extension directory. In this section we touch on some tasks an
extension author will typically need to perform using the test-extension
code to illustrate how the task could be done.

2.19.1. Features and Capabilities

2.19.1.1. Features

A feature represents a functionality provided by an extension. The name of


the feature gets displayed in the log during application bootstrap.

Example Startup Lines

2019-03-22 14:02:37,884 INFO [io.quarkus] (main) 


Quarkus 999-SNAPSHOT started in 0.061s.
2019-03-22 14:02:37,884 INFO [io.quarkus] (main)
Installed features: [cdi, test-extension] 1

1 A list of features installed in the runtime image

A feature can be registered in a Build Step Processors method that


produces a FeatureBuildItem:

TestProcessor#feature()

@BuildStep 
FeatureBuildItem feature() {
return new FeatureBuildItem("test-extension");
}

The name of the feature should only contain lowercase characters, words
are separated by dash; e.g. security-jpa. An extension should provide at
most one feature and the name must be unique. If multiple extensions
register a feature of the same name the build fails.

The feature name should also map to a label in the extension’s


devtools/common/src/main/filtered/extensions.json entry so
that the feature name displayed by the startup line matches a label that one
can use to select the extension when creating a project using the Quarkus
maven plugin as shown in this example taken from the Writing JSON REST
Services guide where the resteasy-reactive-jackson feature is
referenced:

mvn io.quarkus.platform:quarkus-maven- 
plugin:3.7.3:create \
-DprojectGroupId=org.acme \
-DprojectArtifactId=rest-json \
-DclassName="org.acme.rest.json.FruitResource" \
-Dpath="/fruits" \
-Dextensions="resteasy-reactive,resteasy-reactive-

https://es.quarkus.io/guides/writing-extensions 53/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
jackson"
cd rest-json

2.19.1.2. Capabilities

A capability represents a technical capability that can be queried by other


extensions. An extension may provide multiple capabilities and multiple
extensions can provide the same capability. By default, capabilities are not
displayed to users. Capabilities should be used when checking for the
presence of an extension rather than class path based checks.

Capabilities can be registered in a Build Step Processors method that


produces a CapabilityBuildItem:

TestProcessor#capability()

@BuildStep 
void
capabilities(BuildProducer<CapabilityBuildItem>
capabilityProducer) {
capabilityProducer.produce(new
CapabilityBuildItem("org.acme.test-transactions"));
capabilityProducer.produce(new
CapabilityBuildItem("org.acme.test-metrics"));
}

Extensions can consume registered capabilities using the Capabilities


build item:

TestProcessor#doSomeCoolStuff()

@BuildStep 
void doSomeCoolStuff(Capabilities capabilities) {
if
(capabilities.isPresent(Capability.TRANSACTIONS)) {
// do something only if JTA transactions are
in...
}
}

Capabilities should follow the naming conventions of Java packages; e.g.


io.quarkus.security.jpa. Capabilities provided by core extensions
should be listed in the io.quarkus.deployment.Capability enum and
their name should always start with the io.quarkus prefix.

2.19.2. Bean Defining Annotations

The CDI layer processes CDI beans that are either explicitly registered or
that it discovers based on bean defining annotations as defined in 2.5.1.
Bean defining annotations. You can expand this set of annotations to
include annotations your extension processes using a
BeanDefiningAnnotationBuildItem as shown in this
TestProcessor#registerBeanDefinningAnnotations example:

Register a Bean Defining Annotation

https://es.quarkus.io/guides/writing-extensions 54/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus

import jakarta.enterprise.context.ApplicationScoped; 
import org.jboss.jandex.DotName;
import io.quarkus.extest.runtime.TestAnnotation;

public final class TestProcessor {


static DotName TEST_ANNOTATION =
DotName.createSimple(TestAnnotation.class.getName());
static DotName TEST_ANNOTATION_SCOPE =
DotName.createSimple(ApplicationScoped.class.getName())
;

...

@BuildStep
BeanDefiningAnnotationBuildItem registerX() {
1
return new
BeanDefiningAnnotationBuildItem(TEST_ANNOTATION,
TEST_ANNOTATION_SCOPE);
}
...
}

/**
* Marker annotation for test configuration target
beans
*/
@Target({ TYPE })
@Retention(RUNTIME)
@Documented
@Inherited
public @interface TestAnnotation {
}

/**
* A sample bean
*/
@TestAnnotation 2
public class ConfiguredBean implements IConfigConsumer
{

...

Register the annotation class and CDI default scope using the
1
Jandex DotName class.

ConfiguredBean will be processed by the CDI layer the same as a


2
bean annotated with the CDI standard @ApplicationScoped.

2.19.3. Parsing Config to Objects

One of the main things an extension is likely to do is completely separate


the configuration phase of behavior from the runtime phase. Frameworks
often do parsing/load of configuration on startup that can be done during
build time to both reduce the runtime dependencies on frameworks like xml
parsers as well as reducing the startup time the parsing incurs.

https://es.quarkus.io/guides/writing-extensions 55/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
An example of parsing an XML config file using JAXB is shown in the
TestProcessor#parseServiceXmlConfig method:

Parsing an XML Configuration into Runtime XmlConfig Instance

@BuildStep 
@Record(STATIC_INIT)
RuntimeServiceBuildItem
parseServiceXmlConfig(TestRecorder recorder) throws
JAXBException {
RuntimeServiceBuildItem serviceBuildItem =
null;
JAXBContext context =
JAXBContext.newInstance(XmlConfig.class);
Unmarshaller unmarshaller =
context.createUnmarshaller();
InputStream is =
getClass().getResourceAsStream("/config.xml"); 1
if (is != null) {
log.info("Have XmlConfig, loading");
XmlConfig config = (XmlConfig)
unmarshaller.unmarshal(is); 2
...
}
return serviceBuildItem;
}

1 Look for a config.xml classpath resource

2 If found, parse using JAXB context for XmlConfig.class

If there was no /config.xml resource available in the


build environment, then a null
RuntimeServiceBuildItem would be returned and no
 subsequent logic based on a
RuntimeServiceBuildItem being produced would
execute.

Typically, one is loading a configuration to create some runtime


component/service as parseServiceXmlConfig is doing. We will come
back to the rest of the behavior in parseServiceXmlConfig in the
following Manage Non-CDI Service section.

If for some reason you need to parse the config and use it in other build
steps in an extension processor, you would need to create an
XmlConfigBuildItem to pass the parsed XmlConfig instance around.

If you look at the XmlConfig code you will see that it does
carry around the JAXB annotations. If you don’t want
these in the runtime image, you could clone the
 XmlConfig instance into some POJO object graph and
then replace XmlConfig with the POJO class. We will do
this in Replacing Classes in the Native Image.

2.19.4. Scanning Deployments Using Jandex

https://es.quarkus.io/guides/writing-extensions 56/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
If your extension defines annotations or interfaces that mark beans needing
to be processed, you can locate these beans using the Jandex API, a Java
annotation indexer and offline reflection library. The following
TestProcessor#scanForBeans method shows how to find the beans
annotated with our @TestAnnotation that also implement the
IConfigConsumer interface:

Example Jandex Usage

static DotName TEST_ANNOTATION = 


DotName.createSimple(TestAnnotation.class.getName());
...

@BuildStep
@Record(STATIC_INIT)
void scanForBeans(TestRecorder recorder,
BeanArchiveIndexBuildItem beanArchiveIndex, 1
BuildProducer<TestBeanBuildItem>
testBeanProducer) {
IndexView indexView =
beanArchiveIndex.getIndex(); 2
Collection<AnnotationInstance> testBeans =
indexView.getAnnotations(TEST_ANNOTATION); 3
for (AnnotationInstance ann : testBeans) {
ClassInfo beanClassInfo =
ann.target().asClass();
try {
boolean isConfigConsumer =
beanClassInfo.interfaceNames()
.stream()
.anyMatch(dotName ->
dotName.equals(DotName.createSimple(IConfigConsumer.cla
ss.getName()))); 4
if (isConfigConsumer) {
Class<IConfigConsumer> beanClass =
(Class<IConfigConsumer>)
Class.forName(beanClassInfo.name().toString(), false,
Thread.currentThread().getContextClassLoader());
testBeanProducer.produce(new
TestBeanBuildItem(beanClass)); 5
log.infof("Configured bean: %s",
beanClass);
}
} catch (ClassNotFoundException e) {
log.warn("Failed to load bean class",
e);
}
}
}

Depend on a BeanArchiveIndexBuildItem to have the build


1
step be run after the deployment has been indexed.

2 Retrieve the index.

3 Find all beans annotated with @TestAnnotation.

Determine which of these beans also has the IConfigConsumer


4
interface.

https://es.quarkus.io/guides/writing-extensions 57/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus

Save the bean class in a TestBeanBuildItem for use in a latter


5
RUNTIME_INIT build step that will interact with the bean instances.

2.19.5. Interacting With Extension Beans

You can use the io.quarkus.arc.runtime.BeanContainer interface to


interact with your extension beans. The following configureBeans
methods illustrate interacting with the beans scanned for in the previous
section:

Using CDI BeanContainer Interface

// TestProcessor#configureBeans 
@BuildStep
@Record(RUNTIME_INIT)
void configureBeans(TestRecorder recorder,
List<TestBeanBuildItem> testBeans, 1
BeanContainerBuildItem beanContainer, 2
TestRunTimeConfig runTimeConfig) {

for (TestBeanBuildItem testBeanBuildItem :


testBeans) {
Class<IConfigConsumer> beanClass =
testBeanBuildItem.getConfigConsumer();

recorder.configureBeans(beanContainer.getValue(),
beanClass, buildAndRunTimeConfig, runTimeConfig); 3
}
}

// TestRecorder#configureBeans
public void configureBeans(BeanContainer
beanContainer, Class<IConfigConsumer> beanClass,
TestBuildAndRunTimeConfig buildTimeConfig,
TestRunTimeConfig runTimeConfig) {
log.info("Begin BeanContainerListener
callback\n");
IConfigConsumer instance =
beanContainer.beanInstance(beanClass); 4
instance.loadConfig(buildTimeConfig,
runTimeConfig); 5
log.infof("configureBeans, instance=%s\n",
instance);
}

Consume the `TestBeanBuildItem`s produced from the scanning


1
build step.

Consume the BeanContainerBuildItem to order this build step


2
to run after the CDI bean container has been created.

3 Call the runtime recorder to record the bean interactions.

4 Runtime recorder retrieves the bean using its type.

5 Runtime recorder invokes the IConfigConsumer#loadConfig(…​


) method passing in the configuration objects with runtime

https://es.quarkus.io/guides/writing-extensions 58/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
information.

2.19.6. Manage Non-CDI Service

A common purpose for an extension is to integrate a non-CDI aware service


into the CDI based Quarkus runtime. Step 1 of this task is to load any
configuration needed in a STATIC_INIT build step as we did in Parsing Config
to Objects. Now we need to create an instance of the service using the
configuration. Let’s return to the
TestProcessor#parseServiceXmlConfig method to see how this can be
done.

Creating a Non-CDI Service

// TestProcessor#parseServiceXmlConfig 
@BuildStep
@Record(STATIC_INIT)
RuntimeServiceBuildItem
parseServiceXmlConfig(TestRecorder recorder) throws
JAXBException {
RuntimeServiceBuildItem serviceBuildItem =
null;
JAXBContext context =
JAXBContext.newInstance(XmlConfig.class);
Unmarshaller unmarshaller =
context.createUnmarshaller();
InputStream is =
getClass().getResourceAsStream("/config.xml");
if (is != null) {
log.info("Have XmlConfig, loading");
XmlConfig config = (XmlConfig)
unmarshaller.unmarshal(is);
log.info("Loaded XmlConfig, creating
service");
RuntimeValue<RuntimeXmlConfigService>
service = recorder.initRuntimeService(config); 1
serviceBuildItem = new
RuntimeServiceBuildItem(service); 3
}
return serviceBuildItem;
}

// TestRecorder#initRuntimeService
public RuntimeValue<RuntimeXmlConfigService>
initRuntimeService(XmlConfig config) {
RuntimeXmlConfigService service = new
RuntimeXmlConfigService(config); 2
return new RuntimeValue<>(service);
}

// RuntimeServiceBuildItem
final public class RuntimeServiceBuildItem extends
SimpleBuildItem {
private RuntimeValue<RuntimeXmlConfigService>
service;

public
RuntimeServiceBuildItem(RuntimeValue<RuntimeXmlConfigSe

https://es.quarkus.io/guides/writing-extensions 59/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
rvice> service) {
this.service = service;
}

public RuntimeValue<RuntimeXmlConfigService>
getService() {
return service;
}
}

1 Call into the runtime recorder to record the creation of the service.

Using the parsed XmlConfig instance, create an instance of


RuntimeXmlConfigService and wrap it in a RuntimeValue. Use
2
a RuntimeValue wrapper for non-interface objects that are non-
proxiable.

Wrap the return service value in a RuntimeServiceBuildItem for


3
use in a RUNTIME_INIT build step that will start the service.

2.19.6.1. Starting a Service

Now that you have recorded the creation of a service during the build
phase, you need to record how to start the service at runtime during
booting. You do this with a RUNTIME_INIT build step as shown in the
TestProcessor#startRuntimeService method.

Starting/Stopping a Non-CDI Service

// TestProcessor#startRuntimeService 
@BuildStep
@Record(RUNTIME_INIT)
ServiceStartBuildItem
startRuntimeService(TestRecorder recorder,
ShutdownContextBuildItem shutdownContextBuildItem , 1
RuntimeServiceBuildItem serviceBuildItem)
throws IOException { 2
if (serviceBuildItem != null) {
log.info("Registering service start");

recorder.startRuntimeService(shutdownContextBuildItem,
serviceBuildItem.getService()); 3
} else {
log.info("No RuntimeServiceBuildItem seen,
check config.xml");
}
return new
ServiceStartBuildItem("RuntimeXmlConfigService"); 4
}

// TestRecorder#startRuntimeService
public void startRuntimeService(ShutdownContext
shutdownContext, RuntimeValue<RuntimeXmlConfigService>
runtimeValue)
throws IOException {
RuntimeXmlConfigService service =
runtimeValue.getValue();

https://es.quarkus.io/guides/writing-extensions 60/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
service.startService(); 5

shutdownContext.addShutdownTask(service::stopService);
6
}

We consume a ShutdownContextBuildItem to register the service


1
shutdown.

We consume the previously initialized service captured in


2
RuntimeServiceBuildItem.

3 Call the runtime recorder to record the service start invocation.

Produce a ServiceStartBuildItem to indicate the startup of a


4
service. See Startup and Shutdown Events for details.

Runtime recorder retrieves the service instance reference and calls


5
its startService method.

Runtime recorder registers an invocation of the service instance


6
stopService method with the Quarkus ShutdownContext.

The code for the RuntimeXmlConfigService can be viewed here:


RuntimeXmlConfigService.java

The testcase for validating that the RuntimeXmlConfigService has


started can be found in the testRuntimeXmlConfigService test of
ConfiguredBeanTest and NativeImageIT.

2.19.7. Startup and Shutdown Events

The Quarkus container supports startup and shutdown lifecycle events to


notify components of the container startup and shutdown. There are CDI
events fired that components can observe are illustrated in this example:

Observing Container Startup

import io.quarkus.runtime.ShutdownEvent; 
import io.quarkus.runtime.StartupEvent;

public class SomeBean {


/**
* Called when the runtime has started
* @param event
*/
void onStart(@Observes StartupEvent event) { 1
System.out.printf("onStart, event=%s%n",
event);
}

/**
* Called when the runtime is shutting down
* @param event
*/
void onStop(@Observes ShutdownEvent event) { 2
System.out.printf("onStop, event=%s%n", event);

https://es.quarkus.io/guides/writing-extensions 61/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
}
}

1 Observe a StartupEvent to be notified the runtime has started.

Observe a ShutdownEvent to be notified when the runtime is


2
going to shut down.

What is the relevance of startup and shutdown events for extension


authors? We have already seen the use of a ShutdownContext to register a
callback to perform shutdown tasks in the Starting a Service section. These
shutdown tasks would be called after a ShutdownEvent had been sent.

A StartupEvent is fired after all


io.quarkus.deployment.builditem.ServiceStartBuildItem
producers have been consumed. The implication of this is that if an
extension has services that application components would expect to have
been started when they observe a StartupEvent, the build steps that
invoke the runtime code to start those services needs to produce a
ServiceStartBuildItem to ensure that the runtime code is run before
the StartupEvent is sent. Recall that we saw the production of a
ServiceStartBuildItem in the previous section, and it is repeated here
for clarity:

Example of Producing a ServiceStartBuildItem

// TestProcessor#startRuntimeService 
@BuildStep
@Record(RUNTIME_INIT)
ServiceStartBuildItem
startRuntimeService(TestRecorder recorder,
ShutdownContextBuildItem shutdownContextBuildItem,
RuntimeServiceBuildItem serviceBuildItem)
throws IOException {
...
return new
ServiceStartBuildItem("RuntimeXmlConfigService"); 1
}

Produce a ServiceStartBuildItem to indicate that this is a


1 service starting step that needs to run before the StartupEvent is
sent.

2.19.8. Register Resources for Use in Native Image

Not all configuration or resources can be consumed at build time. If you


have classpath resources that the runtime needs to access, you need to
inform the build phase that these resources need to be copied into the
native image. This is done by producing one or more
NativeImageResourceBuildItem or
NativeImageResourceBundleBuildItem in the case of resource bundles.
Examples of this are shown in this sample
registerNativeImageResources build step:

Registering Resources and ResourceBundles


https://es.quarkus.io/guides/writing-extensions 62/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus

public final class MyExtProcessor { 

@BuildStep
void
registerNativeImageResources(BuildProducer<NativeImageR
esourceBuildItem> resource,
BuildProducer<NativeImageResourceBundleBuildItem>
resourceBundle) {
resource.produce(new
NativeImageResourceBuildItem("/security/runtime.keys"))
; 1

resource.produce(new
NativeImageResourceBuildItem(
"META-INF/my-descriptor.xml")); 2

resourceBundle.produce(new
NativeImageResourceBuildItem("jakarta.xml.bind.Messages
")); 3
}
}

Indicate that the /security/runtime.keys classpath resource should


1
be copied into native image.

Indicate that the META-INF/my-descriptor.xml resource should


2
be copied into native image

Indicate that the "jakarta.xml.bind.Messages" resource bundle


3
should be copied into native image.

2.19.9. Service files

If you are using META-INF/services files you need to register the files as
resources so that your native image can find them, but you also need to
register each listed class for reflection so they can be instantiated or
inspected at run-time:

public final class MyExtProcessor { 

@BuildStep
void
registerNativeImageResources(BuildProducer<ServiceProvi
derBuildItem> services) {
String service = "META-INF/services/" +
io.quarkus.SomeService.class.getName();

// find out all the implementation classes


listed in the service files
Set<String> implementations =

ServiceUtil.classNamesNamedIn(Thread.currentThread().ge
tContextClassLoader(),
service);

// register every listed implementation class

https://es.quarkus.io/guides/writing-extensions 63/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
so they can be instantiated
// in native-image at run-time
services.produce(
new
ServiceProviderBuildItem(io.quarkus.SomeService.class.g
etName(),

implementations.toArray(new String[0])));
}
}

ServiceProviderBuildItem takes a list of service


implementation classes as parameters: if you are not
reading them from the service file, make sure that they
 correspond to the service file contents because the
service file will still be read and used at run-time. This is
not a substitute for writing a service file.

This only registers the implementation classes for


instantiation via reflection (you will not be able to
 inspect its fields and methods). If you need to do that,
you can do it this way:

public final class MyExtProcessor { 

@BuildStep
void
registerNativeImageResources(BuildProducer<NativeImageR
esourceBuildItem> resource,

BuildProducer<ReflectiveClassBuildItem>
reflectionClasses) {
String service = "META-INF/services/" +
io.quarkus.SomeService.class.getName();

// register the service file so it is visible


in native-image
resource.produce(new
NativeImageResourceBuildItem(service));

// register every listed implementation class


so they can be inspected/instantiated
// in native-image at run-time
Set<String> implementations =

ServiceUtil.classNamesNamedIn(Thread.currentThread().ge
tContextClassLoader(),
service);
reflectionClasses.produce(
new ReflectiveClassBuildItem(true, true,
implementations.toArray(new String[0])));
}
}

While this is the easiest way to get your services running natively, it’s less
efficient than scanning the implementation classes at build time and

https://es.quarkus.io/guides/writing-extensions 64/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
generating code that registers them at static-init time instead of relying on
reflection.
You can achieve that by adapting the previous build step to use a static-init
recorder instead of registering classes for reflection:

public final class MyExtProcessor { 

@BuildStep
@Record(ExecutionTime.STATIC_INIT)
void registerNativeImageResources(RecorderContext
recorderContext,

SomeServiceRecorder recorder) {
String service = "META-INF/services/" +
io.quarkus.SomeService.class.getName();

// read the implementation classes


Collection<Class<? extends
io.quarkus.SomeService>> implementationClasses = new
LinkedHashSet<>();
Set<String> implementations =
ServiceUtil.classNamesNamedIn(Thread.currentThread().ge
tContextClassLoader(),

service);
for(String implementation : implementations) {
implementationClasses.add((Class<? extends
io.quarkus.SomeService>)

recorderContext.classProxy(implementation));
}

// produce a static-initializer with those


classes
recorder.configure(implementationClasses);
}
}

@Recorder
public class SomeServiceRecorder {

public void configure(List<Class<? extends


io.quarkus.SomeService>> implementations) {
// configure our service statically
SomeServiceProvider serviceProvider =
SomeServiceProvider.instance();
SomeServiceBuilder builder =
serviceProvider.getSomeServiceBuilder();

List<io.quarkus.SomeService> services = new


ArrayList<>(implementations.size());
// instantiate the service implementations
for (Class<? extends io.quarkus.SomeService>
implementationClass : implementations) {
try {

services.add(implementationClass.getConstructor().newIn
stance());
} catch (Exception e) {
throw new
IllegalArgumentException("Unable to instantiate service

https://es.quarkus.io/guides/writing-extensions 65/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
" + implementationClass, e);
}
}

// build our service

builder.withSomeServices(implementations.toArray(new
io.quarkus.SomeService[0]));
ServiceManager serviceManager =
builder.build();

// register it

serviceProvider.registerServiceManager(serviceManager,
Thread.currentThread().getContextClassLoader());
}
}

2.19.10. Object Substitution

Objects created during the build phase that are passed into the runtime
need to have a default constructor in order for them to be created and
configured at startup of the runtime from the build time state. If an object
does not have a default constructor you will see an error similar to the
following during generation of the augmented artifacts:

DSAPublicKey Serialization Error

[error]: Build step 


io.quarkus.deployment.steps.MainClassBuildStep#build
threw an exception: java.lang.RuntimeException: Unable
to serialize objects of type class
sun.security.provider.DSAPublicKeyImpl to bytecode as
it has no default constructor
at
io.quarkus.builder.Execution.run(Execution.java:123)
at
io.quarkus.builder.BuildExecutionBuilder.execute(BuildE
xecutionBuilder.java:136)
at
io.quarkus.deployment.QuarkusAugmentor.run(QuarkusAugme
ntor.java:110)
at
io.quarkus.runner.RuntimeRunner.run(RuntimeRunner.java:
99)
... 36 more

There is a io.quarkus.runtime.ObjectSubstitution interface that can


be implemented to tell Quarkus how to handle such classes. An example
implementation for the DSAPublicKey is shown here:

DSAPublicKeyObjectSubstitution Example

package io.quarkus.extest.runtime.subst; 

import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.DSAPublicKey;
import java.security.spec.InvalidKeySpecException;

https://es.quarkus.io/guides/writing-extensions 66/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
import java.security.spec.X509EncodedKeySpec;
import java.util.logging.Logger;

import io.quarkus.runtime.ObjectSubstitution;

public class DSAPublicKeyObjectSubstitution implements


ObjectSubstitution<DSAPublicKey, KeyProxy> {
private static final Logger log =
Logger.getLogger("DSAPublicKeyObjectSubstitution");
@Override
public KeyProxy serialize(DSAPublicKey obj) { 1

log.info("DSAPublicKeyObjectSubstitution.serialize");
byte[] encoded = obj.getEncoded();
KeyProxy proxy = new KeyProxy();
proxy.setContent(encoded);
return proxy;
}

@Override
public DSAPublicKey deserialize(KeyProxy obj) { 2

log.info("DSAPublicKeyObjectSubstitution.deserialize");
byte[] encoded = obj.getContent();
X509EncodedKeySpec publicKeySpec = new
X509EncodedKeySpec(encoded);
DSAPublicKey dsaPublicKey = null;
try {
KeyFactory kf =
KeyFactory.getInstance("DSA");
dsaPublicKey = (DSAPublicKey)
kf.generatePublic(publicKeySpec);

} catch (NoSuchAlgorithmException |
InvalidKeySpecException e) {
e.printStackTrace();
}
return dsaPublicKey;
}
}

The serialize method takes the object without a default constructor


1 and creates a KeyProxy that contains the information necessary to
recreate the DSAPublicKey.

The deserialize method uses the KeyProxy to recreate the


2
DSAPublicKey from its encoded form using the key factory.

An extension registers this substitution by producing an


ObjectSubstitutionBuildItem as shown in this
TestProcessor#loadDSAPublicKey fragment:

Registering an Object Substitution

@BuildStep 
@Record(STATIC_INIT)
PublicKeyBuildItem loadDSAPublicKey(TestRecorder
recorder,
BuildProducer<ObjectSubstitutionBuildItem>

https://es.quarkus.io/guides/writing-extensions 67/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
substitutions) throws IOException,
GeneralSecurityException {
...
// Register how to serialize DSAPublicKey

ObjectSubstitutionBuildItem.Holder<DSAPublicKey,
KeyProxy> holder = new
ObjectSubstitutionBuildItem.Holder(
DSAPublicKey.class, KeyProxy.class,
DSAPublicKeyObjectSubstitution.class);
ObjectSubstitutionBuildItem keysub = new
ObjectSubstitutionBuildItem(holder);
substitutions.produce(keysub);

log.info("loadDSAPublicKey run");
return new PublicKeyBuildItem(publicKey);
}

2.19.11. Replacing Classes in the Native Image

The Graal SDK supports substitutions of classes in the native image. An


example of how one could replace the XmlConfig/XmlData classes with
versions that have no JAXB annotation dependencies is shown in these
example classes:

Substitution of XmlConfig/XmlData Classes Example

package io.quarkus.extest.runtime.graal; 
import java.util.Date;
import com.oracle.svm.core.annotate.Substitute;
import com.oracle.svm.core.annotate.TargetClass;
import io.quarkus.extest.runtime.config.XmlData;

@TargetClass(XmlConfig.class)
@Substitute
public final class Target_XmlConfig {

@Substitute
private String address;
@Substitute
private int port;
@Substitute
private ArrayList<XData> dataList;

@Substitute
public String getAddress() {
return address;
}

@Substitute
public int getPort() {
return port;
}

@Substitute
public ArrayList<XData> getDataList() {
return dataList;
}

https://es.quarkus.io/guides/writing-extensions 68/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
@Substitute
@Override
public String toString() {
return "Target_XmlConfig{" +
"address='" + address + '\'' +
", port=" + port +
", dataList=" + dataList +
'}';
}
}

@TargetClass(XmlData.class)
@Substitute
public final class Target_XmlData {

@Substitute
private String name;
@Substitute
private String model;
@Substitute
private Date date;

@Substitute
public String getName() {
return name;
}

@Substitute
public String getModel() {
return model;
}

@Substitute
public Date getDate() {
return date;
}

@Substitute
@Override
public String toString() {
return "Target_XmlData{" +
"name='" + name + '\'' +
", model='" + model + '\'' +
", date='" + date + '\'' +
'}';
}
}

3. Ecosystem integration
Some extensions may be private, and some may wish to be part of the
broader Quarkus ecosystem, and available for community re-use. Inclusion
in the Quarkiverse Hub is a convenient mechanism for handling continuous
testing and publication. The Quarkiverse Hub wiki has instructions for on-
boarding your extension.

Alternatively, continuous testing and publication can be handled manually.

https://es.quarkus.io/guides/writing-extensions 69/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus

3.1. Continuous testing of your extension


In order to make it easy for extension authors to test their extensions daily
against the latest snapshot of Quarkus, Quarkus has introduced the notion
of Ecosystem CI. The Ecosystem CI README has all the details on how to set
up a GitHub Actions job to take advantage of this capability, while this video
provides an overview of what the process looks like.

3.2. Publish your extension in registry.quarkus.io


Before publishing your extension to the Quarkus tooling, make sure that the
following requirements are met:

The quarkus-extension.yaml file (in the extension’s runtime/ module)


has the minimum metadata set:
name

description (unless you have it already set in the


runtime/pom.xml's <description> element, which is the
recommended approach)
Your extension is published in Maven Central
Your extension repository is configured to use the Ecosystem CI.

Then you must create a pull request adding a your-extension.yaml file in


the extensions/ directory in the Quarkus Extension Catalog. The YAML
must have the following structure:

group-id: <YOUR_EXTENSION_RUNTIME_GROUP_ID> 
artifact-id: <YOUR_EXTENSION_RUNTIME_ARTIFACT_ID>

When your repository contains multiple extensions, you


 need to create a separate file for each individual
extension, not just one file for the entire repository.

That’s all. Once the pull request is merged, a scheduled job will check Maven
Central for new versions and update the Quarkus Extension Registry.

1. Extension philosophy
1.1. Why an extension framework
1.2. Favor build time work over runtime work
1.3. How to expose configuration
1.4. Expose your components via CDI
1.5. Some types of extensions
2. Technical aspect
2.1. Three Phases of Bootstrap and Quarkus Philosophy
2.2. Project setup
2.3. Build Step Processors
2.4. Configuración
2.5. Conditional Step Inclusion
2.6. Bytecode Recording
2.7. Contexts and Dependency Injection
https://es.quarkus.io/guides/writing-extensions 70/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
2.8. Quarkus Dev UI
2.9. Extension-defined endpoints
2.10. Extension Health Check
2.11. Extension Metrics
2.12. Customizing JSON handling from an extension
2.13. Integrating with Development Mode
2.14. Testing Extensions
2.15. Testing hot reload
2.16. Native Executable Support
2.17. IDE support tips
2.18. Troubleshooting / Debugging Tips
2.19. Sample Test Extension
3. Ecosystem integration
3.1. Continuous testing of your extension
3.2. Publish your extension in registry.quarkus.io

Quarkus Navigation Follow Us Get Languages Quarkus


is open. Help is
Home Twitter English
All made
About Mastodon Support Português(Brasileiro)
dependencies of
of this Blog Facebook Guías Español community
project FAQ projects
Podcast Linkedin 简体中文
are
Events Youtube Primeros日本語 Eclipse
available
pasos Vert.x
under Boletín de GitHub
the noticias Stack SmallRye
Apache Overflow Hibernate
Hoja de ruta
Software Discussions
Security policy Netty
License
Development RESTEasy
2.0 or Uso mailing
compatible Apache
Brand list
license. Camel
Eclipse
This
MicroProfile
website
was built And
with many
Jekyll, is more...
hosted
on
GitHub
Pages

https://es.quarkus.io/guides/writing-extensions 71/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
and is
completely
open
source. If
you want
to make
it better,
fork the
website
and show
us what
you’ve
got.

 CC by 3.0 | Privacy Policy Sponsored by

https://es.quarkus.io/guides/writing-extensions 72/72

You might also like