This rule raises an issue when a CDI bean uses the @Singleton annotation from jakarta.inject instead of @ApplicationScoped in a Quarkus application.

Why is this an issue?

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.

When @Singleton might be acceptable

@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:

If you choose to use @Singleton, document why you need it and confirm that:

What is the potential impact?

Using @Singleton instead of @ApplicationScoped can lead to several issues:

Testing difficulties

@Singleton beans cannot be mocked using QuarkusMock in tests. This means:

Reduced flexibility

Choosing @Singleton limits your future options:

How to fix it in Quarkus

Replace the @Singleton annotation with @ApplicationScoped. Update the import statement to use jakarta.enterprise.context.ApplicationScoped instead of jakarta.inject.Singleton.

Code examples

Noncompliant code example

import jakarta.inject.Singleton;

@Singleton // Noncompliant
public class UserService {
    public String getUser(Long id) {
        // implementation
        return "user";
    }
}

Compliant solution

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.

Noncompliant code example

import jakarta.inject.Singleton;

@Singleton // Noncompliant
public class ApplicationConfig {
    private final String version = "1.0.0";

    public String getVersion() {
        return version;
    }
}

Compliant solution

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;
    }
}

Resources

Documentation