spring-projects/spring-framework · error · IllegalArgumentException

Qualifier type [{}] needs to be annotation type

Error message

Qualifier type [{}] needs to be annotation type

What it means

Thrown by CustomAutowireConfigurer when a resolved custom qualifier type is not an annotation type (i.e., does not extend java.lang.annotation.Annotation). After resolving the Class or String to a Class object, the check Annotation.class.isAssignableFrom(customType) at line 114 fails for non-annotation classes.

Source

Thrown at spring-beans/src/main/java/org/springframework/beans/factory/annotation/CustomAutowireConfigurer.java:115

			if (!(dlbf.getAutowireCandidateResolver() instanceof QualifierAnnotationAutowireCandidateResolver)) {
				dlbf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver());
			}
			QualifierAnnotationAutowireCandidateResolver resolver =
					(QualifierAnnotationAutowireCandidateResolver) dlbf.getAutowireCandidateResolver();
			for (Object value : this.customQualifierTypes) {
				Class<? extends Annotation> customType = null;
				if (value instanceof Class) {
					customType = (Class<? extends Annotation>) value;
				}
				else if (value instanceof String className) {
					customType = (Class<? extends Annotation>) ClassUtils.resolveClassName(className, this.beanClassLoader);
				}
				else {
					throw new IllegalArgumentException(
							"Invalid value [" + value + "] for custom qualifier type: needs to be Class or String.");
				}
				if (!Annotation.class.isAssignableFrom(customType)) {
					throw new IllegalArgumentException(
							"Qualifier type [" + customType.getName() + "] needs to be annotation type");
				}
				resolver.addQualifierType(customType);
			}
		}
	}

}

View on GitHub (pinned to 69bf83ad71)

Solutions

  1. Ensure the specified type is a Java annotation (declared with @interface).
  2. Create a proper custom qualifier annotation annotated with @Target and @Retention.
  3. Verify the class name resolves to an annotation type, not a regular class or interface.

Example fix

// before
configurer.setCustomQualifierTypes(
    Set.of(MyService.class)); // regular class, not annotation

// after
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MyQualifier { }

configurer.setCustomQualifierTypes(
    Set.of(MyQualifier.class)); // annotation type
Defensive patterns

Strategy: validation

Validate before calling

// Validate that each qualifier type is an annotation before registering
for (Object value : customQualifierTypes) {
    Class<?> type = (value instanceof Class<?> c) ? c
        : ClassUtils.resolveClassName((String) value, classLoader);
    if (!Annotation.class.isAssignableFrom(type)) {
        throw new IllegalArgumentException(
            type.getName() + " is not an annotation type");
    }
}

Type guard

// Type guard: ensure a class is an annotation before using as qualifier
static boolean isQualifierAnnotation(Class<?> type) {
    return type != null && Annotation.class.isAssignableFrom(type);
}

Prevention

When it happens

Trigger: Specifying a regular class or interface (not annotated with @Target/@Retention/@interface) as a custom qualifier type. For example, passing com.example.SomeService.class (a regular class) instead of a custom @Qualifier-style annotation.

Common situations: Confusing a service/component class with a qualifier annotation. Typing the wrong fully-qualified class name in XML, resolving to a non-annotation class. Passing an interface that is not an annotation interface.

Related errors


AI-assisted analysis of spring-projects/spring-framework@69bf83ad71 (2026-08-09). Data as JSON: /api/errors/2deb0be9252a5c56. Report an issue: GitHub.