shashank created CAMEL-25288:
--------------------------------

             Summary: camel-dapr - the consumers close the configured or 
autowired Dapr clients when they stop, and the configuration consumer fails to 
start in a route with a NullPointerException
                 Key: CAMEL-25288
                 URL: https://issues.apache.org/jira/browse/CAMEL-25288
             Project: Camel
          Issue Type: Bug
          Components: camel-dapr
            Reporter: shashank


The {{client}}, {{previewClient}} and {{workflowClient}} options are autowired 
({{@Metadata(autowired = true)}}): a single {{DaprClient}} / 
{{DaprPreviewClient}} bean in the registry (for example the beans of the Dapr 
Spring Boot starter) is used by every dapr endpoint. The consumers close 
clients that they do not own:

h3. 1. DaprPubSubConsumer closes a configured or autowired preview client

{code:java}
protected void doStop() throws Exception {
    if (subscription != null) {
        subscription.close();
    }
    if (client != null) {
        client.close();   // also the configured / autowired previewClient
    }
    ...
{code}

After the route is stopped (or restarted), every other dapr endpoint using that 
client fails (the gRPC channel is shut down), and the route itself cannot 
subscribe again: a route restart creates a new consumer, which gets the same 
closed client from the configuration.

h3. 2. DaprConfigurationConsumer closes the client of its endpoint, and never 
ends its subscription

{code:java}
protected void doStop() throws Exception {
    if (client != null) {
        client.unsubscribeConfiguration(subscriptionId, configStore);   // a 
Mono that is never subscribed: does nothing
        client.close();                                                 // the 
endpoint's client
    }
    ...
{code}

The client is {{endpoint.getClient()}}: the configured / autowired 
{{DaprClient}} shared by all dapr endpoints, or the client that the endpoint 
created for all its producers and consumers. The {{Flux}} subscription is never 
disposed and the unsubscribe {{Mono}} is never subscribed, so the subscription 
is not ended at Dapr when the client is not closed. The subscription id was 
only recorded from a response without items.

h3. 3. DaprConfigurationConsumer cannot start in a route

The consumer takes {{endpoint.getClient()}} in its constructor, but the 
endpoint creates (or takes the configured) client in {{doStart}}, and a route 
creates its consumer before it starts the endpoint. In a route the consumer has 
no client and the route fails to start:

{noformat}
FailedToStartRoute Failed to start route: configuration because: Cannot invoke
"io.dapr.client.DaprClient.subscribeConfiguration(io.dapr.client.domain.SubscribeConfigurationRequest)"
 because "this.client" is null
{noformat}

The existing unit test builds the consumer with a mocked endpoint whose client 
is already set, so it did not show this.

h3. Reproduction (unit tests, no Dapr sidecar)

{{DaprConsumerSharedClientTest}}: a {{CamelTestSupport}} test with mocked 
{{DaprClient}}, {{DaprPreviewClient}} and {{DaprWorkflowClient}} beans in the 
registry (autowired), a {{dapr:pubSub}} route and a {{dapr:configuration}} 
route.
* Stop and start the pub/sub route: on main {{previewClient.close()}} is called 
({{NeverWantedButInvoked: daprPreviewClient.close()}}).
* Start the configuration route: on main it fails with the 
{{NullPointerException}} above. With only the client taken in {{doStart}}, 
stopping it does not end the subscription ({{expected: <true> but was: 
<false>}}), does not unsubscribe and closes the shared client.
* The existing {{DaprPubSubConsumerTest}} / {{DaprConfigurationConsumerTest}} 
asserted {{close()}} on the configured / endpoint client; they now assert that 
it is not closed (and that the configuration consumer unsubscribes with the 
subscription id).

h3. Proposed fix

Ownership as in the other components (for example the AWS components close only 
the clients that they created):
* {{DaprPubSubConsumer}} closes only a preview client that it created (none 
configured); then a restart creates a new one.
* {{DaprConfigurationConsumer}} takes the endpoint's client in {{doStart}}, 
records the subscription id from every response, and on stop disposes its 
subscription and unsubscribes (blocking up to 10 seconds, a failure is logged), 
without closing the client. Both are needed with Dapr Java SDK 1.18.1: its 
{{Flux}} does not cancel the gRPC stream when it is disposed, so the explicit 
{{unsubscribeConfiguration}} is what ends the subscription at the sidecar (the 
sidecar logs that this call is deprecated in favour of disconnecting).
* {{DaprEndpoint}} closes the {{DaprClient}}, {{DaprPreviewClient}} and 
{{DaprWorkflowClient}} that it created in {{doStart}} when it stops (as 
{{Sqs2Endpoint}} does), never the configured or autowired ones; it creates new 
ones when it is started again.

No upgrade note: a configured or autowired client was closed by Camel and now 
stays open for its owner (Spring or the application), as for the other 
components.

{{DaprEndpointClientOwnershipTest}} (2): the endpoint closes the clients it 
created (Mockito {{mockConstruction}} of {{DaprClientBuilder}} / 
{{DaprWorkflowClient}}; fails without the endpoint change: {{Wanted but not 
invoked: daprClient.close()}}) and never the configured ones, which it uses 
again after a restart.

With the fix the camel-dapr unit tests pass (56).

Affected: 4.14.x, 4.18.x and main (same code).

Duplicate check (2026-10-03): JIRA component camel-dapr / text "dapr" (9 
issues): CAMEL-25274 (ours, acknowledgement), CAMEL-23645 (open, move to the 
new Topic API), feature issues. GitHub pull requests 
"DaprConfigurationConsumer", "camel-dapr close client": none.

_Filed with Claude Code on behalf of allthingssecurity._




--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to