spring-projects/spring-security · error · AnnotationConfigurationException
Please ensure there is one unique annotation of type
Error message
Please ensure there is one unique annotation of type %s attributed to %s. Found %d competing annotations: %s
What it means
The unique-security-annotation scanner requires exactly one annotation of the target type on the element it inspects. When it finds zero-plus-competing (multiple) annotations of the same security type on a class or method it throws AnnotationConfigurationException with the element and list of competing annotations, because it cannot decide which annotation authorizes the method.
Solutions
- Remove the duplicate annotation so exactly one remains on the target element
- Keep security annotations on one level only (method OR class), not duplicated across both when the scanner merges them
- If you need multiple rules, combine them into a single expression (e.g. @PreAuthorize("hasRole('A') or hasRole('B')")) instead of multiple annotations
Example fix
// before
@PreAuthorize("hasRole('ADMIN')")
@PreAuthorize("hasAuthority('scope:read')")
public void read() {}
// after
@PreAuthorize("hasRole('ADMIN') or hasAuthority('scope:read')")
public void read() {} Defensive patterns
Strategy: validation
Validate before calling
long count = AnnotationUtils.findRepeatableAnnotations(method, PreAuthorize.class).size(); if (count > 1) throw new IllegalStateException("duplicate security annotation"); Try / catch
try { scanner.merge(element, targetClass); } catch (AnnotationConfigurationException ex) { logger.error("Duplicate security annotations: " + ex.getMessage()); } Prevention
- Declare only one security annotation per method
- Do not duplicate security annotations across interface and implementation
- Combine multiple rules into a single expression
- Run annotation-duplication checks in tests
When it happens
Trigger: Declaring two security annotations of the same type on one method or on both the class and an overriding method when lookup merges them — e.g. two @PreAuthorize annotations, or @PreAuthorize at class level plus a competing duplicate at method level where composition yields more than one match.
Common situations: Accidental duplicate imports causing two similar security annotations; meta-annotation composition accidentally matching the same type twice; copy-pasting @PreAuthorize from interface to implementation with an additional one already present.
Understand the failure class
Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.
Related errors
- Unsupported element of type " + element.getClass()
- Unsupported element of type
- A filter chain that matches any request
- Access Denied
- Access is denied
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/9d9061a685772919.
Report an issue: GitHub.
Appendix: source
Thrown at core/src/main/java/org/springframework/security/core/annotation/UniqueSecurityAnnotationScanner.java:140
return this.uniqueMethodAnnotationCache.computeIfAbsent(new MethodClassKey(method, targetClass), (k) -> {
List<MergedAnnotation<A>> annotations = findMethodAnnotations(method, targetClass);
return requireUnique(method, annotations);
});
}
throw new AnnotationConfigurationException("Unsupported element of type " + element.getClass());
}
private @Nullable MergedAnnotation<A> requireUnique(AnnotatedElement element,
List<MergedAnnotation<A>> annotations) {
return switch (annotations.size()) {
case 0 -> null;
case 1 -> annotations.get(0);
default -> {
List<Annotation> synthesized = new ArrayList<>();
for (MergedAnnotation<A> annotation : annotations) {
synthesized.add(annotation.synthesize());
}
throw new AnnotationConfigurationException("""
Please ensure there is one unique annotation of type %s attributed to %s. \
Found %d competing annotations: %s""".formatted(this.types, element, annotations.size(),
synthesized));
}
};
}
private List<MergedAnnotation<A>> findParameterAnnotations(Parameter current) {
List<MergedAnnotation<A>> directAnnotations = findDirectAnnotations(current);
if (!directAnnotations.isEmpty()) {
return directAnnotations;
}
Executable executable = current.getDeclaringExecutable();
if (executable instanceof Method method) {
directAnnotations = findClosestParameterAnnotations(method, method.getDeclaringClass(), current,
new HashSet<>());
if (!directAnnotations.isEmpty()) {
return directAnnotations;View on GitHub (pinned to 96852e8860)