OpenFeign/feign · error · DecodeException
Unable to decode response ( ) ...
Error message
Unable to decode {status} response ({headers}) ... What it means
Feign's MultiDecoder iterates its registered PredicatedDecoders and throws this DecodeException when none of them declares it can decode the response for the requested type. It is a configuration/coverage error: the decoder chain simply has no entry matching the response's Content-Type and the target Java type. The message includes the response status and headers so you can see which Content-Type went unmatched.
Solutions
- Register a decoder whose canDecode matches the actual response Content-Type and return type, e.g. .decoder(new JacksonDecoder()) or a custom PredicatedDecoder
- Add a catch-all/fallback decoder (e.g. new Decoder() returning String or the default decoder) as the last entry in the MultiDecoder builder
- Check the Content-Type header in the message and confirm the server is returning the format you expect; fix the endpoint or add the matching codec module (gson/jackson/etc.)
- Decode to String first to inspect the body if the format is unexpected
Example fix
// before
Feign.builder().decoder(new MultiDecoder.Builder().add(new JacksonDecoder()).build());
// after
Feign.builder().decoder(new MultiDecoder.Builder()
.add(new JacksonDecoder())
.add(new Decoder() { // fallback
public Object decode(Response r, Type t) {
return Util.toString(r.body().asReader(r.charset()));
}
}).build()); Defensive patterns
Strategy: try-catch
Validate before calling
// before building the client, verify decoder coverage for expected content types
List<String> expected = List.of("application/json", "text/plain");
boolean covered = expected.stream().anyMatch(ct ->
registeredDecoders.stream().anyMatch(d -> d.canDecode(
Response.builder().status(200).reason("OK")
.request(Request.create(Request.HttpMethod.GET, "/", Collections.emptyMap(), null, Util.UTF_8))
.headers(Collections.singletonMap("Content-Type", List.of(ct)))
.build(), MyType.class))); Try / catch
try {
return api.call();
} catch (DecodeException e) {
if (e.getMessage().startsWith("Unable to decode")) {
// inspect e.status()/content type, add matching decoder or fall back to raw body
throw new ClientConfigurationException("No decoder for response: " + e.getMessage(), e);
}
throw e;
} Prevention
- Always end MultiDecoder chains with a fallback decoder (Decoder.Default or String) for unmatched Content-Types
- Add a codec module matching every Content-Type your services return (feign-jackson for JSON, feign-jaxb for XML, etc.)
- Log response Content-Type headers in integration tests to catch uncovered types early
- Re-check decoder predicates after server-side Content-Type changes
When it happens
Trigger: Calling a Feign client built with Feign.builder().decoder(MultiDecoder) (or the default pipeline) where the response's Content-Type (e.g. application/xml, text/plain, application/hal+json) matches no registered decoder's predicate for the method's return type.
Common situations: Server returns a Content-Type the decoder list does not cover (e.g. text/plain error body or application/problem+json); a custom decoder's canDecode predicate is too narrow; an encoder/decoder was registered for JSON but the endpoint returns XML; after upgrading, Feign's default decoder no longer covers a type the old default handled.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Status Code [ ] has already been declared to throw [ ] and…
- at least one decoder is required
- Unable to encode ( ) ...
- at least one encoder is required
- is not a type supported by this decoder.
AI-assisted analysis of OpenFeign/feign@e2a1e27560 (2026-09-10).
Data as JSON: /api/errors/e6dd8a31a55407b4.
Report an issue: GitHub.
Appendix: source
Thrown at core/src/main/java/feign/codec/MultiDecoder.java:112
/**
* Decodes using the first decoder that accepts the response.
*
* @param response {@inheritDoc}
* @param type {@inheritDoc}
* @return {@inheritDoc}
* @throws IOException {@inheritDoc}
* @throws DecodeException when no decoder accepts the response, or the chosen one fails
* @throws FeignException {@inheritDoc}
*/
@Override
public Object decode(Response response, Type type)
throws IOException, DecodeException, FeignException {
for (PredicatedDecoder decoder : decoders) {
if (decoder.canDecode(response, type)) {
return decoder.decode(response, type);
}
}
throw new DecodeException(
response.status(), unableToDecode(response, type), response.request());
}
private String unableToDecode(Response response, Type type) {
StringBuilder message =
new StringBuilder("Unable to decode ")
.append(response.status())
.append(" response (")
.append(headers(response))
.append(") as ")
.append(type == null ? "the expected type" : type.getTypeName())
.append(". Decoders tried, in order:");
appendTo(message, "\n ");
return message
.append("\nRegister a decoder that accepts it, or add a catch-all")
.append(" (DecoderPredicate.any()) last.")
.toString();
}View on GitHub (pinned to e2a1e27560)