quarkusio/quarkus · error · IllegalStateException

${actualType} type can not be used to represent JWT claims i

Error message

${actualType} type can not be used to represent JWT claims in @Singleton or @ApplicationScoped beans, make the bean @RequestScoped or wrap this type with org.eclipse.microprofile.jwt.ClaimValue or jakarta.inject.Provider or jakarta.enterprise.inject.Instance

What it means

When a JWT claim is injected (e.g. Optional<X> or raw X via @Claim) into a @Singleton or @ApplicationScoped bean, the value would be resolved once at bean creation outside any request, so it cannot represent a per-request claim. The extension's build step inspects such injection points and throws this IllegalStateException at build time, telling you to make the bean request-scoped or wrap the type in ClaimValue/Provider/Instance for lazy per-request resolution.

Source

Thrown at extensions/smallrye-jwt/deployment/src/main/java/io/quarkus/smallrye/jwt/deployment/SmallRyeJwtProcessor.java:204

            if (injectionPoint.hasDefaultedQualifier()) {
                continue;
            }
            AnnotationInstance claimQualifier = injectionPoint.getRequiredQualifier(CLAIM_NAME);
            if (claimQualifier != null) {
                Type actualType = injectionPoint.getRequiredType();

                Optional<BeanInfo> bean = injectionPoint.getTargetBean();
                if (bean.isPresent()) {
                    DotName scope = bean.get().getScope().getDotName();
                    if (!REQUEST_SCOPED_NAME.equals(scope)
                            && (!ALL_PROVIDER_NAMES.contains(injectionPoint.getType().name())
                                    && !CLAIM_VALUE_NAME.equals(actualType.name()))) {
                        String error = String.format(
                                "%s type can not be used to represent JWT claims in @Singleton or @ApplicationScoped beans"
                                        + ", make the bean @RequestScoped or wrap this type with org.eclipse.microprofile.jwt.ClaimValue"
                                        + " or jakarta.inject.Provider or jakarta.enterprise.inject.Instance",
                                actualType.name());
                        throw new IllegalStateException(error);
                    }
                }

                if (injectionPoint.getType().name().equals(DotNames.PROVIDER) && actualType.name().equals(DotNames.OPTIONAL)) {
                    additionalTypes.add(actualType);
                }
            }

        }

        // Register a custom bean
        BeanConfigurator<Optional<?>> configurator = beanRegistrationPhase.getContext().configure(Optional.class);
        for (Type type : additionalTypes) {
            configurator.addType(type);
        }
        configurator.scope(BuiltinScope.DEPENDENT.getInfo());
        configurator.qualifiers(AnnotationInstance.create(CLAIM_NAME, null,
                new AnnotationValue[] { AnnotationValue.createStringValue("value", ""),

View on GitHub (pinned to e1c734241f)

Solutions

  1. Make the bean @RequestScoped so the claim resolves per request
  2. Wrap the injected type with org.eclipse.microprofile.jwt.ClaimValue (e.g. ClaimValue<Optional<String>>)
  3. Wrap with jakarta.inject.Provider<T> or jakarta.enterprise.inject.Instance<T> to defer resolution to access time

Example fix

// before
@ApplicationScoped
public class ClaimService {
    @Inject
    @Claim(claim = "upn")
    Optional<String> upn; // build-time failure
}

// after
@ApplicationScoped
public class ClaimService {
    @Inject
    ClaimValue<Optional<String>> upn; // resolved lazily per request
}
Defensive patterns

Strategy: type-guard

Validate before calling

// Prefer ClaimValue/Provider/Instance when injecting claims into non-request-scoped beans
@Inject ClaimValue<Optional<String>> upn; // safe in @ApplicationScoped

Type guard

// Guard: claims may only be injected directly in request-scoped beans
static boolean isDirectClaimInjectionSafe(Class<?> beanClass) {
    return beanClass.isAnnotationPresent(jakarta.enterprise.context.RequestScoped.class)
            || beanClass.isAnnotationPresent(jakarta.enterprise.context.SessionScoped.class);
}

Try / catch

try {
    cdiContainer();
} catch (IllegalStateException e) {
    if (e.getMessage().contains("can not be used to represent JWT claims")) {
        throw new IllegalStateException("Use ClaimValue/Provider/Instance or make the bean @RequestScoped", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: @Inject @Claim(claim = "...") Optional<String> claim (or a similar non-ClaimValue type) inside a bean annotated @Singleton or @ApplicationScoped; including cases where actualType is Optional (or non-ClaimValue) in such beans.

Common situations: Injecting claims into application-wide services (config holders, scheduled jobs); refactoring a @RequestScoped bean to @Singleton (or @ApplicationScoped) and forgetting the claim injection; copying claim-injection snippets into singleton REST resources.

Related errors


AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05). Data as JSON: /api/errors/f7428048f6d2a603. Report an issue: GitHub.