Skip to content

fix(client): log a non-JSON response body as it is - #711

Merged
edeandrea merged 1 commit into
docling-project:mainfrom
edeandrea:fix/709-log-response-non-json-body
Sep 29, 2026
Merged

edeandrea merged 1 commit into
docling-project:mainfrom
edeandrea:fix/709-log-response-non-json-body

Conversation

@edeandrea

Copy link
Copy Markdown
Contributor

What

With both logResponses() and prettyPrint() enabled, DoclingServeClient.logResponse(...) tried to parse every response body as JSON so it could pretty-print it. A body that is not JSON, such as an error page from a gateway or proxy in front of docling-serve, made that parse throw. The exception replaced the real error, so the caller got a raw Jackson exception and the status code and response body were lost.

The body is now logged as it is, and the request reports the usual DoclingServeClientException.

How

  • New JsonReadException (a plain RuntimeException, deliberately not a DoclingServeClientException, since reading a text as JSON says nothing about an HTTP status code or response body). It is what readValue reports for text that is not valid JSON or does not match the requested type, whichever JSON library is used. The library's own exception is its cause.
  • Each Jackson client catches only its own parse exception in readValue (JacksonException on Jackson 3, JsonProcessingException on Jackson 2) and throws JsonReadException. Any other failure, such as one thrown by a custom deserializer, propagates unchanged.
  • logResponse catches only JsonReadException and logs the body unchanged.

The base class cannot name a Jackson type, because only one Jackson version may be on the classpath. A library-neutral exception lets it catch precisely the failure it cares about without a broad catch (RuntimeException), and without an extra abstract method on every subclass.

Behavior changes to be aware of

  • readValue used to throw a bare RuntimeException on Jackson 2 and let JacksonException escape on Jackson 3. Both are now JsonReadException. Code that caught JacksonException from the Jackson 3 client would stop matching.
  • Subclasses of DoclingServeClient must now throw JsonReadException from readValue for those failures. This is a contract change with no compile error; it is noted in whats-new.md.
  • HttpOperations.java shows a larger diff than the one @throws line I changed: Spotless is ratcheted from origin/main, so touching the file re-aligned four @param blocks that were already misformatted.
  • .gitignore gains .explyt/.

Tests

New nested NonJsonResponseTests in AbstractDoclingServeClientTests, so both Jackson backends inherit them. Its clients already have logResponses and prettyPrint enabled, which is the path the bug was in. Eight tests per backend:

  • HTML, plain-text and JSON error bodies keep their status code and body.
  • readValue throws exactly JsonReadException (and not a DoclingServeClientException) for non-JSON text and for JSON of another shape.
  • A failing deserializer's IllegalStateException propagates, both from readValue directly and while a response is being logged.

I checked that the tests can fail by mutating the code and confirming they turn red on both backends: restoring the old unguarded readValue in logResponse, widening the catch in logResponse, making a backend wrap every exception, making either backend stop wrapping, and re-coupling JsonReadException to DoclingServeClientException.

spotlessCheck and the full :docling-serve-client:test suite pass locally (207 tests, 0 failures).

Related

Closes #709

Found while reviewing #704, which adds a fallback for a 422 whose body is not a validation error. That fallback was bypassed for clients with both options enabled, because this code threw first. #704 can catch JsonReadException in the base class instead of a broad RuntimeException.

@edeandrea edeandrea added bug Something isn't working module:docling-serve-client The docling-serve-client module labels Sep 29, 2026
@edeandrea
edeandrea enabled auto-merge (squash) September 29, 2026 19:33
With both logResponses() and prettyPrint() enabled, logging a response
that is not JSON, such as an error page from a gateway in front of
docling-serve, failed with a raw Jackson exception before the status
code was checked, so the status code and body were lost.

readValue now throws JsonReadException, a library-neutral exception
that is not a DoclingServeClientException, for text that is not valid
JSON or does not match the requested type. logResponse catches only
that and logs the body unchanged; any other failure still propagates.

References docling-project#709

Signed-off-by: Eric Deandrea <eric.deandrea@ibm.com>
@edeandrea
edeandrea force-pushed the fix/709-log-response-non-json-body branch from 384e614 to 4890ab8 Compare September 29, 2026 19:34
@github-actions

Copy link
Copy Markdown

:java_duke: JaCoCo coverage report

Overall Project 49.68% 🔴

There is no coverage information present for the Files changed

@github-actions

github-actions Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
TestsPassed ✅SkippedFailed
Gradle Test Results (all modules & JDKs)2156 ran2156 passed0 skipped0 failed
TestResult
No test annotations available

@github-actions

Copy link
Copy Markdown

HTML test reports are available as workflow artifacts (zipped HTML).

• Download: Artifacts for this run

@github-actions

Copy link
Copy Markdown

HTML test reports are available as workflow artifacts (zipped HTML).

• Download: Artifacts for this run

@edeandrea
edeandrea merged commit 3721fd0 into docling-project:main Sep 29, 2026
29 checks passed
@edeandrea
edeandrea deleted the fix/709-log-response-non-json-body branch September 29, 2026 20:22
Ashfaqbs added a commit to Ashfaqbs/docling-java that referenced this pull request Sep 30, 2026
A 422 whose body cannot be parsed as a validation error (for example an
HTML page from a gateway) surfaced as a raw Jackson parse exception and
lost the status code and response body. Fall back to
DoclingServeClientException, as for any other 4xx/5xx.

Rebased onto main after docling-project#711 and docling-project#714:
- Narrow the fallback catch to the new JsonReadException instead of a
  broad RuntimeException, so a failure that is not a parse failure (a
  broken custom deserializer) still propagates.
- parseValidationError now returns Optional<ValidationError> instead of
  a @nullable, folded together with docling-project#714's no-details filter.
- Move the standalone tests into AbstractDoclingServeClientTests'
  UnprocessableEntityResponseTests, so both Jackson backends run them,
  and add a case guarding the narrowed catch via a mixin-based failing
  deserializer for ValidationError.
- Update serve-api.md and whats-new.md.

Signed-off-by: Ashfaqbs <105435085+Ashfaqbs@users.noreply.github.com>
edeandrea pushed a commit to Ashfaqbs/docling-java that referenced this pull request Oct 1, 2026
A 422 whose body cannot be parsed as a validation error (for example an
HTML page from a gateway) surfaced as a raw Jackson parse exception and
lost the status code and response body. Fall back to
DoclingServeClientException, as for any other 4xx/5xx.

Rebased onto main after docling-project#711 and docling-project#714:
- Narrow the fallback catch to the new JsonReadException instead of a
  broad RuntimeException, so a failure that is not a parse failure (a
  broken custom deserializer) still propagates.
- parseValidationError now returns Optional<ValidationError> instead of
  a @nullable, folded together with docling-project#714's no-details filter.
- Move the standalone tests into AbstractDoclingServeClientTests'
  UnprocessableEntityResponseTests, so both Jackson backends run them,
  and add a case guarding the narrowed catch via a mixin-based failing
  deserializer for ValidationError.
- Update serve-api.md and whats-new.md.

Signed-off-by: Ashfaqbs <105435085+Ashfaqbs@users.noreply.github.com>
edeandrea pushed a commit to Ashfaqbs/docling-java that referenced this pull request Oct 1, 2026
A 422 whose body cannot be parsed as a validation error (for example an
HTML page from a gateway) surfaced as a raw Jackson parse exception and
lost the status code and response body. Fall back to
DoclingServeClientException, as for any other 4xx/5xx.

Rebased onto main after docling-project#711 and docling-project#714:
- Narrow the fallback catch to the new JsonReadException instead of a
  broad RuntimeException, so a failure that is not a parse failure (a
  broken custom deserializer) still propagates.
- parseValidationError now returns Optional<ValidationError> instead of
  a @nullable, folded together with docling-project#714's no-details filter.
- Move the standalone tests into AbstractDoclingServeClientTests'
  UnprocessableEntityResponseTests, so both Jackson backends run them,
  and add a case guarding the narrowed catch via a mixin-based failing
  deserializer for ValidationError.
- Update serve-api.md and whats-new.md.

Signed-off-by: Ashfaqbs <105435085+Ashfaqbs@users.noreply.github.com>
edeandrea pushed a commit to Ashfaqbs/docling-java that referenced this pull request Oct 2, 2026
A 422 whose body cannot be parsed as a validation error (for example an
HTML page from a gateway) surfaced as a raw Jackson parse exception and
lost the status code and response body. Fall back to
DoclingServeClientException, as for any other 4xx/5xx.

Rebased onto main after docling-project#711 and docling-project#714:
- Narrow the fallback catch to the new JsonReadException instead of a
  broad RuntimeException, so a failure that is not a parse failure (a
  broken custom deserializer) still propagates.
- parseValidationError now returns Optional<ValidationError> instead of
  a @nullable, folded together with docling-project#714's no-details filter.
- Move the standalone tests into AbstractDoclingServeClientTests'
  UnprocessableEntityResponseTests, so both Jackson backends run them,
  and add a case guarding the narrowed catch via a mixin-based failing
  deserializer for ValidationError.
- Update serve-api.md and whats-new.md.

Signed-off-by: Ashfaqbs <105435085+Ashfaqbs@users.noreply.github.com>
edeandrea pushed a commit to Ashfaqbs/docling-java that referenced this pull request Oct 5, 2026
A 422 whose body cannot be parsed as a validation error (for example an
HTML page from a gateway) surfaced as a raw Jackson parse exception and
lost the status code and response body. Fall back to
DoclingServeClientException, as for any other 4xx/5xx.

Rebased onto main after docling-project#711 and docling-project#714:
- Narrow the fallback catch to the new JsonReadException instead of a
  broad RuntimeException, so a failure that is not a parse failure (a
  broken custom deserializer) still propagates.
- parseValidationError now returns Optional<ValidationError> instead of
  a @nullable, folded together with docling-project#714's no-details filter.
- Move the standalone tests into AbstractDoclingServeClientTests'
  UnprocessableEntityResponseTests, so both Jackson backends run them,
  and add a case guarding the narrowed catch via a mixin-based failing
  deserializer for ValidationError.
- Update serve-api.md and whats-new.md.

Signed-off-by: Ashfaqbs <105435085+Ashfaqbs@users.noreply.github.com>
edeandrea pushed a commit that referenced this pull request Oct 5, 2026
* fix(client): keep status and body when a 422 is not a validation error

A 422 whose body cannot be parsed as a validation error (for example an
HTML page from a gateway) surfaced as a raw Jackson parse exception and
lost the status code and response body. Fall back to
DoclingServeClientException, as for any other 4xx/5xx.

Rebased onto main after #711 and #714:
- Narrow the fallback catch to the new JsonReadException instead of a
  broad RuntimeException, so a failure that is not a parse failure (a
  broken custom deserializer) still propagates.
- parseValidationError now returns Optional<ValidationError> instead of
  a @nullable, folded together with #714's no-details filter.
- Move the standalone tests into AbstractDoclingServeClientTests'
  UnprocessableEntityResponseTests, so both Jackson backends run them,
  and add a case guarding the narrowed catch via a mixin-based failing
  deserializer for ValidationError.
- Update serve-api.md and whats-new.md.

Signed-off-by: Ashfaqbs <105435085+Ashfaqbs@users.noreply.github.com>

* fix(client): use Optional map/orElseGet chain and guard null detail bodies

Addresses edeandrea's remaining review notes on #704: getResponse now
throws validationError.map(...).orElseGet(...) instead of isPresent()/
get(), parenthesizes the ternary condition, and computes body.toString()
once as responseBody. Also adds "null" and {"detail":null} cases to
jsonBodyWithoutValidationDetailsKeepsStatusAndBody, which guard the
Optional.ofNullable in parseValidationError against regressing to an
unnoticed NPE.

Signed-off-by: Ashfaq <105435085+Ashfaqbs@users.noreply.github.com>

---------

Signed-off-by: Ashfaqbs <105435085+Ashfaqbs@users.noreply.github.com>
Signed-off-by: Ashfaq <105435085+Ashfaqbs@users.noreply.github.com>
@docling-java-ops docling-java-ops Bot added the released Issue has been released label Oct 5, 2026
@docling-java-ops

Copy link
Copy Markdown
Contributor

🎉 This issue has been resolved in v0.7.0 (Release Notes)

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

Labels

bug Something isn't working module:docling-serve-client The docling-serve-client module released Issue has been released

Projects

None yet

Development

Successfully merging this pull request may close these issues.

logResponse throws on non-JSON response bodies when prettyPrint is enabled, hiding the real error

1 participant