shashank created CAMEL-25250:
--------------------------------
Summary: camel-grpc - with the AGGREGATION or PROPAGATION consumer
strategy a failed exchange is answered with its message body as a successful
response instead of an error
Key: CAMEL-25250
URL: https://issues.apache.org/jira/browse/CAMEL-25250
Project: Camel
Issue Type: Bug
Components: camel-grpc
Reporter: shashank
For unary and server streaming calls, {{GrpcMethodHandler.handle}} ends the
call with {{Status.INTERNAL}} when the exchange failed (CAMEL-14893; the
description follows {{muteException}} since CAMEL-24478). Client streaming and
bidirectional streaming calls go through the consumer strategy observers
instead, and these never look at the exchange result:
* {{GrpcRequestAggregationStreamObserver.onCompleted}} (strategy
{{AGGREGATION}}) sends {{exchange.getMessage().getBody()}} with {{onNext}} and
then {{onCompleted}};
* {{GrpcRequestPropagationStreamObserver.onNext}} (strategy {{PROPAGATION}},
the default) sends the body of each exchange with {{onNext}}, and
{{onCompleted}} completes the call.
So a route that fails after it built the response (a {{to(...)}} after the
transformation, a validation, a database write) answers the client with a
successful response, and the error stays on the server (it only goes to the
consumer's exception handler). When the route fails before it built a response,
the body is the request (or the list of requests), which the response
marshaller cannot write; the call then fails with an unrelated error.
h3. Reproduction
{code:java}
from("grpc://localhost:0/org.apache.camel.component.grpc.PingPong?synchronous=true&consumerStrategy=AGGREGATION&muteException=false")
.bean(new GrpcMessageBuilder(), "buildAggregatedPongResponse") //
List<PingRequest> -> PongResponse
.throwException(CamelException.class, "GRPC Camel streaming exception
message");
{code}
and the same with {{consumerStrategy=PROPAGATION}} and a bean that maps one
{{PingRequest}} to a {{PongResponse}}. A client calling the client streaming
method {{pingAsyncSync}} gets the {{PongResponse}} ({{pong_name: "PINGPONG"}})
and a normal completion; a unary call to the same failing route gets
{{INTERNAL}}. A test with both strategies fails on main.
h3. Proposed fix
Both observers end the call with the same {{Status.INTERNAL}} error as the
unary path when the exchange failed (shared helper
{{GrpcMethodHandler.toStatusException}}, so {{muteException}} applies the same
way). The propagation observer remembers the failure: the call is over, so
later requests of the client are no longer routed and the response observer is
not called again ({{onCompleted}} and {{onError}} of the client are still
forwarded to the route when {{forwardOnCompleted}}/{{forwardOnError}} are set).
A route that must keep a bidirectional stream open handles the exception
({{onException(...).handled(true)}}). The {{DELEGATION}} strategy is unchanged:
the route controls the response observer there. With the fix the new test and
the whole camel-grpc suite pass. Clients now get errors where they got
responses, so the change gets an upgrade guide note.
Affected: 4.14.x, 4.18.x and main (same observers; 4.14.x has no
{{muteException}}, so a backport there uses the exception message as the unary
path does).
Duplicate check (2026-10-01): JIRA component camel-grpc (all 41 issues) and
text "grpc" with "aggregation"/"propagation"/"streaming" and "exception": only
CAMEL-14893 (unary calls) and CAMEL-24478. GitHub pull requests "grpc
exception", "grpc streaming": none about the streaming strategies.
_Filed with Claude Code on behalf of allthingssecurity._
--
This message was sent by Atlassian Jira
(v8.20.10#820010)