This rule raises an issue when a CDI bean uses the @Singleton annotation from jakarta.inject instead of
@ApplicationScoped in a Quarkus application.
In Quarkus applications, both @Singleton (from Jakarta Dependency Injection) and @ApplicationScoped (from Jakarta
Contexts and Dependency Injection) create single-instance beans. However, they behave differently in important ways.
@ApplicationScoped beans are full CDI beans with these capabilities:
@Singleton beans are simpler but more limited:
For most application-level services, @ApplicationScoped is the better choice because it provides the full CDI feature set that Quarkus
applications typically need.
@Singleton might have a slightly better performance than @ApplicationScoped because of the absence of client proxy. Thus,
there are specific scenarios where @Singleton may be intentionally chosen:
@ApplicationScoped is measured and found to be
significant (rare, and should be documented)If you choose to use @Singleton, document why you need it and confirm that:
Using @Singleton instead of @ApplicationScoped can lead to several issues:
@Singleton beans cannot be mocked using QuarkusMock in tests. This means:
Choosing @Singleton limits your future options:
Replace the @Singleton annotation with @ApplicationScoped. Update the import statement to use
jakarta.enterprise.context.ApplicationScoped instead of jakarta.inject.Singleton.
import jakarta.inject.Singleton;
@Singleton // Noncompliant
public class UserService {
public String getUser(Long id) {
// implementation
return "user";
}
}
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class UserService {
public String getUser(Long id) {
// implementation
return "user";
}
}
If you have a documented, valid reason to use @Singleton, you can keep it but should add a comment explaining why. This is an
alternative to changing the annotation, though it’s only appropriate in specific cases.
import jakarta.inject.Singleton;
@Singleton // Noncompliant
public class ApplicationConfig {
private final String version = "1.0.0";
public String getVersion() {
return version;
}
}
import jakarta.inject.Singleton;
// Using @Singleton intentionally: this is a simple configuration holder
// with no interceptor requirements and no need for testing isolation.
// It's instantiated eagerly at startup to validate configuration.
@Singleton
public class ApplicationConfig {
private final String version = "1.0.0";
public String getVersion() {
return version;
}
}