Quarkus Extension Synthetic Build Item
In the previous post on the AdditionalBeanBuildItem
, we discussed how to reference a bean with the ARC dependency
manager. While useful, there are cases where you need the manager to inject a specific instance of a pre-configured
bean. Since AdditionalBeanBuildItem
only accepts a bean class as a parameter, it cannot be used for this purpose.
This is where the SyntheticBeanBuildItem
comes into play.
Use Case
Imagine your extension needs to provide a configuration as an injectable bean to communicate with a third-party API. You’ll need to instruct Quarkus on which bean instance to inject and with what values.
SyntheticBeanBuildItem
The name of this build item is not very descriptive, and it took me some time to understand its purpose.
In summary, it’s an AdditionalBeanBuildItem
that accepts an instance of a class instead of a class as a parameter,
making it very powerful. To produce a class instance, you first need to provide this instance in the form of a Recorder
.
Recorder
Let’s start by creating our API configuration bean in the runtime
module of our extension:
|
|
Our bean must be “CDI compatible,” meaning it needs to have a default constructor and accessors.
In our case, we use a private default constructor to ensure the class is not used in unintended ways.
The Quarkus Maven plugin checks for injectable beans during the packaging phase. Without this default constructor,
our project build would fail with an Unsatisfied dependency
error. We define our bean with the @ApplicationScoped
scope so that the same instance is always injected.
Now, let’s create the recorder for our Config
class:
When a recorder is declared, Quarkus will execute it during the build phase and record the bytecode produced by
the recorder. During runtime, Quarkus will provide this bytecode directly instead of recalculating it dynamically,
which greatly speeds up startup time. In our example, the recorder returns a RuntimeValue<Config>
object containing
our configuration instance. This RuntimeValue
will be consumed by our SyntheticBeanBuildItem
.
BuildStep
|
|
To configure our SyntheticBuildItem
, we use a method annotated with @Record(ExecutionTime.RUNTIME_INIT)
.
Here, ExecutionTime.STATIC_INIT
won’t work because our recorder won’t be available yet. Next, we retrieve our
API URL via a DevServicesResultBuildItem
, which we will discuss in a future post. Once the API URL is retrieved,
we construct our SyntheticBeanBuildItem
with our recorder. The unremovable
method ensures that our bean instance
will not be removed by Quarkus during the extension’s compilation, even if the bean is not used. Without this,
our bean won’t be accessible in the Maven module importing our extension.
Since we used @Record(ExecutionTime.RUNTIME_INIT)
, we inform Quarkus that our bean instance will be available
during the RuntimeInit
phase using the setRuntimeInit
method. Finally, we pass our config bean instance to
the runtimeValue
method to complete the initialization of our SyntheticBeanBuildItem
.
If you don’t have control over the produced bean class, you can add a missing qualifier using the
.addQualifier(ApplicationScoped.class)
method.
Bean Injection
In a project importing our extension:
We can now inject our configuration bean to use it:
Conclusion
A SyntheticBeanBuildItem
is the dynamic counterpart of the AdditionalBeanBuildItem
. It uses a Recorder
to record the bytecode produced by our instance during Quarkus’s build phase.
This bytecode is provided as-is during the application’s runtime, accelerating its startup.