
Here I’ll cover the basics of CDI (contexts and dependency injection) on Quarkus.
This will include all jargon that comes with it (bean, injection point, …).
If you’re new to Java EE, you’re in the right place!
- Simple demo
- What is a bean? And an Injection Point?
- Injecting multiple beans / Ambiguous dependency exception
- Scopes
- Client proxy
- @Singleton or @ApplicationScoped?
- When would someone need @Singleton?
Simple demo
To start off, let’s create a simple example, to get some practice in.
Head over to https://code.quarkus.io, to set up the project.
We just need the RESTEasy Reactive extension.

Note: Java will be used, Kotlin is currently still in preview, but if you want to use it, check this before.
Now generate and download it, and we’re ready to inject some beans.
Create two classes:
PersonController
PersonService
For the PersonController
, we’ll begin with this:
package me.davidgomes.blog.quarkus;
import javax.ws.rs.Path;
@Path("/people")
public class PersonController {
}
If you’re on IntelliJ, you’ll notice you get a warning, it will be tackled in a second.
The @Path
annotation makes it a REST endpoint, on /people
.
And for the service:
package me.davidgomes.blog.quarkus;
import javax.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class PersonService {
}
Here we start using scopes, in this case, the @ApplicationScoped
annotation means only one instance will be created for our application. (Don’t worry, all scopes will be explained later in this post)
This is pretty barebones, let’s add some functionality.
// PersonService.java
String sayHello(String name) {
return "Hello " + name + "!";
}
Now we want to call that from the Controller, and this is where injection begins.
// PersonController.java
final PersonService service;
PersonController(PersonService service) {
this.service = service;
}
We inject it by the constructor and, unlike other frameworks, you don’t need to annotate with @Inject
. (in case you’ve seen that elsewhere)
@GET
public String hello(@QueryParam("name") String name) {
return service.sayHello(name);
}
Here we expose the HTTP GET verb/method, on our previously declared /people
endpoint, with the name
as a query parameter.
And the endpoint returns:
$ curl http://localhost:8080/people?name=David
Hello David!
What is a bean? And an Injection Point?
A bean refers to the class you’re injecting/producing, and an injection point is where you’re getting it.
In our example above, the bean is PersonService
and the injection point is the service
variable in the controller.
This terminology helps when reading the official docs.
Producing a bean
You can inject a bean, as long as it has a scope or is produced somewhere.
To produce a bean, you create a function with @Produces
.
import javax.enterprise.inject.Produces;
public class EarthProducer {
@Produces
Earth earth() {
return new Earth(2022);
}
}
Here Earth
is simply a POJO.
Although we don’t specify its scope, by default it has the ApplicationScoped
as well.
If we specify the scope, @Produces
can be omitted.
@ApplicationScoped
Earth earth() {
return new Earth(2022);
}
You can try injecting via the constructor, as we’ve done before.
You can also add parameters for beans you want to be injected, just as we’ve done in the constructor of the service.
Producing and injecting different variants of a bean
Imagine you have a service, with a configuration-based variance. You want both to be injected into different parts of the code.
To re-use this base service:
public abstract class HelloService {
final String name;
HelloService(String name) {
this.name = name;
}
String sayHello() {
return "Hi " + name + "!";
}
}
You can make use of @Produces
and @Named
, which is a qualifier for the bean you’re producing.
import javax.enterprise.inject.Produces;
public class HelloServiceProducer {
@Named("earth")
@Produces
HelloService earthHelloService() {
return new HelloService("Mother Nature");
}
@Named("person")
@Produces
HelloService personHelloService() {
return new HelloService("Ordinary person");
}
}
Now in the PersonController
, if you wish to use its HelloService
variant, you need to specify that same qualifier.
@Path("/people")
public class PersonController {
final HelloService service;
// Note: annotate only here in the parameter, since the service is being injected here
PersonController(@Named("person") HelloService service) {
this.service = service;
}
@GET
public String hello() {
return service.sayHello();
}
}
As you can see, I haven’t annotated the HelloService
itself with any scope, that’s because you annotate the implementations themselves, not the abstractions (interface
/base class
), since these cannot be instantiated.
Injecting multiple beans / Ambiguous dependency exception
If you have 2 beans that match what you’re asking in your injection point without any qualifier (like the @Named
) you’re going to get an exception.
Consider this:
public interface HelloService {
String sayHello();
}
// Person bean
@ApplicationScoped
public class PersonHelloService implements HelloService {
@Override
public String sayHello() {
return "Hi, I'm a person!";
}
}
// Earth bean
@ApplicationScoped
public class EarthHelloService implements HelloService {
@Override
public String sayHello() {
return "Hello, I'm mother nature!";
}
}
There are no qualifiers, so if we have our injection point as such:
final HelloService service;
PersonController(HelloService service) {
this.service = service;
}
We get “javax.enterprise.inject.AmbiguousResolutionException: Ambiguous dependencies
[…]”, at compile-time. (that’s the beauty of Quarkus, erroring in compile-time so we don’t get surprised in runtime!)
It’s not that it’s bad to have multiple implementations, we just need to properly work with them, in this case, we could have used the @Named
qualifier as done in the previous section.
@ApplicationScoped
@Named("person")
public class PersonHelloService implements HelloService {
...
}
// Controller
final HelloService service;
PersonController(@Named("person") HelloService service) {
this.service = service;
}
Remember, annotate in the constructor, since that’s where we want it injected.
Keep in mind that, if you inject without this @Named("person")
, you’re still going to get the exception, because you need to add a qualifier to the EarthService
too.
Using multiple beans of the same type
However, we could want to call multiple implementations.
For that, you must rely on Instance<YourBean>
, which is the programmatic way of injecting beans.
// list of Instances
final Instance<HelloService> services;
PersonController(Instance<HelloService> services) {
this.services = services;
}
...
var hellos = new StringBuilder();
for (var service : services) {
hellos.append(service.sayHello()).append("\n");
}
return hellos.toString();
// Output:
// Hi, I'm a person!
// Hello, I'm mother nature!
Mostly for curiosity, since I’ve never had such a need.
Perhaps you could just make use of the first in it, as a dirty hack for the ambiguous problem. ¯\_(ツ)_/¯
Scopes
Oh finally, I’ve waited a long time to reach here.
ApplicationScoped | One instance for the entire app |
RequestScoped | One instance per request |
SessionScoped | One instance per HTTP session |
* Singleton | One instance for the entire app |
* Dependent | Uses the same scope of the bean injecting it |
The scopes are instantiated lazily, only after calling a method upon it. (we’ll see how this is achieved in the next section 😉)
While the pseudo ones are instantiated right after being injected.
Client proxy
The scopes all have a “client proxy”, which is a class that wraps the bean.
It is this client proxy that allows for lazy instantiation at the call level.
When you inject a scoped bean, it doesn’t actually inject the bean itself, but the proxy.
It is only when you call something on the bean (which is actually the proxy), that it does initialize the bean. (in case it isn’t initialized yet)
They also allow for another thing: circular dependency.
A circular dependency is when you have a PersonService
calling HelloService
, and PersonService
also calling HelloService
.
Although you shouldn’t rely on that, since it does have some drawbacks.
@Singleton or @ApplicationScoped?
At first sight, you’d consider @Singleton
, by the fact that it doesn’t have the seriooooously small overhead, of not calling through the client proxy.
However, there are 2 drawbacks. By using @Singleton
you:
- Can’t mock in a test
- although one can argue that you needn’t injection in a test
- in the unit ones you use the classes directly
- in integration, you preferably don’t use mocks
- although one can argue that you needn’t injection in a test
- Can’t have the bean destroyed and recreated in runtime
- but… who has ever used that?
And, in the Quarkus article, they mention that on an @ApplicationScoped
, you shouldn’t directly use fields, because the proxy only delegates/proxies the methods, but it’s also something uncommon, people always use setters and getters anyway.
In the end, I don’t see it being a game-changer, but Quarkus does recommend using @ApplicationScoped
unless there is a good reason to go for @Singleton
.
When would someone need @Singleton?
In a project of mine, I had to use @Singleton
to produce instances of the KMongo
library, because, at the time of writing, their classes are final. (it’s the default on Kotlin, although they could have made them open)
Summary
In this post, we went through the basics of dependency injection in Quarkus, including its terminology.
Going over how to inject a service, produce beans, and how the different scopes differ.
Now it’s time to start using Quarkus in your projects!