Beans are the centerpiece of a Quark’s application. In contrast to a classically POJO class instance, the instance of a bean is encapsulated and lives inside a container object. Therefore beans are called container-managed. The container takes care of the lifecycle from the dependency resolution, the instantiation to the destruction of the bean. This container is called client-proxy.
A scope defines the behavior of a client-proxy. For example, this scope determines the exact times when the lifecycle of a bean starts and ends and when to reuse a bean instance or create a new one.
In this article, we will take a closer look at all the scopes that Quarkus offers.
Background: Dependency Injection and Injection Points
Before we get into the scopes, we should first understand some of the essential terminologies for understanding the concepts around beans.
It all starts with the understanding of dependency injection (DI): A class that has a dependency on another class does not create an instance of that class itself. It just defines a dependency on the type of the class. The instance of the dependent class gets passed to the class at runtime from the outside.
The basis of Quarkus’s context and dependency injection framework (CDI) is the JSR 365 specification. But we should note that Quarkus has not fully implanted this specification so far and has made optimizations in certain places.
Defining dependencies to other classes is done via so-called injection points. Such injection points are declared in the code by the annotation
@Inject. In Quarkus, we have three ways to declare injection points:
A non-static field that is at least package-private and has the type of our dependent class:
A constructor that is at least package-private and has a parameter with the type of our dependent class (it is allowed to omit the
A non-static setter method, that is at least package-private, returns
void and has a parameter with the type of our dependent class:
The question is now, which conditions the CDI mechanism used to create and reuse beans and how we can control them during the design process of our architecture.
Short Overview of Quarkus Scopes
|@ApplicationScoped||When a method on the injected instance gets called.||One per application.|
|@Singleton||When injected into parent.||One per application.|
|@Dependent||When injected into parent.||For every injection point.|
|@RequestScoped||When a method on the injected instance gets called.||For every HTTP request.|
|@SessionScoped||When a method on the injected instance gets called.||For every undertow session.|
|Custome Scope||Dependend on implementation.||Dependend on implementation.|
Application Wide Scopes
All application-wide scopes have in common that an instance of a bean class is created only once within an application. Therefore, the CDI will use the same instance at each injection point. This behavior corresponds to the classic singleton pattern and allows us to share a state across multiple injection points.
If a bean class has the annotation
@ApplicationScoped, CDI will lazily instantiate the bean during the first call to a bean method. But if the bean is never accessed, no instance will ever be created.
In the following example, we have a simple service annotated with
This service holds a state with the
counter field. Additionally, we can observe the life cycle with the post-construct listener
onConstructed() and see when the bean gets initialized.
If we want to use the single instance of our
@ApplicationScoped annotated service in other places of our application, we need to inject it via a field annotated with
@Inject and has the type of the service. The field itself does not require any further instantiation. Quarkus’s CDI will do that for us.
In the following startup listener bean, which is called as soon as the Quarkus application started, we are injecting two instances of our
@ApplicationScoped annotated service:
The execution of the application would give us the following output:
We can now observe two things here: First, the creation of the instance gets deferred until we call
getAndIncrementCounter() for the first time. If we would remove the two calls, we would only get the following output:
MyApplicationScopedService class was never instantiated.
Second, we see that we continuously operated on the same instance even though we used two different fields to access the service when incrementing the
counter field. This behavior shows that the state gets shared between two injection points.
Unlike @ApplicationScoped, if a bean has the annotation
@Singleton, it gets instantiated when injected into another bean. But it is irrelevant whether the bean is ever accessed.
The following example shows a simple
@Singleton service that has only one post-construction listener, with which we can observe when the CDI mechanism has created our instance:
We now inject this
@Singleton service into a field of the following startup listener, but without explicitly calling a method of the service bean instance:
The execution of the application would result in the following output:
Firstly, we can see that the instantiation of
MySingletonService happened during the instantiation of
ApplicationStartHandler, but before the invocation of the
onApplicationStart method. And secondly, that the creation of the instance happened although we have never accessed it directly, unlike in the
@ApplicationScope example from the previous chapter.
If a bean has the annotation
@Dependent, CDI will create a new instance at each injection point when it gets injected. In contrast to the application-wide scopes, there is no shared state across multiple injection points.
The life cycle of such a bean depends on that of the parent bean into which it gets injected. For example, if the parent is a
@Singleton bean, a
@Dependent bean would exist as long as the application runs. But, on the other hand, if it is a
@RequestScoped bean that exists only for a request (see next chapter), a
@Dependent bean would also exist only for the request’s scope.
@Dependent service holds a counter state field and has a post-constructed listener with which we can see when and how often the creation of our instance gets triggered:
Analogous to the
@ApplicationScope example, we inject the service twice into a startup listener, and icrement the
counter field on both instances:
Running the application will give us the following output:
On the one hand, we can see that CDI has created two separate instances at the moment of injection. And on the other hand, incrementing the
counter field on the first instance did not affect the second one.
If a bean class has the annotation
@RequestScoped, CDI will lazily instantiate the bean during the first call to a bean method. Such a bean lives only within a chain used to process a single HTTP request.
A request-scoped bean encapsulates information of a request and shares them through multiple injection points within the processing chain. Parts of this chain are, e.g., request/response filters, a JAX-RS method, or a classic servlet.
One of the most known request-scoped beans in Quarkus is the
io.vertx.core.http.HttpServerRequest, which contains information about the called URI, or the HTTP headers of the current request.
In the following example, we want to implement a JAX-RS HTTP endpoint method that we can use to add two numbers. To make larger numbers more readable, we want to format the result using a decimal separator. However, the decimal separator character depends on the requester’s region: It’s a point in the USA, in other countries a comma. Therefore, for the requester to get the correct output, we give him the option to include the header
X-Locale to specify his desired Locale value.
To only have to compute this information once and make the code reusable for different purposes, we put the parsing of the header in a bean annotated with
Now we can create a JAX-RS resource where we inject the
RequestUserContext bean. In the HTTP Endpoint method, where we add the two numbers passed as query parameters, we now have access to the
Locale requested by the requester. We can then use this
Locale in a
NumberFormat that formats the final result with the desired decimal separator:
If a bean class has the annotation
@SessionScoped, CDI will lazily instantiate the bean during the first call to a bean method. Such a bean lives till the associated session gets terminated. Thus, unlike
@RequestScoped, we can have a shared state in a session-scoped bean instance across multiple HTTP requests.
The session scope is only available if the undertow extension is enabled:
What exactly is a session in this context? Suppose the Quarkus server is processing an HTTP request and there is at least one bean annotated with
@SessionScoped injected in the request processing chain (see the previous chapter). In that case, the undertow SessionManager will create a new session with a unique ID. This session is effectively the parent of the session scoped bean. As long as the session lives, the bean also exists. It gets deconstructed alongside the termination of the session.
The processed response of the server contains the session ID as the cookie
JSESSIONID. Since HTTP is a stateless protocol, the requester must resend this session ID cookie with every request. If he does this, our session-scoped bean will get reused by CDI in the request processing chain. Otherwise, CDI will create a new instance of the bean.
We can use such a session scoped bean, for example, to remember the last queries in a search field. And offer them again to the user for selection when he revisits the page later.
Session scoped beans are not limited to anonymous sessions. We can also use them if we create an actual user session through one of the many authentication paths in Quarkus.
We might find ourselves in the situation where we want to set the scope of a bean class at runtime dynamically. Or, we may want to use a class that comes from an external library as a bean. In both cases, we could not hard-code the scope annotation to the class. So-called producer methods (which must be annotated with
javax.enterprise.inject.Produces) are a solution to this problem.
In the following example, we define two producer methods, each creating an instance of
MyService. If the “dev” profile is active, CDI will create a single application-scoped instance at runtime (the first method). However, if this is not the case, Quarkus would fall back to a method which annotated with
@DefaultBean and, in our case, produces a request-scoped service (the second method):
The same mechanism works for external classes as long as they are not
There is the possibility that we define custom scopes, which self-selected criteria. For example, when and how a bean gets created and reused at an injection point within such a scope.
But, custom scopes are a complex topic since we have to take care of the life cycle of a bean ourselves, which would go beyond the scope of this article. We will possibly take a deeper look in a future article. However, here is a brief list of all the steps we need to take to implement a custom scope:
- First, we need a new annotation that identifies a class as a bean of our custom scope. This annotation must have the
RUNTIMEand be targeted to at last one of the
FIELD. And in order for this annotation to represent a scope, it needs either be annotated with
javax.enterprise.context.NormalScope. (The JavaDoc of both contains a detailed explanation of scope annotations).
- For the annotation, we need a logic that creates and manages the beans within the scope. For this, we need to create a new Quarkus extension. This new extension needs a dependency on the project that contains our custom scope annotation.
- In the extension’s runtime project, we must create a scope context class that implements
io.quarkus.arc.InjectableContext. This class manages the life cycle of the beans inside our scope.
- In the extension’s deployment project, we would have to register our annotation together with the context. As an example, we can look at the method buildCdiScopes() in the OmniFaces Quarkus extension.
Beans get instantiated either lazily if a bean method gets called or if the parent bean gets initialized. As a consequence, our bean may never get instantiated. But by registering our bean as an observer for specific events, we can force the instantiation.
By adding an observer method for the
StartupEvent, the application start will initialize the bean at least once:
Alternatively, if we don’t need a dedicated method, we can annotate the entire class with
If we want to react to the creation of a new
SessionScope, we can also do this with an observer method:
However, we must consider the life cycle of scopes here. For example, this means for a bean annotated with
@Dependent: The startup event will trigger the creation of an instance that gets immediately deconstructed after the execution of the startup method.
Field Access Will Not Trigger the Instantiation of a Bean
At the definition of the
@SessionScoped we should pay special attention to the condition “(…) CDI will lazily instantiate the bean during the first call to a bean method.”. If we access only fields of the injected bean, the values are stored in the instance, but the instantiation (e.g., a
@PostConstruct method or the constructor for injection points) is not executed.
Unsupported CDI Scopes
Although Quarkus follows the CDI specification, it has not yet fully implemented it. The consequence of that is that the
@ConversationScoped annotation is not supported. However, we can represent most use cases of
@ConversationScoped in Quarkus by using
Injection Points are Just Proxy Instances
One of the fundamental understandings of beans is that we never work directly on the bean itself but always via a so-called client proxy instance. It is important to note that we cannot make any assumptions about this client proxy. Depending on the scope implementation, there may be only one client proxy instance for multiple bean instances. This client then forwards the field accesses or method calls to the correct bean internally. Therefore, the CDI specification (5.4.1. Client proxy invocation) states that the behavior of the methods from
hashCode()) is undefined. Therefore we should not work with them.