quarkusio/quarkus · error · DefinitionException

Qualifier annotation '${qualifierClass}' contains a member '

Error message

Qualifier annotation '${qualifierClass}' contains a member '${member}' with ${problem}-valued return type. All such members have to be annotated with @jakarta.enterprise.util.Nonbinding

What it means

CDI qualifiers may only have binding members of types that support equality: primitives, String, Class, enum, annotation, and arrays thereof. During BeanDeployment validation, Arc inspects each qualifier member's return type; if it's some other class type (e.g. an arbitrary POJO) — even potentially an annotation it cannot resolve — it throws DefinitionException requiring @Nonbinding on such members.

Source

Thrown at independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java:911

     * @param qualifierClass class info of the qualifier
     * @param nonbindingMembers collection of members we consider {@code @Nonbinding} for synthetic qualifier, null otherwise
     */
    private void validateQualifier(ClassInfo qualifierClass, Set<String> nonbindingMembers) {
        for (MethodInfo mi : qualifierClass.methods()) {
            Type returnType = mi.returnType();
            if ((nonbindingMembers != null && !nonbindingMembers.contains(mi.name()))
                    || (nonbindingMembers == null && mi.annotation(DotNames.NONBINDING) == null)) {
                String problem = null;
                if (returnType.kind().equals(Type.Kind.ARRAY)) {
                    problem = "array";
                } else if (returnType.kind().equals(Type.Kind.CLASS)) {
                    ClassInfo typeClassInfo = beanArchiveImmutableIndex.getClassByName(returnType.asClassType().name());
                    if (typeClassInfo != null && typeClassInfo.isAnnotation()) {
                        problem = "annotation";
                    }
                }
                if (problem != null) {
                    throw new DefinitionException("Qualifier annotation '" + qualifierClass + "' contains a member '"
                            + mi.name()
                            + "' with " + problem
                            + "-valued return type. All such members have to be annotated with @jakarta.enterprise.util.Nonbinding");
                }
            }
        }
    }

    private Map<DotName, ClassInfo> findContainerAnnotations(Map<DotName, ClassInfo> annotations) {
        Map<DotName, ClassInfo> containerAnnotations = new HashMap<>();
        for (ClassInfo annotation : annotations.values()) {
            AnnotationInstance repeatableMetaAnnotation = annotation.declaredAnnotation(DotNames.REPEATABLE);
            if (repeatableMetaAnnotation != null) {
                DotName containerAnnotationName = repeatableMetaAnnotation.value().asClass().name();
                ClassInfo containerClass = getClassByName(getBeanArchiveIndex(), containerAnnotationName);
                containerAnnotations.put(containerAnnotationName, containerClass);
            }
        }

View on GitHub (pinned to e1c734241f)

Solutions

  1. Add @jakarta.enterprise.util.Nonbinding to the offending member
  2. Change the member type to a binding-safe type (String, Class, enum, primitive, nested annotation, or arrays of these)
  3. Remove the member if it isn't needed for matching
  4. If it must carry complex data, encode it as a String and parse it where used

Example fix

// before
@Qualifier @Retention(RUNTIME) @interface Fast { MyConfig value(); }
// after
@Qualifier @Retention(RUNTIME) @interface Fast { @Nonbinding MyConfig value(); }
Defensive patterns

Strategy: validation

Validate before calling

for (MethodInfo m : qualifierClassInfo.methods()) {
    if (m.isStatic() || m.isSynthetic()) continue;
    Type r = m.returnType();
    boolean bindingSafe = r.kind() == Type.Kind.PRIMITIVE || isLegalArrayType(r) || isStringClassEnumAnnotation(r);
    if (!bindingSafe && !m.hasDeclaredAnnotation(DotNames.NONBINDING)) throw new DefinitionException(m + " must be @Nonbinding");
}

Try / catch

try { deployment.init(); }
catch (DefinitionException e) { log.error(e.getMessage() + " — add @Nonbinding or change the member type"); throw e; }

Prevention

When it happens

Trigger: Declaring a qualifier annotation with a member whose return type is a class type other than the allowed ones (or not @Nonbinding), e.g. @MyQualifier Foo foo(); where Foo is an interface/class that's not an annotation or unsupported type.

Common situations: Custom qualifiers with Object/POJO/list-typed members; qualifiers copied from Spring/Guice code (@Guice allows arbitrary types); missing @Nonbinding after refactoring a member type to a complex type.

Related errors


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