Skip to content

fix(java/feign): handle binary response types in ApiResponseDecoder#22939

Merged
wing328 merged 3 commits intoOpenAPITools:masterfrom
cziberpv:fix/feign-binary-response-decoder
Feb 11, 2026
Merged

fix(java/feign): handle binary response types in ApiResponseDecoder#22939
wing328 merged 3 commits intoOpenAPITools:masterfrom
cziberpv:fix/feign-binary-response-decoder

Conversation

@cziberpv
Copy link
Contributor

@cziberpv cziberpv commented Feb 10, 2026

Summary

The Feign library's ApiResponseDecoder routes all responses through JacksonDecoder, including binary ones (File, byte[], InputStream). This causes JsonParseException when an endpoint returns non-JSON content (e.g. PDF, ZIP, images):

com.fasterxml.jackson.core.JsonParseException: Unexpected character ('%' (code 37)):
expected a valid value (JSON String, Number, Array, Object or token 'null', 'true' or 'false')

(% is the first byte of %PDF-1.4)

This is the Feign equivalent of the issue fixed for the native library in #21346.

Changes

Modified ApiResponseDecoder.mustache to detect binary return types before delegating to JacksonDecoder:

  • File → stream response body to a temp file (filename extracted from Content-Disposition header when available, consistent with native library's prepareDownloadFile)
  • byte[]response.body().asInputStream().readAllBytes()
  • InputStreamresponse.body().asInputStream() (no transformation — the caller chose this type intentionally)

This applies to both direct return types and ApiResponse<T> wrappers. The ApiResponse<File> case was also broken because ApiResponseDecoder unwraps the inner type and calls super.decode() (JacksonDecoder), bypassing any subclass override due to Java's static dispatch.

Non-binary responses are completely unaffected — they follow the existing JacksonDecoder path.

Design decisions

  • Consistent with native library fix (Java Native Client: handle Byte[] and File response types correctly #21346): same patterns for temp file creation, Content-Disposition parsing, deleteOnExit()
  • Minimal intervention: the decoder only avoids routing binary types through Jackson. It does not transform data beyond what the return type requires
  • No new classes or dependencies: the fix is contained within the existing ApiResponseDecoder

Related issues

How to reproduce

  1. OpenAPI spec with an endpoint returning type: string, format: binary (e.g. PDF download)
  2. Generate Java client with library=feign
  3. Call the endpoint → JsonParseException on first byte of binary content

How tested

Tested against a real API returning application/pdf responses. Verified that:

  • File return type works (PDF streamed to temp file)
  • byte[] return type works
  • JSON endpoints still work (no regression)

PR checklist

  • Read the contribution guidelines
  • Pull Request title clearly describes the work in the pull request and Pull Request description provides details about how to validate the work
  • Ran generate-samples.sh for all java-feign* configs. Committed all changed files
  • PR filed against the master branch

@bbdouglas @sreeshas @jfiala @lukoyanov @cbornet @jeff9finger @karismann @Zomzog @lwlee2608 @martin-mfg


Summary by cubic

Fixes Feign ApiResponseDecoder to handle binary responses (File, byte[], InputStream) without Jackson, avoiding JsonParseException on PDFs, ZIPs, and images. Adds safer filename parsing and path sanitization, and handles empty bodies; JSON decoding is unchanged.

  • Bug Fixes
    • Detect binary types and bypass JacksonDecoder.
    • File: stream to a temp file; parse Content-Disposition (quoted/unquoted); sanitize filename; deleteOnExit.
    • Handle empty response bodies (e.g. 204/205) by returning null for binary types.
    • byte[]: read all bytes; InputStream: return as-is.
    • Works with ApiResponse and preserves headers/status.
    • Regenerated feign-hc5 sample to match feign and feign-no-nullable with the updated decoder.

Written for commit cb7ed8e. Summary will update on new commits.

The Feign library's ApiResponseDecoder routes all responses through
JacksonDecoder, including binary ones (File, byte[], InputStream).
This causes JsonParseException when an endpoint returns non-JSON
content (e.g. PDF, ZIP, images).

Add binary type detection and handling before delegating to
JacksonDecoder. This applies to both direct return types and
ApiResponse<T> wrappers.

Consistent with the native library fix in OpenAPITools#21346.

Closes OpenAPITools#2486

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

9 issues found across 4 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="modules/openapi-generator/src/main/resources/Java/libraries/feign/ApiResponseDecoder.mustache">

<violation number="1" location="modules/openapi-generator/src/main/resources/Java/libraries/feign/ApiResponseDecoder.mustache:62">
P2: NullPointerException risk: decodeBinary calls response.body().asInputStream() without guarding against null bodies (e.g., 204/205 responses return null body in Feign).</violation>

<violation number="2" location="modules/openapi-generator/src/main/resources/Java/libraries/feign/ApiResponseDecoder.mustache:75">
P1: Unvalidated filename from Content-Disposition is resolved directly, allowing path traversal/absolute paths to create files outside the temp directory</violation>
</file>

<file name="samples/client/petstore/java/feign-no-nullable/src/main/java/org/openapitools/client/ApiResponseDecoder.java">

<violation number="1" location="samples/client/petstore/java/feign-no-nullable/src/main/java/org/openapitools/client/ApiResponseDecoder.java:86">
P2: Untrusted filename from Content-Disposition is used directly in tempDir.resolve(...), allowing path traversal to create files outside the intended temp directory.</violation>
</file>

<file name="samples/client/petstore/java/feign-hc5/src/main/java/org/openapitools/client/ApiResponseDecoder.java">

<violation number="1" location="samples/client/petstore/java/feign-hc5/src/main/java/org/openapitools/client/ApiResponseDecoder.java:39">
P2: Filename regex excludes whitespace, causing Content-Disposition filenames containing spaces to be truncated or not captured.</violation>

<violation number="2" location="samples/client/petstore/java/feign-hc5/src/main/java/org/openapitools/client/ApiResponseDecoder.java:73">
P2: NullPointerException risk: decodeBinary dereferences response.body() without null check; Feign returns null body for 204 responses.</violation>

<violation number="3" location="samples/client/petstore/java/feign-hc5/src/main/java/org/openapitools/client/ApiResponseDecoder.java:86">
P1: Unsanitized Content-Disposition filename allows path traversal when resolving tempDir; a filename like "../evil.sh" can escape the temp directory and create arbitrary files.</violation>
</file>

<file name="samples/client/petstore/java/feign/src/main/java/org/openapitools/client/ApiResponseDecoder.java">

<violation number="1" location="samples/client/petstore/java/feign/src/main/java/org/openapitools/client/ApiResponseDecoder.java:39">
P2: Filename extraction regex disallows spaces, truncating filenames like `"my invoice.pdf"` to `"my"` when parsing Content-Disposition.</violation>

<violation number="2" location="samples/client/petstore/java/feign/src/main/java/org/openapitools/client/ApiResponseDecoder.java:73">
P2: Binary decoder dereferences a potentially null response body, causing NPE on empty responses.</violation>

<violation number="3" location="samples/client/petstore/java/feign/src/main/java/org/openapitools/client/ApiResponseDecoder.java:86">
P1: Untrusted filename from Content-Disposition is resolved directly, allowing path traversal and arbitrary file write outside the temp directory.</violation>
</file>

Since this is your first cubic review, here's how it works:

  • cubic automatically reviews your code and comments on bugs and improvements
  • Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
  • Add one-off context when rerunning by tagging @cubic-dev-ai with guidance or docs links (including llms.txt)
  • Ask questions if you need clarification on any suggestion

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

cziberpv and others added 2 commits February 10, 2026 11:56
- Sanitize Content-Disposition filename to prevent path traversal
  (Paths.get(filename).getFileName() strips directory components)
- Add null check for response.body() to handle 204/205 empty responses
- Fix regex to support quoted filenames with spaces
  (e.g. filename="my invoice.pdf")
The feign-hc5 sample was missed during the second commit's regeneration
because setTemplateDir("feign") overrides the filesystem templateDir
from the config, causing the generator to use embedded JAR resources.
After rebuilding the JAR with the updated mustache template, the
feign-hc5 sample now matches feign and feign-no-nullable.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@wing328 wing328 merged commit b39aad0 into OpenAPITools:master Feb 11, 2026
83 checks passed
@wing328
Copy link
Member

wing328 commented Feb 11, 2026

thanks for the PR, which has been merged.

a minor suggestion is to add docstrings for newly-added functions so as to make the code more readable for humans/AI coding agent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants