Contexts and Dependency Injection
Quarkus DI solution (also called ArC) is based on the Jakarta Contexts and Dependency Injection 4.0 specification. It implements the CDI Lite specification, with selected improvements on top, and passes the CDI Lite TCK. It does not implement CDI Full. See also the list of supported features and limitations.
If you’re new to CDI then we recommend you to read the Introduction to CDI first. |
Most of the existing CDI code should work just fine but there are some small differences which follow from the Quarkus architecture and goals. |
1. Bean Discovery
Bean discovery in CDI is a complex process which involves legacy deployment
structures and accessibility requirements of the underlying module
architecture. However, Quarkus is using a simplified bean discovery.
There is only single bean archive with the
bean discovery mode annotated
and no visibility boundaries.
The bean archive is synthesized from:
-
the application classes,
-
dependencies that contain a
beans.xml
descriptor (content is ignored), -
dependencies that contain a Jandex index -
META-INF/jandex.idx
, -
dependencies referenced by
quarkus.index-dependency
inapplication.properties
, -
and Quarkus integration code.
Bean classes that don’t have a
bean defining annotation are not discovered. This behavior is
defined by CDI. But producer methods and fields and observer methods are
discovered even if the declaring class is not annotated with a bean defining
annotation (this behavior is different to what is defined in CDI). In fact,
the declaring bean classes are considered annotated with @Dependent
.
Quarkus extensions may declare additional discovery rules. For example,
@Scheduled business methods are registered even if the declaring class is
not annotated with a bean defining annotation.
|
1.1. How to Generate a Jandex Index
A dependency with a Jandex index is automatically scanned for beans. To generate the index just add the following plugin to your build file:
If you can’t modify the dependency, you can still index it by adding
quarkus.index-dependency
entries to your application.properties
:
quarkus.index-dependency.<name>.group-id=
quarkus.index-dependency.<name>.artifact-id=
quarkus.index-dependency.<name>.classifier=(this one is optional)
For example, the following entries ensure that the org.acme:acme-api
dependency is indexed:
quarkus.index-dependency.acme.group-id=org.acme (1)
quarkus.index-dependency.acme.artifact-id=acme-api (2)
1 | Value is a group id for a dependency identified by name acme . |
2 | Value is an artifact id for a dependency identified by name acme . |
1.2. How To Exclude Types and Dependencies from Discovery
It may happen that some beans from third-party libraries do not work
correctly in Quarkus. A typical example is a bean injecting a portable
extension. In such case, it’s possible to exclude types and dependencies
from the bean discovery. The quarkus.arc.exclude-types
property accepts a
list of string values that are used to match classes that should be
excluded.
Value |
Description |
|
Match the fully qualified name of the class |
|
Match classes with package |
|
Match classes where the package starts with |
|
Match the simple name of the class |
quarkus.arc.exclude-types=org.acme.Foo,org.acme.*,Bar (1)(2)(3)
1 | Exclude the type org.acme.Foo . |
2 | Exclude all types from the org.acme package. |
3 | Exclude all types whose simple name is Bar |
It is also possible to exclude a dependency artifact that would be otherwise
scanned for beans. For example, because it contains a beans.xml
descriptor.
quarkus.arc.exclude-dependency.acme.group-id=org.acme (1)
quarkus.arc.exclude-dependency.acme.artifact-id=acme-services (2)
1 | Value is a group id for a dependency identified by name acme . |
2 | Value is an artifact id for a dependency identified by name acme . |
2. Native Executables and Private Members
Quarkus is using GraalVM to build a native executable. One of the limitations of GraalVM is the usage of Reflection. Reflective operations are supported but all relevant members must be registered for reflection explicitly. Those registrations result in a bigger native executable.
And if Quarkus DI needs to access a private member it has to use reflection. That’s why Quarkus users are encouraged not to use private members in their beans. This involves injection fields, constructors and initializers, observer methods, producer methods and fields, disposers and interceptor methods.
How to avoid using private members? You can use package-private modifiers:
@ApplicationScoped
public class CounterBean {
@Inject
CounterService counterService; (1)
void onMessage(@Observes Event msg) { (2)
}
}
1 | A package-private injection field. |
2 | A package-private observer method. |
Or constructor injection:
@ApplicationScoped
public class CounterBean {
private CounterService service;
CounterBean(CounterService service) { (1)
this.service = service;
}
}
1 | A package-private constructor injection. @Inject is optional in this
particular case. |
3. Supported Features and Limitations
The CDI Lite specification is fully supported. The following features from CDI Full are also supported:
-
Decorators
-
Decoration of built-in beans, such as
Event
, is not supported
-
-
BeanManager
-
In addition to the
BeanContainer
methods, the following methods are supported:getInjectableReference()
,resolveDecorators()
-
-
@SessionScoped
-
Only with the Undertow extension; see here for details
-
These additional features are not covered by the CDI Lite TCK. |
4. Non-standard Features
4.1. Eager Instantiation of Beans
4.1.1. Lazy By Default
By default, CDI beans are created lazily, when needed. What exactly "needed" means depends on the scope of a bean.
-
A normal scoped bean (
@ApplicationScoped
,@RequestScoped
, etc.) is needed when a method is invoked upon an injected instance (contextual reference per the specification).In other words, injecting a normal scoped bean will not suffice because a client proxy is injected instead of a contextual instance of the bean.
-
A bean with a pseudo-scope (
@Dependent
and@Singleton
) is created when injected.
@Singleton // => pseudo-scope
class AmazingService {
String ping() {
return "amazing";
}
}
@ApplicationScoped // => normal scope
class CoolService {
String ping() {
return "cool";
}
}
@Path("/ping")
public class PingResource {
@Inject
AmazingService s1; (1)
@Inject
CoolService s2; (2)
@GET
public String ping() {
return s1.ping() + s2.ping(); (3)
}
}
1 | Injection triggers the instantiation of AmazingService . |
2 | Injection itself does not result in the instantiation of CoolService . A
client proxy is injected. |
3 | The first invocation upon the injected proxy triggers the instantiation of
CoolService . |
4.1.2. Startup Event
However, if you really need to instantiate a bean eagerly you can:
-
Declare an observer of the
StartupEvent
- the scope of the bean does not matter in this case:@ApplicationScoped class CoolService { void startup(@Observes StartupEvent event) { (1) } }
1 A CoolService
is created during startup to service the observer method invocation. -
Use the bean in an observer of the
StartupEvent
- normal scoped beans must be used as described in Lazy By Default:@Dependent class MyBeanStarter { void startup(@Observes StartupEvent event, AmazingService amazing, CoolService cool) { (1) cool.toString(); (2) } }
1 The AmazingService
is created during injection.2 The CoolService
is a normal scoped bean, so we have to invoke a method upon the injected proxy to force the instantiation. -
Annotate the bean with
@io.quarkus.runtime.Startup
as described in Startup annotation:@Startup (1) @ApplicationScoped public class EagerAppBean { private final String name; EagerAppBean(NameGenerator generator) { (2) this.name = generator.createName(); } }
1 For each bean annotated with @Startup
a synthetic observer ofStartupEvent
is generated. The default priority is used.2 The bean constructor is called when the application starts and the resulting contextual instance is stored in the application context.
Quarkus users are encouraged to always prefer the @Observes StartupEvent
to @Initialized(ApplicationScoped.class) as explained in the
Application Initialization and Termination guide.
|
4.2. Request Context Lifecycle
The request context is also active:
-
during notification of a synchronous observer method.
The request context is destroyed:
-
after the observer notification completes for an event, if it was not already active when the notification started.
An event with qualifier @Initialized(RequestScoped.class) is fired when
the request context is initialized for an observer notification. Moreover,
the events with qualifiers @BeforeDestroyed(RequestScoped.class) and
@Destroyed(RequestScoped.class) are fired when the request context is
destroyed.
|
4.2.1. How to Enable Trace Logging for Request Context Activation
You can set the TRACE
level for the logger io.quarkus.arc.requestContext
and try to analyze the log output afterwards.
application.properties
Examplequarkus.log.category."io.quarkus.arc.requestContext".min-level=TRACE (1)
quarkus.log.category."io.quarkus.arc.requestContext".level=TRACE
1 | You also need to adjust the minimum log level for the relevant category. |
4.3. Qualified Injected Fields
In CDI, if you declare a field injection point you need to use @Inject
and
optionally a set of qualifiers.
@Inject
@ConfigProperty(name = "cool")
String coolProperty;
In Quarkus, you can skip the @Inject
annotation completely if the injected
field declares at least one qualifier.
@ConfigProperty(name = "cool")
String coolProperty;
With the notable exception of one special case discussed below, @Inject is
still required for constructor and method injection.
|
4.4. Simplified Constructor Injection
In CDI, a normal scoped bean must always declare a no-args constructor (this constructor is normally generated by the compiler unless you declare any other constructor). However, this requirement complicates constructor injection - you need to provide a dummy no-args constructor to make things work in CDI.
@ApplicationScoped
public class MyCoolService {
private SimpleProcessor processor;
MyCoolService() { // dummy constructor needed
}
@Inject // constructor injection
MyCoolService(SimpleProcessor processor) {
this.processor = processor;
}
}
There is no need to declare dummy constructors for normal scoped bean in
Quarkus - they are generated automatically. Also, if there’s only one
constructor there is no need for @Inject
.
@ApplicationScoped
public class MyCoolService {
private SimpleProcessor processor;
MyCoolService(SimpleProcessor processor) {
this.processor = processor;
}
}
We don’t generate a no-args constructor automatically if a bean class extends a class that does not declare a no-args constructor. |
4.5. Removing Unused Beans
The container attempts to remove all unused beans, interceptors and
decorators during build by default. This optimization helps to minimize the
amount of generated classes, thus conserving memory. However, Quarkus can’t
detect the programmatic lookup performed via the CDI.current()
static
method. Therefore, it is possible that a removal results in a false
positive error, i.e. a bean is removed although it’s actually used. In such
cases, you’ll notice a big warning in the log. Users and extension authors
have several options how to eliminate false
positives.
The optimization can be disabled by setting
quarkus.arc.remove-unused-beans
to none
or false
. Quarkus also
provides a middle ground where application beans are never removed whether
or not they are unused, while the optimization proceeds normally for non
application classes. To use this mode, set
quarkus.arc.remove-unused-beans
to fwk
or framework
.
4.5.1. What’s Removed?
Quarkus first identifies so-called unremovable beans that form the roots
in the dependency tree. A good example is a Jakarta REST resource class or
a bean which declares a @Scheduled
method.
An unremovable bean:
-
is excluded from removal by an extension, or
-
has a name designated via
@Named
, or -
declares an observer method.
An unused bean:
-
is not unremovable, and
-
is not eligible for injection to any injection point in the dependency tree, and
-
does not declare any producer which is eligible for injection to any injection point in the dependency tree, and
-
is not eligible for injection into any
jakarta.enterprise.inject.Instance
orjakarta.inject.Provider
injection point.
Unused interceptors and decorators are not associated with any bean.
When using the dev mode (running
|
4.5.2. How To Eliminate False Positives
Users can instruct the container to not remove any of their specific beans
(even if they satisfy all the rules specified above) by annotating them with
@io.quarkus.arc.Unremovable
. This annotation can be declared on a class,
a producer method or field.
Since this is not always possible, there is an option to achieve the same
via application.properties
. The quarkus.arc.unremovable-types
property
accepts a list of string values that are used to match beans based on their
name or package.
Value |
Description |
|
Match the fully qualified name of the bean class |
|
Match beans where the package of the bean class is |
|
Match beans where the package of the bean class starts with |
|
Match the simple name of the bean class |
quarkus.arc.unremovable-types=org.acme.Foo,org.acme.*,Bar
Furthermore, extensions can eliminate false positives by producing an
UnremovableBeanBuildItem
.
4.6. Default Beans
Quarkus adds a capability that CDI currently does not support which is to
conditionally declare a bean if no other bean with equal types and
qualifiers was declared by any available means (bean class, producer,
synthetic bean, …) This is done using the @io.quarkus.arc.DefaultBean
annotation and is best explained with an example.
Say there is a Quarkus extension that among other things declares a few CDI beans like the following code does:
@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
}
}
The idea is that the extension autoconfigures things for the user,
eliminating a lot of boilerplate - we can just @Inject
a Tracer
wherever
it is needed. Now imagine that in our application we would like to utilize
the configured Tracer
, but we need to customize it a little, for example
by providing a custom Reporter
. The only thing that would be needed in
our application would be something like the following:
@Dependent
public class CustomTracerConfiguration {
@Produces
public Reporter reporter(){
// create a custom Reporter
}
}
@DefaultBean
allows extensions (or any other code for that matter) to
provide defaults while backing off if beans of that type are supplied in any
way Quarkus supports.
4.7. Enabling Beans for Quarkus Build Profile
Quarkus adds a capability that CDI currently does not support which is to
conditionally enable a bean when a Quarkus build time profile is enabled,
via the @io.quarkus.arc.profile.IfBuildProfile
and
@io.quarkus.arc.profile.UnlessBuildProfile
annotations. When used in
conjunction with @io.quarkus.arc.DefaultBean
, these annotations allow for
the creation of different bean configurations for different build profiles.
Imagine for instance that an application contains a bean named Tracer
,
which needs to do nothing when in tests or in dev mode, but works in its
normal capacity for the production artifact. An elegant way to create such
beans is the following:
@Dependent
public class TracerConfiguration {
@Produces
@IfBuildProfile("prod")
public Tracer realTracer(Reporter reporter, Configuration configuration) {
return new RealTracer(reporter, configuration);
}
@Produces
@DefaultBean
public Tracer noopTracer() {
return new NoopTracer();
}
}
If instead, it is required that the Tracer
bean also works in dev mode and
only default to doing nothing for tests, then @UnlessBuildProfile
would be
ideal. The code would look like:
@Dependent
public class TracerConfiguration {
@Produces
@UnlessBuildProfile("test") // this will be enabled for both prod and dev build time profiles
public Tracer realTracer(Reporter reporter, Configuration configuration) {
return new RealTracer(reporter, configuration);
}
@Produces
@DefaultBean
public Tracer noopTracer() {
return new NoopTracer();
}
}
The runtime profile has absolutely no effect on the bean resolution using
@IfBuildProfile and @UnlessBuildProfile .
|
4.8. Enabling Beans for Quarkus Build Properties
Quarkus adds a capability that CDI currently does not support which is to
conditionally enable a bean when a Quarkus build time property has/has not a
specific value, via the @io.quarkus.arc.properties.IfBuildProperty
and
@io.quarkus.arc.properties.UnlessBuildProperty
annotations. When used in
conjunction with @io.quarkus.arc.DefaultBean
, this annotation allow for
the creation of different bean configurations for different build
properties.
The scenario we mentioned above with Tracer
could also be implemented in
the following way:
@Dependent
public class TracerConfiguration {
@Produces
@IfBuildProperty(name = "some.tracer.enabled", stringValue = "true")
public Tracer realTracer(Reporter reporter, Configuration configuration) {
return new RealTracer(reporter, configuration);
}
@Produces
@DefaultBean
public Tracer noopTracer() {
return new NoopTracer();
}
}
@IfBuildProperty and @UnlessBuildProperty are repeatable annotations,
i.e. a bean will only be enabled if all the conditions defined by these
annotations are satisfied.
|
If instead, it is required that the RealTracer
bean is only used if the
some.tracer.enabled
property is not false
, then @UnlessBuildProperty
would be ideal. The code would look like:
@Dependent
public class TracerConfiguration {
@Produces
@UnlessBuildProperty(name = "some.tracer.enabled", stringValue = "false")
public Tracer realTracer(Reporter reporter, Configuration configuration) {
return new RealTracer(reporter, configuration);
}
@Produces
@DefaultBean
public Tracer noopTracer() {
return new NoopTracer();
}
}
Properties set at runtime have absolutely no effect on the bean resolution
using @IfBuildProperty .
|
4.9. Declaring Selected Alternatives
In CDI, an alternative bean may be selected either globally for an
application by means of @Priority
, or for a bean archive using a
beans.xml
descriptor. Quarkus has a simplified bean discovery and the
content of beans.xml
is ignored.
However, it is also possible to select alternatives for an application using
the unified configuration. The quarkus.arc.selected-alternatives
property
accepts a list of string values that are used to match alternative beans.
If any value matches then the priority of Integer#MAX_VALUE
is used for
the relevant bean. The priority declared via @Priority
or inherited from
a stereotype is overridden.
Value |
Description |
|
Match the fully qualified name of the bean class or the bean class of the bean that declares the producer |
|
Match beans where the package of the bean class is |
|
Match beans where the package of the bean class starts with |
|
Match the simple name of the bean class or the bean class of the bean that declares the producer |
quarkus.arc.selected-alternatives=org.acme.Foo,org.acme.*,Bar
4.10. Simplified Producer Method Declaration
In CDI, a producer method must be always annotated with @Produces
.
class Producers {
@Inject
@ConfigProperty(name = "cool")
String coolProperty;
@Produces
@ApplicationScoped
MyService produceService() {
return new MyService(coolProperty);
}
}
In Quarkus, you can skip the @Produces
annotation completely if the
producer method is annotated with a scope annotation, a stereotype or a
qualifier.
class Producers {
@ConfigProperty(name = "cool")
String coolProperty;
@ApplicationScoped
MyService produceService() {
return new MyService(coolProperty);
}
}
4.11. Interception of Static Methods
The Interceptors specification is clear that around-invoke methods must not be declared static. However, this restriction was driven mostly by technical limitations. And since Quarkus is a build-time oriented stack that allows for additional class transformations, those limitations don’t apply anymore. It’s possible to annotate a non-private static method with an interceptor binding:
class Services {
@Logged (1)
static BigDecimal computePrice(long amount) { (2)
BigDecimal price;
// Perform computations...
return price;
}
}
1 | Logged is an interceptor binding. |
2 | Each method invocation is intercepted if there is an interceptor associated
with Logged . |
4.11.1. Limitations
-
Only method-level bindings are considered for backward compatibility reasons (otherwise static methods of bean classes that declare class-level bindings would be suddenly intercepted)
-
Private static methods are never intercepted
-
InvocationContext#getTarget()
returnsnull
for obvious reasons; therefore not all existing interceptors may behave correctly when intercepting static methodsInterceptors can use InvocationContext.getMethod()
to detect static methods and adjust the behavior accordingly.
4.12. Ability to handle 'final' classes and methods
In normal CDI, classes that are marked as final
and / or have final
methods are not eligible for proxy creation, which in turn means that
interceptors and normal scoped beans don’t work properly. This situation is
very common when trying to use CDI with alternative JVM languages like
Kotlin where classes and methods are final
by default.
Quarkus however, can overcome these limitations when
quarkus.arc.transform-unproxyable-classes
is set to true
(which is the
default value).
4.13. Container-managed Concurrency
There is no standard concurrency control mechanism for CDI beans.
Nevertheless, a bean instance can be shared and accessed concurrently from
multiple threads. In that case it should be thread-safe. You can use
standard Java constructs (volatile
, synchronized
, ReadWriteLock
, etc.)
or let the container control the concurrent access. Quarkus provides
@io.quarkus.arc.Lock
and a built-in interceptor for this interceptor
binding. Each interceptor instance associated with a contextual instance of
an intercepted bean holds a separate ReadWriteLock
with non-fair ordering
policy.
io.quarkus.arc.Lock is a regular interceptor binding and as such can be
used for any bean with any scope. However, it is especially useful for
"shared" scopes, e.g. @Singleton and @ApplicationScoped .
|
import io.quarkus.arc.Lock;
@Lock (1)
@ApplicationScoped
class SharedService {
void addAmount(BigDecimal amount) {
// ...changes some internal state of the bean
}
@Lock(value = Lock.Type.READ, time = 1, unit = TimeUnit.SECONDS) (2) (3)
BigDecimal getAmount() {
// ...it is safe to read the value concurrently
}
}
1 | @Lock (which maps to @Lock(Lock.Type.WRITE) ) declared on the class
instructs the container to lock the bean instance for any invocation of any
business method, i.e. the client has "exclusive access" and no concurrent
invocations will be allowed. |
2 | @Lock(Lock.Type.READ) overrides the value specified at class level. It
means that any number of clients can invoke the method concurrently, unless
the bean instance is locked by @Lock(Lock.Type.WRITE) . |
3 | You can also specify the "wait time". If it’s not possible to acquire the
lock in the given time a LockException is thrown. |
4.14. Repeatable interceptor bindings
Quarkus has limited support for @Repeatable
interceptor binding
annotations.
When binding an interceptor to a component, you can declare multiple
@Repeatable
annotations on methods. Repeatable interceptor bindings
declared on classes and stereotypes are not supported, because there are
some open questions around interactions with the Interceptors
specification. This might be added in the future.
As an example, suppose we have an interceptor that clears a cache. The
corresponding interceptor binding would be called @CacheInvalidateAll
and
would be declared as @Repeatable
. If we wanted to clear two caches at the
same time, we would add @CacheInvalidateAll
twice:
@ApplicationScoped
class CachingService {
@CacheInvalidateAll(cacheName = "foo")
@CacheInvalidateAll(cacheName = "bar")
void heavyComputation() {
// ...
// some computation that updates a lot of data
// and requires 2 caches to be invalidated
// ...
}
}
This is how interceptors are used. What about creating an interceptor?
When declaring interceptor bindings of an interceptor, you can add multiple
@Repeatable
annotations to the interceptor class as usual. This is
useless when the annotation members are @Nonbinding
, as would be the case
for the @Cached
annotation, but is important otherwise.
For example, suppose we have an interceptor that can automatically log
method invocations to certain targets. The interceptor binding annotation
@Logged
would have a member called target
, which specifies where to
store the log. Our implementation could be restricted to console logging
and file logging:
@Interceptor
@Logged(target = "console")
@Logged(target = "file")
class NaiveLoggingInterceptor {
// ...
}
Other interceptors could be provided to log method invocations to different targets.
4.15. Caching the Result of Programmatic Lookup
In certain situations, it is practical to obtain a bean instance
programmatically via an injected jakarta.enterprise.inject.Instance
and
Instance.get()
. However, according to the specification the get()
method must identify the matching bean and obtain a contextual reference.
As a consequence, a new instance of a @Dependent
bean is returned from
each invocation of get()
. Moreover, this instance is a dependent object
of the injected Instance
. This behavior is well-defined, but it may lead
to unexpected errors and memory leaks. Therefore, Quarkus comes with the
io.quarkus.arc.WithCaching
annotation. An injected Instance
annotated
with this annotation will cache the result of the Instance#get()
operation. The result is computed on the first call and the same value is
returned for all subsequent calls, even for @Dependent
beans.
class Producer {
AtomicLong nextLong = new AtomicLong();
AtomicInteger nextInt = new AtomicInteger();
@Dependent
@Produces
Integer produceInt() {
return nextInt.incrementAndGet();
}
@Dependent
@Produces
Long produceLong() {
return nextLong.incrementAndGet();
}
}
class Consumer {
@Inject
Instance<Long> longInstance;
@Inject
@WithCaching
Instance<Integer> intInstance;
// this method should always return true
// Producer#produceInt() is only called once
boolean pingInt() {
return intInstance.get().equals(intInstance.get());
}
// this method should always return false
// Producer#produceLong() is called twice per each pingLong() invocation
boolean pingLong() {
return longInstance.get().equals(longInstance.get());
}
}
It is also possible to clear the cached value via
io.quarkus.arc.InjectableInstance.clearCache() . In this case, you’ll need
to inject the Quarkus-specific io.quarkus.arc.InjectableInstance instead
of jakarta.enterprise.inject.Instance .
|
4.16. Declaratively Choose Beans That Can Be Obtained by Programmatic Lookup
It is sometimes useful to narrow down the set of beans that can be obtained
by programmatic lookup via jakarta.enterprise.inject.Instance
. Typically,
a user needs to choose the appropriate implementation of an interface based
on a runtime configuration property.
Imagine that we have two beans implementing the interface
org.acme.Service
. You can’t inject the org.acme.Service
directly unless
your implementations declare a CDI qualifier. However, you can inject the
Instance<Service>
instead, then iterate over all implementations and
choose the correct one manually. Alternatively, you can use the
@LookupIfProperty
and @LookupUnlessProperty
annotations.
@LookupIfProperty
indicates that a bean should only be obtained if a
runtime configuration property matches the provided value.
@LookupUnlessProperty
, on the other hand, indicates that a bean should
only be obtained if a runtime configuration property does not match the
provided value.
@LookupIfProperty
Example interface Service {
String name();
}
@LookupIfProperty(name = "service.foo.enabled", stringValue = "true")
@ApplicationScoped
class ServiceFoo implements Service {
public String name() {
return "foo";
}
}
@ApplicationScoped
class ServiceBar implements Service {
public String name() {
return "bar";
}
}
@ApplicationScoped
class Client {
@Inject
Instance<Service> service;
void printServiceName() {
// This will print "bar" if the property "service.foo.enabled" is NOT set to "true"
// If "service.foo.enabled" is set to "true" then service.get() would result in an AmbiguousResolutionException
System.out.println(service.get().name());
}
}
4.17. Injecting Multiple Bean Instances Intuitively
In CDI, it’s possible to inject multiple bean instances (aka contextual
references) via the jakarta.enterprise.inject.Instance
which implements
java.lang.Iterable
. However, it’s not exactly intuitive. Therefore, a
new way was introduced in Quarkus - you can inject a java.util.List
annotated with the io.quarkus.arc.All
qualifier. The type of elements in
the list is used as the required type when performing the lookup.
@ApplicationScoped
public class Processor {
@Inject
@All
List<Service> services; (1) (2)
}
1 | The injected instance is an immutable list of the contextual references of the disambiguated beans. |
2 | For this injection point the required type is Service and no additional
qualifiers are declared. |
The list is sorted by priority as defined by
io.quarkus.arc.InjectableBean#getPriority() . Higher priority goes
first. In general, the @jakarta.annotation.Priority annotation can be used
to assign the priority to a class bean, producer method or producer field.
|
If an injection point declares no other qualifier than @All
then @Any
is
used, i.e. the behavior is equivalent to @Inject @Any Instance<Service>
.
You can also inject a list of bean instances wrapped in
io.quarkus.arc.InstanceHandle
. This can be useful if you need to inspect
the related bean metadata.
@ApplicationScoped
public class Processor {
@Inject
@All
List<InstanceHandle<Service>> services;
public void doSomething() {
for (InstanceHandle<Service> handle : services) {
if (handle.getBean().getScope().equals(Dependent.class)) {
handle.get().process();
break;
}
}
}
}
Neither a type variable nor a wildcard is a legal type parameter for an
@All List<> injection point, i.e. @Inject @All List<?> all is not
supported and results in a deployment error.
|
It is also possible to obtain the list of all bean instance handles
programmatically via the Arc.container().listAll() methods.
|
4.18. Ignoring Class-Level Interceptor Bindings for Methods and Constructors
If a managed bean declares interceptor binding annotations on the class
level, the corresponding @AroundInvoke
interceptors will apply to all
business methods. Similarly, the corresponding @AroundConstruct
interceptors will apply to the bean constructor.
For example, suppose we have a logging interceptor with the @Logged
binding annotation and a tracing interceptor with the @Traced
binding
annotation:
@ApplicationScoped
@Logged
public class MyService {
public void doSomething() {
...
}
@Traced
public void doSomethingElse() {
...
}
}
In this example, both doSomething
and doSomethingElse
will be
intercepted by the hypothetical logging interceptor. Additionally, the
doSomethingElse
method will be intercepted by the hypothetical tracing
interceptor.
Now, if that @Traced
interceptor also performed all the necessary logging,
we’d like to skip the @Logged
interceptor for this method, but keep it for
all other methods. To achieve that, you can annotate the method with
@NoClassInterceptors
:
@Traced
@NoClassInterceptors
public void doSomethingElse() {
...
}
The @NoClassInterceptors
annotation may be put on methods and constructors
and means that all class-level interceptors are ignored for these methods
and constructors. In other words, if a method/constructor is annotated
@NoClassInterceptors
, then the only interceptors that will apply to this
method/constructor are interceptors declared directly on the
method/constructor.
This annotation affects only business method interceptors (@AroundInvoke
)
and constructor lifecycle callback interceptors (@AroundConstruct
).
4.19. Exceptions Thrown By An Asynchronous Observer Method
If an exception is thrown by an asynchronous observer then the
CompletionStage
returned by the fireAsync()
method completes
exceptionally so that the event producer may react appropriately. However,
if the event producer does not care then the exception is ignored silently.
Therefore, Quarkus logs an error message by default. It is also possible to
implement a custom AsyncObserverExceptionHandler
. A bean that implements
this interface should be @jakarta.inject.Singleton
or
@jakarta.enterprise.context.ApplicationScoped
.
NoopAsyncObserverExceptionHandler
@Singleton
public class NoopAsyncObserverExceptionHandler implements AsyncObserverExceptionHandler {
void handle(Throwable throwable, ObserverMethod<?> observerMethod, EventContext<?> eventContext) {
// do nothing
}
}
5. Build Time Extensions
Quarkus incorporates build-time optimizations in order to provide instant startup and low memory footprint. The downside of this approach is that CDI Portable Extensions cannot be supported. Nevertheless, most of the functionality can be achieved using Quarkus extensions. See the integration guide for more information.
6. Development Mode
In the development mode, two special endpoints are registered automatically to provide some basic debug info in the JSON format:
-
HTTP GET
/q/arc
- returns the summary; number of beans, config properties, etc. -
HTTP GET
/q/arc/beans
- returns the list of all beans-
You can use query params to filter the output:
-
scope
- include beans with scope that ends with the given value, i.e.http://localhost:8080/q/arc/beans?scope=ApplicationScoped
-
beanClass
- include beans with bean class that starts with the given value, i.e.http://localhost:8080/q/arc/beans?beanClass=org.acme.Foo
-
kind
- include beans of the specified kind (CLASS
,PRODUCER_FIELD
,PRODUCER_METHOD
,INTERCEPTOR
orSYNTHETIC
), i.e.http://localhost:8080/q/arc/beans?kind=PRODUCER_METHOD
-
-
-
HTTP GET
/q/arc/removed-beans
- returns the list of unused beans removed during build -
HTTP GET
/q/arc/observers
- returns the list of all observer methods
These endpoints are only available in the development mode, i.e. when you
run your application via mvn quarkus:dev (or ./gradlew quarkusDev ).
|
7. Strict Mode
By default, ArC does not perform all validations required by the CDI specification. It also improves CDI usability in many ways, some of them being directly against the specification.
To pass the CDI Lite TCK, ArC also has a strict mode. This mode enables additional validations and disables certain improvements that conflict with the specification.
To enable the strict mode, use the following configuration:
quarkus.arc.strict-compatibility=true
Some other features affect specification compatibility as well:
To get a behavior closer to the specification, these features should be disabled.
Applications are recommended to use the default, non-strict mode, which makes CDI more convenient to use. The "strictness" of the strict mode (the set of additional validations and the set of disabled improvements on top of the CDI specification) may change over time.
8. ArC Configuration Reference
Configuration property fixed at build time - All other configuration properties are overridable at runtime
Type |
Default |
|||
---|---|---|---|---|
An unused bean:
Environment variable: Show more |
string |
|
||
If set to true Environment variable: Show more |
boolean |
|
||
If set to true, the bytecode of unproxyable beans will be transformed. This ensures that a proxy/subclass can be created properly. If the value is set to false, then an exception is thrown at build time indicating that a subclass/proxy could not be created. Quarkus performs the following transformations when this setting is enabled:
Environment variable: Show more |
boolean |
|
||
If set to true, the bytecode of private fields that are injection points will be transformed to package private. This ensures that field injection can be performed completely reflection-free. If the value is set to false, then a reflection fallback is used to perform the injection. Environment variable: Show more |
boolean |
|
||
If set to true (the default), the build fails if a private method that is neither an observer nor a producer, is annotated with an interceptor binding. An example of this is the use of Environment variable: Show more |
boolean |
|
||
The list of selected alternatives for an application. An element value can be:
Environment variable: Show more |
list of string |
|||
If set to true then Environment variable: Show more |
boolean |
|
||
The list of types that should be excluded from discovery. An element value can be:
Environment variable: Show more |
list of string |
|||
List of types that should be considered unremovable regardless of whether they are directly used or not. This is a configuration option equivalent to using An element value can be:
Environment variable: Show more |
list of string |
|||
If set to true then the container attempts to detect "unused removed beans" false positives during programmatic lookup at runtime. You can disable this feature to conserve some memory when running your application in production. Environment variable: Show more |
boolean |
|
||
If set to true then the container attempts to detect wrong usages of annotations and eventually fails the build to prevent unexpected behavior of a Quarkus application. A typical example is Environment variable: Show more |
boolean |
|
||
If set to The strict mode is mainly introduced to allow passing the CDI Lite TCK. Applications are recommended to use the default, non-strict mode, which makes CDI more convenient to use. The "strictness" of the strict mode (the set of additional validations and the set of disabled improvements on top of the CDI specification) may change over time. Note that Environment variable: Show more |
boolean |
|
||
If set to true then the container monitors business method invocations and fired events during the development mode.
Environment variable: Show more |
boolean |
|
||
If set to true then disable Environment variable: Show more |
boolean |
|
||
The list of packages that will not be checked for split package issues. A package string representation can be:
Environment variable: Show more |
list of string |
|||
If set to true and SmallRye Context Propagation extension is present then enable the context propagation for CDI contexts. Environment variable: Show more |
boolean |
|
||
Type |
Default |
|||
The maven groupId of the artifact. Environment variable: Show more |
string |
required |
||
The maven artifactId of the artifact. Environment variable: Show more |
string |
required |
||
The maven classifier of the artifact. Environment variable: Show more |
string |