shashank created CAMEL-25253:
--------------------------------
Summary: camel-dynamic-router - updating a subscription is ignored
when the priority is unchanged, and duplicates the subscription when the
priority changes
Key: CAMEL-25253
URL: https://issues.apache.org/jira/browse/CAMEL-25253
Project: Camel
Issue Type: Bug
Components: camel-core
Reporter: shashank
The control endpoint and the JMX operations can update an existing subscription
({{update=true}}: "update the subscription (true), or add a new one (false)").
{{DynamicRouterFilterService.addFilterForChannel}} checks that a filter with
the id exists and then only does
{code:java}
filters.add(filter); // ConcurrentSkipListSet ordered
by FILTER_COMPARATOR (priority, then id)
filterStatistics.add(filter.statistics());
{code}
The set compares filters by priority and id only, so:
* *same priority:* the new filter is "equal" to the existing one, {{add}}
returns false and the old filter stays. The update (new predicate, new
destination) is silently ignored, while the operation returns the subscription
id as if it had succeeded;
* *other priority:* both filters stay in the set. The subscription now routes
with its old and its new definition: in {{allMatch}} mode messages go to both
destinations (duplicates), in {{firstMatch}} mode the old definition can still
win; and {{getFilterById}} returns either.
In addition, {{filterExists}} is computed with
{{filterMap.get(channel).stream()}} as soon as any channel has filters, so a
subscription for a channel that has none yet fails with a
{{NullPointerException}}.
The existing unit test of the update passes only because its factory returns
the same mock for the old and the new filter, whose priority it changes in
place.
h3. Reproduction
With the default filter factory:
{code:java}
DynamicRouterFilterService service = new DynamicRouterFilterService();
service.initializeChannelFilters("test");
service.addFilterForChannel("sub", 1, PredicateBuilder.constant(true),
"mock:old", "test", false);
service.addFilterForChannel("sub", 1, PredicateBuilder.constant(true),
"mock:new", "test", true); // returns "sub"
service.getFilterById("sub", "test").endpoint();
// "mock:old"
service.addFilterForChannel("sub", 10, PredicateBuilder.constant(true),
"mock:new", "test", true);
service.getFiltersForChannel("test").size();
// 2
service.addFilterForChannel("other", 1, PredicateBuilder.constant(true),
"mock:other", "other", false); // NullPointerException
{code}
A unit test with these three cases fails on main.
h3. Proposed fix
Get (or create) the channel's filter set and statistics list first; on an
update remove the existing filter with that id, then add the new filter. The
statistics of the replaced filter stay in the list, as {{removeFilterById}}
keeps those of a removed filter on purpose ("the statistics still represent
actions that happened"), so after an update the list looks as after an
unsubscribe and a subscribe with the same id. With the fix the new test and the
camel-dynamic-router suite pass (137 unit tests, 21 integration tests).
Affected: 4.14.x, 4.18.x and main (same code since CAMEL-20302, 4.4).
Duplicate check (2026-10-01): JIRA text "dynamic-router" (54 issues) and
"dynamic router" with "update": CAMEL-21407 (unsubscribe), CAMEL-23735
(aggregation), CAMEL-24576 (control predicate), none about update. GitHub pull
requests: none open on the component.
_Filed with Claude Code on behalf of allthingssecurity._
--
This message was sent by Atlassian Jira
(v8.20.10#820010)