{"record":{"id":"e6dd8a31a55407b4","repo":"OpenFeign/feign","slug":"unable-to-decode-status-response-headers","errorCode":null,"errorMessage":"Unable to decode {status} response ({headers}) ...","messagePattern":"Unable to decode (.+?) response \\((.+?)\\) \\.\\.\\.","errorType":"exception","errorClass":"DecodeException","httpStatus":null,"severity":"error","filePath":"core/src/main/java/feign/codec/MultiDecoder.java","lineNumber":112,"sourceCode":"  /**\n   * Decodes using the first decoder that accepts the response.\n   *\n   * @param response {@inheritDoc}\n   * @param type {@inheritDoc}\n   * @return {@inheritDoc}\n   * @throws IOException {@inheritDoc}\n   * @throws DecodeException when no decoder accepts the response, or the chosen one fails\n   * @throws FeignException {@inheritDoc}\n   */\n  @Override\n  public Object decode(Response response, Type type)\n      throws IOException, DecodeException, FeignException {\n    for (PredicatedDecoder decoder : decoders) {\n      if (decoder.canDecode(response, type)) {\n        return decoder.decode(response, type);\n      }\n    }\n    throw new DecodeException(\n        response.status(), unableToDecode(response, type), response.request());\n  }\n\n  private String unableToDecode(Response response, Type type) {\n    StringBuilder message =\n        new StringBuilder(\"Unable to decode \")\n            .append(response.status())\n            .append(\" response (\")\n            .append(headers(response))\n            .append(\") as \")\n            .append(type == null ? \"the expected type\" : type.getTypeName())\n            .append(\". Decoders tried, in order:\");\n    appendTo(message, \"\\n  \");\n    return message\n        .append(\"\\nRegister a decoder that accepts it, or add a catch-all\")\n        .append(\" (DecoderPredicate.any()) last.\")\n        .toString();\n  }","sourceCodeStart":94,"sourceCodeEnd":130,"githubUrl":"https://github.com/OpenFeign/feign/blob/e2a1e27560a1e68840c34f031afca88b36096e30/core/src/main/java/feign/codec/MultiDecoder.java#L94-L130","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nFeign.builder().decoder(new MultiDecoder.Builder().add(new JacksonDecoder()).build());\n// after\nFeign.builder().decoder(new MultiDecoder.Builder()\n    .add(new JacksonDecoder())\n    .add(new Decoder() { // fallback\n      public Object decode(Response r, Type t) {\n        return Util.toString(r.body().asReader(r.charset()));\n      }\n    }).build());","handlingStrategy":"try-catch","validationCode":"// before building the client, verify decoder coverage for expected content types\nList<String> expected = List.of(\"application/json\", \"text/plain\");\nboolean covered = expected.stream().anyMatch(ct ->\n    registeredDecoders.stream().anyMatch(d -> d.canDecode(\n        Response.builder().status(200).reason(\"OK\")\n            .request(Request.create(Request.HttpMethod.GET, \"/\", Collections.emptyMap(), null, Util.UTF_8))\n            .headers(Collections.singletonMap(\"Content-Type\", List.of(ct)))\n            .build(), MyType.class)));","typeGuard":null,"tryCatchPattern":"try {\n  return api.call();\n} catch (DecodeException e) {\n  if (e.getMessage().startsWith(\"Unable to decode\")) {\n    // inspect e.status()/content type, add matching decoder or fall back to raw body\n    throw new ClientConfigurationException(\"No decoder for response: \" + e.getMessage(), e);\n  }\n  throw e;\n}","preventionTips":["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"],"tags":["feign","decoding","content-type","configuration"],"backgroundTag":"unexpected-response-shape","analyzedSha":"e2a1e27560a1e68840c34f031afca88b36096e30","analyzedAt":"2026-09-10T12:37:37.238Z","contentChangedAt":"2026-09-10T12:37:37.238Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}