{"record":{"id":"9d9061a685772919","repo":"spring-projects/spring-security","slug":"please-ensure-there-is-one-unique-annotation-of-ty","errorCode":null,"errorMessage":"Please ensure there is one unique annotation of type %s attributed to %s. Found %d competing annotations: %s","messagePattern":"Please ensure there is one unique annotation of type (.+?) attributed to (.+?)\\. Found (.+?) competing annotations: (.+?)","errorType":"exception","errorClass":"AnnotationConfigurationException","httpStatus":null,"severity":"error","filePath":"core/src/main/java/org/springframework/security/core/annotation/UniqueSecurityAnnotationScanner.java","lineNumber":140,"sourceCode":"\t\t\treturn this.uniqueMethodAnnotationCache.computeIfAbsent(new MethodClassKey(method, targetClass), (k) -> {\n\t\t\t\tList<MergedAnnotation<A>> annotations = findMethodAnnotations(method, targetClass);\n\t\t\t\treturn requireUnique(method, annotations);\n\t\t\t});\n\t\t}\n\t\tthrow new AnnotationConfigurationException(\"Unsupported element of type \" + element.getClass());\n\t}\n\n\tprivate @Nullable MergedAnnotation<A> requireUnique(AnnotatedElement element,\n\t\t\tList<MergedAnnotation<A>> annotations) {\n\t\treturn switch (annotations.size()) {\n\t\t\tcase 0 -> null;\n\t\t\tcase 1 -> annotations.get(0);\n\t\t\tdefault -> {\n\t\t\t\tList<Annotation> synthesized = new ArrayList<>();\n\t\t\t\tfor (MergedAnnotation<A> annotation : annotations) {\n\t\t\t\t\tsynthesized.add(annotation.synthesize());\n\t\t\t\t}\n\t\t\t\tthrow new AnnotationConfigurationException(\"\"\"\n\t\t\t\t\t\tPlease ensure there is one unique annotation of type %s attributed to %s. \\\n\t\t\t\t\t\tFound %d competing annotations: %s\"\"\".formatted(this.types, element, annotations.size(),\n\t\t\t\t\t\tsynthesized));\n\t\t\t}\n\t\t};\n\t}\n\n\tprivate List<MergedAnnotation<A>> findParameterAnnotations(Parameter current) {\n\t\tList<MergedAnnotation<A>> directAnnotations = findDirectAnnotations(current);\n\t\tif (!directAnnotations.isEmpty()) {\n\t\t\treturn directAnnotations;\n\t\t}\n\t\tExecutable executable = current.getDeclaringExecutable();\n\t\tif (executable instanceof Method method) {\n\t\t\tdirectAnnotations = findClosestParameterAnnotations(method, method.getDeclaringClass(), current,\n\t\t\t\t\tnew HashSet<>());\n\t\t\tif (!directAnnotations.isEmpty()) {\n\t\t\t\treturn directAnnotations;","sourceCodeStart":122,"sourceCodeEnd":158,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/core/src/main/java/org/springframework/security/core/annotation/UniqueSecurityAnnotationScanner.java#L122-L158","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\n@PreAuthorize(\"hasRole('ADMIN')\")\n@PreAuthorize(\"hasAuthority('scope:read')\")\npublic void read() {}\n// after\n@PreAuthorize(\"hasRole('ADMIN') or hasAuthority('scope:read')\")\npublic void read() {}","handlingStrategy":"validation","validationCode":"long count = AnnotationUtils.findRepeatableAnnotations(method, PreAuthorize.class).size(); if (count > 1) throw new IllegalStateException(\"duplicate security annotation\");","typeGuard":null,"tryCatchPattern":"try { scanner.merge(element, targetClass); } catch (AnnotationConfigurationException ex) { logger.error(\"Duplicate security annotations: \" + ex.getMessage()); }","preventionTips":["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"],"tags":["annotation-scanning","duplicate-annotation","authorization","configuration"],"backgroundTag":"conflicting-config-options","analyzedSha":"96852e8860138a482cb13d1479573f24ff6443c6","analyzedAt":"2026-09-10T23:25:23.477Z","contentChangedAt":"2026-09-10T23:25:23.477Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}