OpenFeign/feign · error · IllegalStateException
Status Code [ ] has already been declared to throw [ ] and…
Error message
Status Code [${statusCode}] has already been declared to throw [${existing}] and [${duplicate}] - dupe definition What it means
Thrown by AnnotationErrorDecoder.readAnnotation when the same HTTP status code is mapped to more than one exception class within a single @ErrorHandling configuration (duplicated across codeSpecific entries). The library requires each status code to map to exactly one exception so decoding is unambiguous. This fails fast at proxy creation time, not at request time.
Solutions
- Remove or change the duplicate status code so each code appears in only one @ErrorCodes entry per @ErrorHandling annotation
- Consolidate the entries so one exception covers the shared status code, or move one @ErrorCodes block to method-level @ErrorHandling to override the interface-level mapping
- Search the annotated interface for all occurrences of the conflicting status code
Example fix
// before
@ErrorHandling(codeSpecific = {
@ErrorCodes(codes = {404}, generate = UserNotFound.class),
@ErrorCodes(codes = {404}, generate = ResourceMissing.class)
})
// after
@ErrorHandling(codeSpecific = {
@ErrorCodes(codes = {404}, generate = UserNotFound.class),
@ErrorCodes(codes = {410}, generate = ResourceMissing.class)
}) Defensive patterns
Strategy: validation
Validate before calling
Set<Integer> seen = new HashSet<>();
for (ErrorCodes ec : errorHandling.codeSpecific()) {
for (int code : ec.codes()) {
if (!seen.add(code)) {
throw new IllegalArgumentException("Duplicate status code mapped twice: " + code);
}
}
} Try / catch
try (Feign feign = Feign.builder()...build()) { ... } catch (IllegalStateException e) {
if (e.getMessage().contains("has already been declared")) { /* fix annotation config */ }
} Prevention
- Keep each status code mapped exactly once per @ErrorHandling scope
- Prefer method-level @ErrorHandling overrides instead of duplicating codes at interface level
- Review @ErrorCodes blocks after merges and refactors for copy-pasted codes
When it happens
Trigger: Declaring two @ErrorCodes entries in @ErrorHandling(codeSpecific={...}) whose codes() arrays contain the same integer; e.g. @ErrorCodes(codes={404}, generate=NotFoundA.class) and @ErrorCodes(codes={404}, generate=NotFoundB.class) on the same interface or method.
Common situations: Copy-pasting an @ErrorCodes block and forgetting to change the status code; merging two interfaces' error configs where both map 500; refactoring exception classes and leaving a stale duplicate entry.
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
- Cannot generate exception - check constructor parameter…
- Too many constructors marked with @FeignExceptionConstructor
- Cannot find any suitable constructor in class
- Cannot access constructor
- Cannot instantiate exception with constructor
AI-assisted analysis of OpenFeign/feign@e2a1e27560 (2026-09-10).
Data as JSON: /api/errors/2ed7b53a5a2d203a.
Report an issue: GitHub.
Appendix: source
Thrown at annotation-error-decoder/src/main/java/feign/error/AnnotationErrorDecoder.java:168
}
}
return annotation;
}
static ErrorHandlingDefinition readAnnotation(
ErrorHandling errorHandling, Decoder responseBodyDecoder) {
ExceptionGenerator defaultException =
new ExceptionGenerator.Builder()
.withResponseBodyDecoder(responseBodyDecoder)
.withExceptionType(errorHandling.defaultException())
.build();
Map<Integer, ExceptionGenerator> statusCodesDefinition =
new HashMap<Integer, ExceptionGenerator>();
for (ErrorCodes statusCodeDefinition : errorHandling.codeSpecific()) {
for (int statusCode : statusCodeDefinition.codes()) {
if (statusCodesDefinition.containsKey(statusCode)) {
throw new IllegalStateException(
"Status Code ["
+ statusCode
+ "] "
+ "has already been declared to throw ["
+ statusCodesDefinition.get(statusCode).getExceptionType().getName()
+ "] "
+ "and ["
+ statusCodeDefinition.generate()
+ "] - dupe definition");
}
statusCodesDefinition.put(
statusCode,
new ExceptionGenerator.Builder()
.withResponseBodyDecoder(responseBodyDecoder)
.withExceptionType(statusCodeDefinition.generate())
.build());
}
}View on GitHub (pinned to e2a1e27560)