Quarkus Tip : Select a bean at runtime

Quarkus Tip : Select a bean at runtime

When developing an application, it is very common to have several implementations of a servicen and to have to select one or the other depending on the environment on which it is deployed.

A classic example: a service that calls an API of an external partner that we want to call only in production, and therefore mock on the development and test / UAT / staging environments.

Quarkus tries to move to build time, via its Maven or Gradle plugin, the most possible things. Among other things, it discovers CDI beans at build time, and will instantiate only those necessary.

To be able to select a bean, we have the possibility to use the @IfBuildProfile annotation, the @IfBuildProperty, or CDI alternatives. But these solutions will select the bean at build time! This allows to have a different bean for unit tests or dev mode but not once the application has been built. To use this mechanism to select a different bean on a UAT/staging environment and on the production, you would have to build several packages of the application.

Selecting a bean at runtime is still possible using a CDI Instance, it requires a few lines of code.

Here is how to do it.

Step 1: add a property in your application.properties

service.implementation-name=mock

Step 2: annotate your two implementations with a CDI qualifier, here we will use @Named but Quarkus also offers @Identifier.

@ApplicationScoped
@Named("mock")
public class MockService implements RemoteService {
    // ....
}
@ApplicationScoped
@Named("real")
public class RealService implements RemoteService {
    // ....
}

Step 3: In the class using your service, select the bean at application startup based on the application configuration.

@Path("/api")
public class MyEndpoint {
    @ConfigProperty(name="service.implementation-name") String implementationName;
    @Inject Instance<RemoteService> remoteServiceInstance;
    
    private final RemoteService remoteService;

    @PostConstruct
    void init() {
        remoteService = remoteServiceInstance
            .select(NamedLiteral.of(implementationName)).get();
    }

    // ...
}

UPDATE : Martin Kouba (thanks to him) proposed a shorter and clearer implementation, via constructor injection (which, to my great shame, I am not familiar with). So I put below a step 3 with a simplified code:

@Path("/api")
public class MyEndpoint {   
   private RemoteService remoteService;

   MyEndpoint(@ConfigProperty(name="service.implementation-name") String implName, 
         Instance<RemoteService> remoteServiceInstance) {
     remoteService = remoteServiceInstance.select(NamedLiteral.of(implName)).get();
   }

    // ...
}

Caution : the definitions (metadata) of the two beans MockService and RemoteService will be created and will remain in memory for the life of your application, even if only one of the two will be used. They will not be deleted automatically by Arc, the CDI implementation of Quarkus, via its unused bean deletion mechanism. The beans, on the other hand, if they use a normal scope, will be created in a lazy manner.

Conclusion: even if selecting a bean at runtime goes against the philosophy of Quarkus, it is very often a necessity and it is possible via a few lines of code. As it is something classic, I will raise the point with the Quarkus community so that we find something more practical, stay tuned 😉

UPDATE : I have since raised the point with the Quarkus community and a more practical solution than using instance.select() has been implemented thanks to the @LookupIfProperty and @LookupUnlessProperty annotations that allow to add, for each bean, a condition for their selection based on a configuration property.

So we can replace the previous steps 2 and 3 with these:

Step 2: annotate both your implementations with @LookupIfProperty.

@ApplicationScoped
@LookupIfProperty(name = "service.implementation-name", stringValue = "mock")
public class MockService implements RemoteService {
    // ....
}
@ApplicationScoped
@LookupIfProperty(name = "service.implementation-name", stringValue = "real")
public class RealService implements RemoteService {
    // ....
}

Step 3: In the class using your service, the bean must be obtained at startup via the get() method of the instance. The presence of the @LookupIfProperty annotation on both implementations will cause only the bean of type MockService to be created because the value of the service.implementation-name property used in its @LookupIfProperty instance is mock.

@Path("/api")
public class MyEndpoint {
    @Inject Instance<RemoteService>remoteServiceInstance;
    
    private RemoteService remoteService;

    @PostConstruct
    void init() {
        remoteService = remoteServiceInstance.get();
    }

    // ...
}

3 thoughts on “Quarkus Tip : Select a bean at runtime

  1. You can also use @LookupIfProperty annotation on your bean. Then, to get the bean instance, you just need to call instance.get(). Here is the documentation https://quarkus.io/guides/cdi-reference#declaratively-choose-beans-that-can-be-obtained-by-programmatic-lookup

    Moreover when your bean is annotated with @ApplicationScoped, the bean will be created lazily (when a method of the bean is invoked). So I don’t think your sentence “both MockService and RemoteService beans will be created and remain in memory throughout the life of your application, even if only one of them is used” is correct. I think the @ApplicationScoped bean that is not used will not be created (only the proxy will be created and not the target bean), here is the documentation https://quarkus.io/guides/cdi-reference#lazy_by_default

    1. Hi Olivier,
      The @LookupIfProperty annotation has been created following discussion by this very article 😉
      So, at the time I wrote this article, it didn’t exist. I’ll update the article to reflect this change.

      Regarding the second point, I remember discussing it with one of the CDI expert on the Quarkus teams, as I understand it Instance is handled differently as when you access the Instance objet the CDI container cannot knows which bean you will use (and if you will use the Instance) so the bean will not be removed. But maybe the phrasing is not good as it’s the bean metadata that will not be removed but, as you sais, as beans are created lazilly the overhead is low. I’ll ask on the Quarkus Zulip chat and clarify this sentence if needed.

    2. Hi Olivier,
      I updated the article with both a precision for lazy instanciaton of beans and the new way to select a bean at runtime via @LookupIfProperty. Thanks for pointing this better way of doing it to me.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.