Create Custom Quarkus Extensions Guide
Create Custom Quarkus Extensions Guide
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.
1. Extension philosophy
This section is a work in progress and gathers the philosophy under which
extensions should be designed and written.
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)
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.
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.
@Singleton 1
public class Echo {
@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:
@Singleton
public class Echo {
@Inject
DataSource dataSource; 1
@Inject
Instance<List<String>> listsOfStrings; 2
//...
}
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.
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
}
}
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 {
@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 {
https://es.quarkus.io/guides/writing-extensions 6/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
@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();
}
"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();
}
https://es.quarkus.io/guides/writing-extensions 7/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
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
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
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.
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.
<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>
<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>
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:
As and example, let’s add a new extension called my-ext to the Quarkus
source tree:
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
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')
}
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
/**
* 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 {
https://es.quarkus.io/guides/writing-extensions 15/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
/**
* 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
https://es.quarkus.io/guides/writing-extensions 17/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
)
);
}
}
}
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.
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.
/**
* 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)
// ...
}
/**
* 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");
}
They represent build items with validation errors that make the build fail.
These build items are consumed during the initialization of the CDI
container.
@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")));
}
}
@BuildStep
@Produce(ArtifactResultBuildItem.class)
void runBuildStepThatProducesNothing() {
// ...
}
2.3.3. Injection
Classes which contain build steps support the following types of injection:
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.
A build step may produce values for subsequent steps in several possible
ways:
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
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.
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.
/**
* 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
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.
/**
* 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
) {
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.
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.
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.
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();
2.4. Configuración
Configuration in Quarkus is based on SmallRye Config. All features provided
by SmallRye Config are also available in Quarkus.
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:
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
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.
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();
}
}
/*
* Logging configuration.
*/
https://es.quarkus.io/guides/writing-extensions 26/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
LogConfiguration config; 3
}
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.
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.
/**
* 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();
// @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();
@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();
}
/**
* Config group related configuration.
* Amazing introduction here
*/
@ConfigDocSection 1
ConfigGroupConfig configGroup();
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]
include::{generated-dir}/config/hyphenated-config-
group-class-name-with-runtime-or-deployment-namespace-
replaced-by-config-group-namespace.adoc[opts=optional,
leveloffset=+1]
A few recommendations:
[TIP]
For more information about the extension configuration
please refer to the <<configuration-
reference,Configuration Reference>>.
[[configuration-reference]]
== Configuration Reference
include::{generated-dir}/config/quarkus-your-
extension.adoc[opts=optional, leveloffset=+1]
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.
@BuildStep(onlyIf = IsDevMode.class)
LogCategoryBuildItem enableDebugLogging() {
return new
LogCategoryBuildItem("org.your.quarkus.extension",
Level.DEBUG);
}
You can also apply a set of conditions to all build steps in a given class 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());
}
https://es.quarkus.io/guides/writing-extensions 32/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
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 {
@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.
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
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.
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.
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
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.
@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.
@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();
}
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.
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.
@BuildStep
HealthBuildItem addHealthCheck(AgroalBuildTimeConfig
agroalBuildTimeConfig) {
return new
HealthBuildItem("io.quarkus.agroal.runtime.health.DataS
ourceHealthCheck",
agroalBuildTimeConfig.healthEnabled);
}
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.
There are two broad patterns that extensions can use to interact with an
optional metrics extension to add their own metrics:
When adding metrics for your extension, you may find yourself in one of the
following situations:
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.
There are two examples of a library providing its own metrics API:
@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())));
}
}
}
/* 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();
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);
....
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)
{
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);
}
}
}
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.
+
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());
}
/* RUNTIME_INIT */
public Consumer<MetricsFactory> registerMetrics() {
return new Consumer<MetricsFactory>() {
@Override
public void accept(MetricsFactory
metricsFactory) {
metricsFactory.builder("my.extension.counter")
.buildGauge(extensionCounter::longValue);
....
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.
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>
jsonGenerator.writeString(objectId.toString());
}
}
}
<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
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jsonb</artifactId>
<optional>true</optional>
</dependency>
<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.
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
It also provides a global context map you can use to store information
between restarts, without needing to resort to static fields.
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;
@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());
}
}
This method directly invokes the health check Servlet and verifies
4
the response
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;
@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
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:
@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.
io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem
io.quarkus.deployment.builditem.nativeimage.NativeImageResourceDirectoryBuildItem
io.quarkus.deployment.builditem.nativeimage.RuntimeReinitializedClassBuildItem
io.quarkus.deployment.builditem.nativeimage.NativeImageSystemPropertyBuildItem
io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBundleBuildItem
io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem
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
io.quarkus.deployment.builditem.NativeImageEnableAllCharsetsBuildItem
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.
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.
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.
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.
You should see a line in the log for each class written to the directory:
https://es.quarkus.io/guides/writing-extensions 52/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
2.19.1.1. Features
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.
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
TestProcessor#capability()
@BuildStep
void
capabilities(BuildProducer<CapabilityBuildItem>
capabilityProducer) {
capabilityProducer.produce(new
CapabilityBuildItem("org.acme.test-transactions"));
capabilityProducer.produce(new
CapabilityBuildItem("org.acme.test-metrics"));
}
TestProcessor#doSomeCoolStuff()
@BuildStep
void doSomeCoolStuff(Capabilities capabilities) {
if
(capabilities.isPresent(Capability.TRANSACTIONS)) {
// do something only if JTA transactions are
in...
}
}
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:
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;
...
@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.
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:
@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;
}
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.
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:
@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);
}
}
}
https://es.quarkus.io/guides/writing-extensions 57/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
// TestProcessor#configureBeans
@BuildStep
@Record(RUNTIME_INIT)
void configureBeans(TestRecorder recorder,
List<TestBeanBuildItem> testBeans, 1
BeanContainerBuildItem beanContainer, 2
TestRunTimeConfig runTimeConfig) {
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);
}
https://es.quarkus.io/guides/writing-extensions 58/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
information.
// 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.
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.
// 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
}
import io.quarkus.runtime.ShutdownEvent;
import io.quarkus.runtime.StartupEvent;
/**
* 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
}
}
// TestProcessor#startRuntimeService
@BuildStep
@Record(RUNTIME_INIT)
ServiceStartBuildItem
startRuntimeService(TestRecorder recorder,
ShutdownContextBuildItem shutdownContextBuildItem,
RuntimeServiceBuildItem serviceBuildItem)
throws IOException {
...
return new
ServiceStartBuildItem("RuntimeXmlConfigService"); 1
}
@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
}
}
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:
@BuildStep
void
registerNativeImageResources(BuildProducer<ServiceProvi
derBuildItem> services) {
String service = "META-INF/services/" +
io.quarkus.SomeService.class.getName();
ServiceUtil.classNamesNamedIn(Thread.currentThread().ge
tContextClassLoader(),
service);
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])));
}
}
@BuildStep
void
registerNativeImageResources(BuildProducer<NativeImageR
esourceBuildItem> resource,
BuildProducer<ReflectiveClassBuildItem>
reflectionClasses) {
String service = "META-INF/services/" +
io.quarkus.SomeService.class.getName();
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:
@BuildStep
@Record(ExecutionTime.STATIC_INIT)
void registerNativeImageResources(RecorderContext
recorderContext,
SomeServiceRecorder recorder) {
String service = "META-INF/services/" +
io.quarkus.SomeService.class.getName();
service);
for(String implementation : implementations) {
implementationClasses.add((Class<? extends
io.quarkus.SomeService>)
recorderContext.classProxy(implementation));
}
@Recorder
public class SomeServiceRecorder {
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);
}
}
builder.withSomeServices(implementations.toArray(new
io.quarkus.SomeService[0]));
ServiceManager serviceManager =
builder.build();
// register it
serviceProvider.registerServiceManager(serviceManager,
Thread.currentThread().getContextClassLoader());
}
}
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:
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;
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;
}
}
@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);
}
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.
https://es.quarkus.io/guides/writing-extensions 69/72
28/2/24, 11:16 Writing Your Own Extension - Quarkus
group-id: <YOUR_EXTENSION_RUNTIME_GROUP_ID>
artifact-id: <YOUR_EXTENSION_RUNTIME_ARTIFACT_ID>
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
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.
https://es.quarkus.io/guides/writing-extensions 72/72