Skip to content

Propagate non-recoverable JSON conversion failures instead of demoting them to a generic 500 - #6

Draft
apoupard wants to merge 1 commit into
mainfrom
fix/nonrecoverable-conversion-exception
Draft

Propagate non-recoverable JSON conversion failures instead of demoting them to a generic 500#6
apoupard wants to merge 1 commit into
mainfrom
fix/nonrecoverable-conversion-exception

Conversation

@apoupard

@apoupard apoupard commented Aug 5, 2026

Copy link
Copy Markdown

Summary

spring-cloudGH-1429 (ef533f3, already on main) made convertNonMessageInputIfNecessary rethrow (rather than swallow) a JSON conversion failure when the request's Content-Type is JSON (failOnJsonError), fixing a misleading ClassCastException into a real Jackson exception in the logs. That's a great fix for the log message - but the rethrown exception is still wrapped as a plain IllegalStateException by convertInputPublisherIfNecessary, indistinguishable from any other conversion failure. Two consequences:

  • It still resolves to a generic 500 for any web layer that doesn't parse exception messages to guess what went wrong.
  • There's no way for a downstream consumer (a web framework's exception resolver, a custom @ControllerAdvice, an app's own error handler) to recognize "this converter deliberately, definitively rejected this input" without inspecting the message/cause chain.

This PR adds NonRecoverableConversionException, a narrow MessageConversionException subtype thrown only from the failOnJsonError path, and lets it propagate unwrapped through convertInputPublisherIfNecessary instead of being folded into the generic IllegalStateException bucket.

  • No new dependency: MessageConversionException already lives in spring-messaging, a non-optional dependency of spring-cloud-function-context.
  • No behavior change for any other exception type or code path - only the failOnJsonError branch's exception type changes.

Known follow-up (intentionally out of scope here)

A function declared as Function<Message<T>, R> (explicit Message-wrapped type) never reaches this path at all: convertInputMessageIfNecessary deliberately returns the original, unconverted message whenever the target type is itself Message<T> (to support legitimate no-conversion-needed cases like KafkaNull), which silently masks the same class of failure a different way. Functions declared over the plain payload type - the common case, and the only shape the new PojoFunctionIntegrationTests covers - are unaffected and fixed by this change.

Test plan

  • SimpleFunctionRegistryTests#testReactivePojoFunctionPropagatesNonRecoverableConversionExceptionForMalformedJson (new) - removal-proofed: fails without the fix, passes with it.
  • SimpleFunctionRegistryTests#testReactiveFunctionMessages (existing, valid-JSON case) - still green, confirming no behavior change on the success path.
  • Full spring-cloud-function-context + spring-cloud-function-web suite: 199 tests, 0 failures, 0 errors (including the new PojoFunctionIntegrationTests).

Was previously opened against spring-cloud#1436 by mistake and closed - this is the fork-local equivalent.

…ead of demoting them to a generic 500

spring-cloudGH-1429/ef533f377 already made convertNonMessageInputIfNecessary rethrow
(rather than swallow) a JSON conversion failure when the request's
Content-Type is JSON (failOnJsonError), fixing a misleading
ClassCastException into a real Jackson exception in the logs. But that
rethrown exception is still wrapped as a plain IllegalStateException by
convertInputPublisherIfNecessary, indistinguishable from any other
conversion failure - so it still resolves to a generic 500, and there is
no way for a downstream consumer (a web framework's exception resolver,
a custom @ControllerAdvice, etc.) to recognize "this was a deliberate,
non-recoverable rejection" without inspecting the message/cause chain.

Add NonRecoverableConversionException, a narrow MessageConversionException
subtype thrown only from the failOnJsonError path, and let it propagate
unwrapped through convertInputPublisherIfNecessary. This adds no new
dependency (MessageConversionException already lives in spring-messaging,
a non-optional dependency of this module) and changes no existing
behavior for any other exception type.

Known follow-up, intentionally out of scope here: a function declared as
Function<Message<T>, R> (explicit Message-wrapped type) never reaches
this path at all - convertInputMessageIfNecessary deliberately returns
the original, unconverted message whenever the target type is itself
Message<T> (to support legitimate no-conversion-needed cases like
KafkaNull), which silently masks the same class of failure. Functions
declared over the plain payload type (the common case, and the only
shape spring-cloud-function's own new PojoFunctionIntegrationTests
covers) are unaffected and fixed by this change.
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0c914ff0-f4f2-498e-8c05-6607ec265214

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

apoupard pushed a commit to komune-io/fixers-f2 that referenced this pull request Aug 5, 2026
…une-io/spring-cloud-function#6 is merged

Hypothetical/deliberately non-compiling: shows what F2's SimpleFunctionRegistry
vendoring would look like if upstream (via #6) already threw and rethrew
NonRecoverableConversionException natively. That PR only touches
SimpleFunctionRegistry - JsonMessageConverter.java, SmartCompositeMessageConverter.java,
and both exception-handler mappings stay exactly as they are, since none of
that is in scope for #6.

- Deletes F2's own NonRecoverableConversionException.java - would come from
  the (not-yet-real) upstream spring-cloud-function-context jar instead.
- SimpleFunctionRegistry.convertInputPublisherIfNecessary keeps the same
  catch clauses, but they're no longer KOMUNE-marked: this content would
  already be present in a fresh extract at the hypothetical merged tag, not
  something F2 is patching in anymore. The file is still vendored overall,
  for the unrelated typed List<T> collection deserialization patch elsewhere
  in it.

Does not compile against F2's actual current spring-cloud-function-context
dependency, on purpose - there is no real released version with #6 in it.
Not mergeable as-is; this commit exists to answer "what does the diff look
like if the upstream PR lands," not to ship.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant