User manual

Apache Camel 4.x Upgrade Guide

This document is for helping you upgrade your Apache Camel application from Camel 4.x to 4.y. For example, if you are upgrading Camel 4.0 to 4.2, then you should follow the guides from both 4.0 to 4.1 and 4.1 to 4.2.

The Camel Upgrade Recipes project provides automated assistance for some common migration tasks. Note that manual migration is still required. See the documentation page for details.

Upgrading Camel 4.23 to 4.24

camel-observability-services - promoted to Stable

The camel-observability-services component has been promoted from Preview to Stable support level.

camel-ehcache - shared CacheManager and changed defaults

The following user-visible changes take effect in 4.24:

  • Shared CacheManager. Endpoints that have the same cacheName and no explicit cacheManager, cacheManagerConfiguration, or configurationUri now share one CacheManager per component instance. Previously each endpoint created its own unmanaged (UserManagedCache) cache.

    • The same name now means the same cache. ehcache:foo?action=PUT and ehcache:foo?action=GET now read and write the same entries.

    • All endpoints for a cache name must use compatible key and value types. Ehcache validates them exactly, so ehcache:foo and ehcache:foo?valueType=java.lang.String now fail at startup with a type-mismatch error.

    • The shared caches, and their entries, are closed when the last endpoint that uses the shared CacheManager stops (for example when the CamelContext stops).

  • Event delivery on default caches. A consumer on a cache without an explicit configuration now receives events. Previously, the UserManagedCache fallback lacked event executor services, so listeners were silently never called.

  • Null body rejected. PUT, PUT_IF_ABSENT and REPLACE without a body and without a CamelEhcacheValue header now fail with CamelExchangeException instead of silently storing the Message object.

  • User-supplied cacheManager caches persist. Caches created on demand inside a user-supplied cacheManager (e.g. ehcache:foo?cacheManager=#myManager) are no longer removed when Camel stops; the lifecycle of the caches follows the lifecycle of the supplied manager.

camel-flowable - xml inbound channels parse the event with the Camel XML settings

The flowable producer now parses the event of an inbound channel of type camel with deserializerType set to xml with the XML parser settings that Camel uses, which are also the settings of Flowable 8: external DTDs are not loaded, external general and parameter entities are not included, entity references are kept as entity reference nodes, and XInclude is not processed. Before, the Flowable 7 deserializer parsed the event with the JDK defaults. An event that relies on an external DTD or an external entity no longer gets their content. A channel that sets deserializerDelegateExpression keeps its own deserializer, which can parse such events.

camel-jpa - query parameters from CamelJpaParameters header are bound literally

In previous versions, string values supplied in the CamelJpaParameters header map were evaluated as Simple expressions by the JPA producer.

Starting in 4.24, parameter values provided via the CamelJpaParameters header are used as-is and bound literally as query parameters without expression evaluation. The endpoint URI option parameters continues to support Simple expression evaluation for route-configured parameters.

Routes that previously relied on evaluating Simple expressions within the CamelJpaParameters header should evaluate those expressions in the route before setting the header value.

camel-micrometer, camel-opentelemetry-metrics - tags and attributes from headers are used as given

The micrometer and opentelemetry-metrics producers now use the tags of the CamelMetricsTags header and the attributes of the CamelMetricsAttributes header as given. Before, their keys and values were evaluated as Simple expressions, in the same way as the tags and attributes URI options. The URI options are still evaluated per exchange, and a tag or attribute from the header still overrides one from the URI with the same key.

A route that relied on a Simple expression in a header tag or attribute should use the tags / attributes URI option instead (for example tags.customer=${header.customerId}), or compute the value in the route before setting the header.

camel-a2a - consumer context is separate from producer headers

An A2A consumer no longer populates producer control headers with the incoming operation and identity. Update consumer routes that read these headers:

Previous consumer header Replacement consumer header / exchange property

CamelA2AOperation

CamelA2ARequestOperation

CamelA2ATaskId

CamelA2ARequestTaskId

CamelA2AContextId

CamelA2ARequestContextId

To control a consumer’s SendMessage reply, replace writes to CamelA2ATaskState with CamelA2AReplyTaskState, and writes to CamelA2AResponseType with CamelA2AReplyType. The old names continue to control producers or describe their responses. Explicit producer operation headers still override the endpoint’s operation option.

Java code using A2AConstants must also migrate even though the existing constant names and values remain. For consumer metadata reads, replace OPERATION, TASK_ID and CONTEXT_ID with REQUEST_OPERATION, REQUEST_TASK_ID and REQUEST_CONTEXT_ID, respectively. For consumer reply controls, replace TASK_STATE and RESPONSE_TYPE with REPLY_TASK_STATE and REPLY_TYPE.

No Endpoint DSL header helper methods were removed. The new a2ARequestOperation(), a2ARequestTaskId() and a2ARequestContextId() methods provide the consumer metadata header names; a2AReplyTaskState() and a2AReplyType() provide the consumer reply control header names. Existing helpers retain their producer roles.

Downstream results no longer replace the outer task ID, context, state or reply type, and progress updates remain attached to the outer task. Consumer reply messages use the outer context and task identity. With dataFormat=POJO, forwarding the same incoming Message instance to another agent no longer carries the outer task/context IDs. A new message supplied by the route retains its IDs, including an equal copy made with Message.builder(incoming).build(). When copying or transforming the incoming message to start a new downstream conversation, clear inherited IDs with .taskId(null).contextId(null). Set explicit downstream IDs when continuing a downstream conversation.

camel-shiro - ShiroSecurityPolicy scopes SecurityManager to policy instance

The ShiroSecurityPolicy constructors that load configuration from an INI file or object no longer set the resulting SecurityManager as Shiro’s global security manager via SecurityUtils.setSecurityManager(…​). ShiroSecurityProcessor now creates and authenticates the caller identity (principal) using the route policy’s SecurityManager directly. Applications configuring multiple ShiroSecurityPolicy instances in the same JVM now have their realms evaluated independently per policy.

Code that accesses the ambient security context or calls SecurityUtils.getSecurityManager() outside the policy and previously relied on the policy constructor setting the global security manager will now receive UnavailableSecurityManagerException. Such applications should explicitly configure SecurityUtils.setSecurityManager(policy.getSecurityManager()) at startup.

Additionally: - A ShiroSecurityPolicy created with the no-arg constructor must be configured with setSecurityManager(…​). - When alwaysReauthenticate=true (the default), no caller identity stays bound to the processing thread after the policy check completes.

camel-ai-tool - required arguments with reserved names reject the call

Required tool arguments whose names start with camel or org.apache.camel. (case-insensitive) now reject the invocation before the tool route runs. Previously, these arguments could pass the required-argument check and then be discarded by the reserved-name filter, leaving the route without a required value. Rename these parameters and update their parameter.* or argSchema declarations.

Java

Breaking change: Camel 4.24 requires Java 21 as the minimum runtime version. Java 17 is no longer supported. Upgrade your JDK to 21 or later before upgrading to Camel 4.24.

camel-core - Package scanning of JARs no longer includes sibling packages

When scanning classes in a package (for example camel.main.base-package-scan=com.acme.routes, or PackageScanClassResolver.findImplementations), classes found in a JAR were also included when they belonged to a sibling package whose name merely started with the scanned package name, such as com.acme.routesupport or com.acme.routes2. Scanning a directory on the classpath never did this. Both now only include the scanned package and its sub packages, so a RoutesBuilder or @Configuration class in such a sibling package is no longer discovered when running from a JAR; list that package explicitly if it should be scanned.

camel-core - Model has a new getModelLock method

The org.apache.camel.model.Model interface (and so ModelCamelContext) has a new method getModelLock(), which returns the lock held while routes are added to or removed from the model. A custom implementation of Model must implement it (classes that extend DefaultCamelContext need no change). The Kamelet component now creates the routes of Kamelets holding this lock.

camel-core - Route.clearRouteModel() is deprecated

The Route.clearRouteModel() method is deprecated and is now a no-op. It will be removed in a later release.

This method dropped the references a runtime route held to its model (the route definition, the error handler factory and the error handlers) once the route was created at startup. The CamelContext already keeps the route definitions in its model, so clearing them on the route saved little memory at the cost of making Route.getRoute() and Route.getErrorHandlerFactory() return null for routes created at startup (but not for routes added after startup).

Applications that called Route.clearRouteModel() directly can remove the call. Implementations of the Route interface that override clearRouteModel() can remove the override.

camel-management - LastExchangeCompletedExchangeId is updated at most once per millisecond

The JMX attribute LastExchangeCompletedExchangeId, of the CamelContext, route and processor MBeans, is now updated at most once per millisecond, so that the JMX statistics no longer generate an exchange id for every exchange. When several exchanges complete within the same millisecond, the attribute has the id of the first one, not of the last one. Failed exchanges are not affected: LastExchangeFailureExchangeId is still updated for every failed exchange.

camel-core - a nested split no longer changes the processed items of the outer split

With errorThreshold or maxFailedRecords on a split that contains another split, the inner split changed the number of processed items of the outer split. It counted its own items when it had no thresholds, and hid the items of the outer split when it had them. SplitResult.getProcessedItems() now reports the items of that split only. A nested split with errorThreshold can now stop earlier than before, as the ratio is computed from the correct number.

camel-util - BoundedExecutorService: a task of the executor does not wait for a permit

With virtual threads, a task that runs in a thread pool bounded by maxQueueSize and submits more work to it, such as a parallel split, no longer waits for a permit when none is available. It runs the work itself at once, whatever the rejected policy. Before, it could wait for a permit it held itself, until keepAliveTime expired, or forever with Block. A split with a timeout could lose items that way. With Abort, a parallel split ran one item fewer at a time than maxQueueSize allows, and with maxQueueSize 1 it was rejected after keepAliveTime. A submitter outside the pool waits, and is rejected with Abort, as before.

camel-core - Routing Slip and Dynamic Router fire the sent event of a step before the next step

The ExchangeSentEvent of a step of a routingSlip or dynamicRouter is now fired when that step is done, before the next step is sent, as with recipientList. Before, when a step completed asynchronously (nearly every step, as the error handler of each step schedules its work), the sent events of the steps were fired in reverse order after the last step: the time taken of a step included all the steps after it, and the sent event of the first step carried the message and the failure after the last step.

Users of the sent events see the change: the time of a step is lower (such as in the exchange event notifier timers of camel-micrometer and camel-opentelemetry-metrics), the first step is no longer reported as failed because a later step failed, and event-based tracing (camel-jfr, and camel-telemetry together with its own fix for asynchronous steps) pairs the events of each step. There is no option to keep the old order.

camel-core - RedeliveryErrorHandler.defensiveCopyExchangeIfNeeded is deprecated

When redelivery is enabled, the error handler (DefaultErrorHandler, DeadLetterChannel and the transaction error handlers) keeps a copy of the message before each step of the route, so a redelivery starts again from the original message. It copied the whole exchange for that, at every step, also when the step succeeded, but a redelivery only ever restored the IN message from it. It now keeps a copy of the IN message only. Routes behave as before: exchange properties and variables were not restored on redelivery, and still are not.

The protected RedeliveryErrorHandler.defensiveCopyExchangeIfNeeded(Exchange) method is deprecated and no longer called. A custom error handler that overrides it to change what is redelivered must change the message before the redelivery instead, for example with onRedelivery.

camel-core - the documented default of skipBindingOnErrorCode is true

The skipBindingOnErrorCode option of the Rest DSL (on restConfiguration, rest, its verbs and restBinding) is now documented with the default value true, the default it has always had at runtime. Before, the model, the catalog, the XML and YAML DSL schemas said false. Routes behave as before.

Tools that read the default from the catalog or the schemas now see true.

camel-core - pollEnrich: the body is null after a timeout also for a message that creates its body on demand

When pollEnrich (or poll) times out and no aggregationStrategy is configured, the body is set to null, and with variableReceive the variable is null. For a message that creates its body on demand, the null did not stick: the next read created the body again. The message of a file consumer creates its body from its file, so after a timeout the body, or the variable of variableReceive, was the file of the incoming exchange instead of null. The messages of the JMS, sjms, mail, HTTP, XMPP, SMPP, SNMP and Spring WS consumers create their body on demand as well, so there it was the payload of the consumer again.

The body, or the variable, is now null after a timeout for these consumers too. A route that read the incoming file or payload from the body after a timed out pollEnrich must keep it before the pollEnrich instead, for example with variableReceive, which leaves the body as it was. To keep the body empty, the message is replaced by a plain message with the same message id, headers and attachments, so after a timeout it is no longer of the message type of the consumer (such as GenericFileMessage or JmsMessage).

camel-core - doFinally runs all its steps after a called route handled a failure

When a doTry calls a route (such as with direct) whose error handler handles a failure (a dead letter channel or a handled onException), the doFinally block now runs all its steps. Before, in all Camel 4.x releases, it ran only its first step and skipped the rest, so a second or later step of the block (such as a to, a recipient list or a routing slip) did not send its message. A route called from such a doFinally that fails is now handled (or continued) by its own error handler, as anywhere else; before, its error handler left the failure alone and the exchange failed with that exception. As before, the route does not continue after the doTry once a called route handled the failure.

camel-core - an AsyncProcessor that throws on the first attempt goes through the error handler

When the error handler of a step can redeliver, has an onException or an onPrepareFailure processor, or is a dead letter channel, and the processor of the step (such as a custom AsyncProcessor used with process(…​); an endpoint in to(…​) is not affected) throws from process(exchange, callback) on the first attempt instead of setting the exception on the exchange, the error handler now handles that failure as any other failure of the step: it redelivers it, and when the redeliveries are exhausted, the matching onException or the dead letter channel handles it. This is what it already did when the processor set the same exception on the exchange, or when a plain Processor threw it. Before, the exchange failed at once with the thrown exception. When such a processor calls its callback and then throws, the exception is now only logged at WARN; when it handed its callback to another thread before it threw, and that thread calls it after the error handler took over the failure, or when it calls its callback twice, that call is now ignored and logged at WARN. Before, the exchange was continued a second time in these cases.

To not redeliver such a failure, add an onException for its exception type with maximumRedeliveries(0); as for the same failure set on the exchange, the dead letter channel and the onException still handle it. There is no option to let such a failure skip the dead letter channel or onException as before; a plain Processor that throws gets the same handling. Better, fix the processor to set the exception on the exchange and call the callback, as an AsyncProcessor must.

camel-core - ThrottlingExceptionRoutePolicy counts the failures within the failure window

The circuit of ThrottlingExceptionRoutePolicy now opens when failureThreshold failures occurred within the last failureWindow milliseconds, as documented. Before, it counted every failure since the circuit last closed (or since the route started), and only the last failure had to be within the window, so failures spread over hours opened the circuit on a single new failure. Routes whose failures are spread out therefore open their circuit less often now; to open it sooner, lower failureThreshold or raise failureWindow.

In the half open state, a successful exchange now closes the circuit, and a failed exchange opens it again. Before, a successful exchange opened the circuit again as long as the failures that opened it were within failureWindow; with the defaults (a window of 60 seconds, a half open check after 30 seconds) a success in the first 30 seconds after the half open check therefore kept the circuit open. A halfOpenHandler decides as before.

camel-core - the failover load balancer does not fail over on a suppressed exception

The failover load balancer with a list of exceptions now matches only the exception the attempt fails with and its causes, as onException and doCatch do. Before, it also matched the suppressed exceptions of that exception. Since 4.23, an exception thrown in a doFinally block is added as a suppressed exception to the exception from the doTry block, so a doFinally that threw one of the exceptions to fail over on made the load balancer fail over, and the exception from the doTry block was lost when the next processor succeeded. Now the exchange fails with that exception. The same applies to a failing onCompletion that runs before the consumer (modeBeforeConsumer) and a failing saga compensation, whose exceptions are also added as suppressed exceptions.

The load balancer also no longer fails over when one of the exceptions to fail over on is only the suppressed exception of the exception the attempt fails with in these cases:

  • An onException block (or another error handler) fails while it handles the exception: the exchange fails with the new exception, and the original exception is added to it as a suppressed exception (by the FatalFallbackErrorHandler). Unlike the doFinally case, this is not a 4.23 regression: it changes behaviour that dates back to Camel 2.16, intentionally, so that the load balancer matches exceptions as onException and doCatch do.

  • The error handler redelivers the attempt (with inheritErrorHandler enabled, or the error handler of the route the attempt calls) and the last redelivery fails with a different exception than the first attempt: the exchange fails with the exception of the last redelivery, and the exception of the first attempt is added to it as a suppressed exception. For example, with only java.io.IOException in the list, when the first attempt fails with a java.net.ConnectException and the redelivery with an HttpOperationFailedException, the load balancer used to fail over and now does not.

  • The onWhen predicate of a doCatch throws an exception: the exchange fails with the exception of the predicate, and the original exception is added to it as a suppressed exception.

  • A processor or component adds a suppressed exception itself (such as Java’s try-with-resources when closing a resource fails).

To fail over in these cases, add the type of the exception the attempt fails with to the list.

camel-core - ThrottlingExceptionRoutePolicy does not count a failure on a suppressed exception

The exceptions of ThrottlingExceptionRoutePolicy (its exceptions option, or the list given to its constructor) are now matched against the exception the exchange fails with and its causes only, as onException, doCatch and the failover load balancer do. Before, they also matched the suppressed exceptions of that exception, so such a failure counted towards opening the circuit. Since 4.23, an exception thrown in a doFinally block is added as a suppressed exception to the exception from the doTry block, so a doFinally that threw one of the listed exceptions counted the failure, although the exception from the doTry block was not listed. The same applies, since 4.23, when the onWhen predicate of a doCatch throws an exception: the exchange fails with the exception of the predicate, and the original exception is added to it as a suppressed exception.

A failure also no longer counts when one of the listed exceptions is only the suppressed exception of the exception the exchange fails with in other cases, for example:

  • An onException block (or another error handler) fails while it handles the exception: the exchange fails with the new exception, and the original exception is added to it as a suppressed exception (by the FatalFallbackErrorHandler).

  • The error handler redelivers and the last redelivery fails with a different exception than the first attempt: the exchange fails with the exception of the last redelivery, and the exception of the first attempt is added to it as a suppressed exception.

  • An onCompletion that runs before the consumer (modeBeforeConsumer) or a saga compensation fails, or a processor or component adds a suppressed exception itself (such as Java’s try-with-resources when closing a resource fails).

Unlike the doFinally and onWhen cases, these are not 4.23 regressions; they change older behaviour intentionally, so that the policy matches exceptions as onException and doCatch do.

To count these failures, add the type of the exception the exchange fails with to exceptions, or leave exceptions empty to count every failure.

camel-core - JsonObject and JsonArray convert to their JSON text

The camel-util-json types org.apache.camel.util.json.JsonObject and JsonArray (given by convertBodyTo: JsonObject, graphql, kubernetes-custom-resources and typesafe-ai) now have type converters to String, byte[] and InputStream that give their JSON text, using the charset of the exchange for the bytes. Before, a String was the toString of the map or list ({sku=CAMEL-MUG, qty=3}, which is not JSON), and there was no conversion to byte[] or InputStream, so such a body sent to http or a file failed. A JsonObject or JsonArray that holds a value that is not JSON, such as a POJO, converts as before.

camel-core - TypeConversionException gives the value that failed to convert

When a type converter fails during the conversion of a message, the TypeConversionException now gives the value that failed to convert from getValue(), and its type from getFromType() and in the message (Error during type conversion from type: …​). Before, they gave the value as a String prepared for logging, so the message always said from type: java.lang.String. The message does not include the value itself.

The conversion of a collection or array to an array whose component type does not fit an element, such as a list of strings to int[] or byte[], now gives no conversion (null, or NoTypeConversionAvailableException for a mandatory conversion), and other type converters can try. Before, it failed with a TypeConversionException due to IllegalArgumentException: argument type mismatch or an ArrayStoreException.

camel-core - a route called from a doCatch handles its failure after the doTry called a route that failed

When the doTry block calls a route (such as with direct) that fails without handling the failure (no onException handles it, and no dead letter channel), the error handler of a route called from the doCatch block now runs as it does anywhere else, as when the doTry failed in the same route: it redelivers, runs its onException (also one that does not handle the failure) and its dead letter channel, and logs the exhausted failure. Before, it gave the failure straight back, and the exchange failed with that route’s exception after one attempt. When that route handles its failure, the rest of the doCatch block and the steps after the doTry do not run, as for any failure that a called route handles; when it continues its failure, they run. When the doCatch block does not call a failing route, the route continues after the doTry as before.

The exchange no longer keeps the marks of the caught failure, which also changes the following:

  • the same applies to a later doTry in the same route, and to a doTry inside an onCompletion;

  • the same applies to a route called later from a split, multicast, recipientList, enrich or wireTap (their copies of the exchange carried the mark that the caught failure was not handled);

  • a doTry and doCatch inside an onException(…​).handled(true), whose doTry calls such a route, no longer makes the exchange fail with the exception the doCatch caught;

  • with bridgeErrorHandler=true on the consumer, a failure of such an exchange that is not handled (a doCatch that fails, or a later failure in the route) now goes to the route’s error handler, as an exchange with the body Error processing exchange, instead of only being logged;

  • as when the doTry failed in the same route, the error registry (camel get error) now also lists the failure that such a doCatch caught, and the JMX failuresHandled counters of the steps inside the doCatch block no longer count it.

To keep the exchange failing after one attempt as before, give the route called from the doCatch no error handler (noErrorHandler()); this applies to every caller of that route.

camel-core - Simple: more predicates inside ${ } are read as predicates

A predicate written inside the braces (CAMEL-24921, 4.23) is now also read as one when it uses the operators equals, !equals, !startsWith or !endsWith, and when its left side is a function with a quoted argument, such as ${substringAfter('ID-') == '42'}. Before, the whole text was read as one function: ${header.type equals 'gold'} answered null (a predicate that never matches) and ${uppercase('a') == 'B'} answered A (a predicate that always matches). The condition of a ternary inside the braces accepts equals and !equals as well: ${header.type equals 'gold' ? 'a' : 'b'} was refused. The same predicates written outside the braces, such as ${header.type} equals 'gold', are unchanged.

camel-core - Simple: the elvis operator inside the braces

The elvis operator can now be written inside the braces: ${header.limit ?: 10} means ${header.limit} ?: 10. In an expression this used to fail with ${ cannot accept ?:. In a predicate it used to be read as one function whose name took in the rest of the text, such as a header named limit ?: 10, which is not set: so ${header.limit ?: 10} == 10 was false, and is true now. Without a space on both sides of ?: nothing changes: ${header.limit ?:10} still reads a header named limit ?:10. The default value of a property placeholder, ${properties:key:a ?: b}, is not affected.

camel-core - Simple: iif and val return a quoted literal without its quotes

In the Simple language, ${iif(${header.foo} > 0,'positive','negative')} returns positive again, as it did before Camel 4.10.7 and 4.13.0 (CAMEL-22204, which made it return 'positive') and as the ternary operator ${header.foo > 0 ? 'positive' : 'negative'} does. ${val('Hello')} returns Hello; it returned 'Hello' since val was added in Camel 4.18.1. A route that removed the quotes itself, or that relied on them, must be adjusted. Values without quotes, functions such as ${body}, and the quotes inside the predicate of iif are not changed.

camel-core - Simple: a space after a comma is not part of a function argument

The arguments of the replace, substring, substringBefore, substringAfter, substringBetween and contains functions no longer include the white space around them, as for the collection functions since 4.23 (CAMEL-24969): ${replace('o', '0')} replaces o with 0 (it replaced it with 0), and the documented ${replace(" , ')} replaces double quotes with single quotes. The spaces inside quotes are kept, and an argument of only white space is kept as it is, so ${replace( ,-)} still replaces a space. A route that relied on a space next to the comma, such as ${replace(a,b )} for the text b , quotes it instead: ${replace(a,'b ')}. Only the quotes around a whole argument are removed: a quote inside an argument, as in a'b'c, is now kept (it was removed).

The prefix and the expression of the join function no longer include the white space around them either: for the body [A, B, C], ${join(&, id=)} gives id=A&id=B&id=C (it gave id=A& id=B& id=C). An unquoted separator is kept as written, as it can be white space on purpose: ${join(; )} still joins with ; ; a quoted separator is the text inside its quotes (${join('&' ,id=)} joins with &, it joined with & ; put the space inside the quotes to keep it: ${join('& ',id=)}). A prefix with a space at its start or end is quoted instead: ${join(&,' id=')}. As for the other functions, only the quotes around a whole argument of join are removed: a quote inside an argument, as in ${join(&,a'b'c)}, is now kept (it was removed), so an expression such as ${join(&,id=,${header.x.split(',')})} now works.

camel-core - Simple: an empty function argument is kept, and a comma in a nested function does not split

In the arguments of replace, substring, substringBefore, substringAfter, substringBetween, contains, join, split, sort, setHeader, setVariable, forEach, filter, listAdd, listRemove, mapAdd and mapRemove, an argument left out now stays in its place instead of letting the next argument take it: ${replace(b,,${header.s})} replaces b with nothing in the header (it replaced b with the text ${header.s}), and ${join(,id=)} joins with a comma and the prefix id= (it joined with id=). Where the function has no default for it, an argument left out is refused, so a stray comma that was ignored is now an error, such as ${contains(xyz,)}, ${substring(,2)} or ${replace(a,b,${body},)}: remove the comma. ${sort(true,)} now sorts the text true, and ${join( )} joins with a space (it joined with a comma). A comma inside a nested function, such as ${header.s.substring(1,3)}, no longer separates two arguments; a parenthesis outside a nested function is text, as before (${substringBetween(${body},(,))}). In the values of distinct, reverse, shuffle, list and map, a comma after an unquoted ( or { no longer separates two values: quote a value with a lone parenthesis. A lone quote at the end of an argument is now kept: ${replace(b,x')} replaces with x'.

camel-core - a route called from a doTry uses the error handler of its EIPs

A route that a doTry calls from its steps, or from its doCatch or doFinally blocks (with direct, seda, enrich and so on), now uses the error handler per output of its routing slip, dynamic router, multicast and recipient list EIPs, as it does when it is called from outside a doTry. Before, these EIPs left out that error handler while the caller was in its doTry: a routing slip redelivered the whole slip (so an endpoint that had already succeeded got the message again), a multicast or recipient list did not redeliver the failed output, and a dynamic router that counts its calls could lose the failure. The same applies to a route called from the steps of a circuit breaker. A redelivery now starts at the failed output, so such a route can make more delivery attempts than before.

When the called route handles failures (a dead letter channel, or an onException with handled(true)), a failed output of its multicast is now handled there (after the redeliveries of that error handler), and no longer reaches the doCatch of the caller.

To turn off the error handler per output of such an EIP again (no redelivery of its outputs, and the failure goes to the caller), put the EIP in a doTry with only a doFinally in the called route, or turn off the error handler of the called route with errorHandler(noErrorHandler()), which turns it off for the other steps of that route too. Neither option brings back the redelivery of a whole routing slip or dynamic router.

camel-core - an AsyncProcessor that throws in a step without an error handler fails the exchange

A step that has no error handler of its own (the steps of doTry, doCatch, doFinally and onException, also when nested in another EIP inside them, the outputs of a multicast, the steps of a circuit breaker, and a step with inheritErrorHandler(false)) now handles a processor that throws from process(exchange, callback) instead of setting the exception on the exchange as if it had set it: the exchange fails with that exception, so the doCatch catches it, and the output of a multicast is redelivered by the error handler of the route as for any other failure. Before, the exchange hung.

When such a processor handed its callback to another thread and then throws, the exchange now fails with the thrown exception unless that thread calls the callback first, and a later call is ignored and logged at WARN; before, the exception was dropped and that callback continued the exchange as a success. A second call of the callback is ignored and logged at WARN; before, the exchange was continued a second time. There is no option for the old behaviour; fix the processor to set the exception on the exchange and call the callback, as an AsyncProcessor must.

camel-core - variableReceive into a route or group variable stores the header variables in that route or group

With variableReceive (toV, enrich, pollEnrich, fromV and so on) into a route variable or a group variable, the received headers are now stored in the route (group) of the variable. Before, they were stored under a route (group) with the id header, so they could only be read with an internal name, and were not removed with the route.

Variable Header variable before Header variable now

route:resp (in route rs)

route:header:rs:resp.status

route:header:resp.status or route:rs:header:resp.status

group:myGroup:resp

group:header:myGroup:resp.status

group:myGroup:header:resp.status

Inside a route, route:header:resp.status now means the header variable of the route variable resp of the current route, not the variable resp.status of a route with the id header.

camel-util - FilterIterator.remove after hasNext throws IllegalStateException

FilterIterator.hasNext() looks ahead in the iterator it wraps, so a remove() after next() and then hasNext() removed the element after the one next() returned, and a remove() before any next() removed an element as well. remove() now throws IllegalStateException unless it directly follows next(). Code that called hasNext() between next() and remove() must call remove() before hasNext().

The same applies to a remove() after a next() that failed after it looked at further elements: a NoSuchElementException because the filter rejected all the elements that were left (or they were null), or an exception thrown by the filter or by the iterator it wraps. Such a remove() could remove an element that next() never returned; it now throws IllegalStateException and removes nothing. A next() that fails at the end of the wrapped iterator, without looking at any element, still leaves the element returned before it removable.

camel-util - CaseInsensitiveMap merges keys that differ only in the case of a supplementary letter

CaseInsensitiveMap, which holds the message headers, now treats two keys that differ only in the case of a letter outside the Basic Multilingual Plane (such as U+10400 and U+10428) as the same key, as String.equalsIgnoreCase does; before, they were usually kept as two entries. Keys without such letters are not affected.

camel-core - ThrottlingExceptionRoutePolicy runs the half open check on a thread pool of Camel

The half open check of ThrottlingExceptionRoutePolicy now runs on a thread pool created through the ExecutorServiceManager (one thread, named ThrottlingExceptionRoutePolicy and managed like the other thread pools of Camel), instead of a new java.util.Timer thread each time the circuit opens. The thread is created when the circuit opens the first time, and it ends when the last route of the policy is removed or the policy is stopped.

halfOpenAfter must now be greater than 0: adding a route with a policy whose halfOpenAfter is 0 or less fails, and setHalfOpenAfter (also over JMX) rejects such a value with an IllegalArgumentException. With 0, a check that keeps the circuit open (keepOpen, or a halfOpenHandler that is not ready) scheduled the next check without delay, a busy loop. To half open the circuit almost at once, as 0 did, use a small value such as 1.

An exception thrown by the isReadyToBeClosed method of a halfOpenHandler is now logged (by the exception handler of the policy), and the circuit is kept open and checked again after halfOpenAfter; before, the timer thread died and the circuit stayed open.

camel-core - Split EIP keeps the original message when maxFailedRecords or errorThreshold stops it

When the Split EIP stops because maxFailedRecords or errorThreshold is reached, the exchange now keeps its original input message, headers, exchange properties and variables, and only gets the exception, as with stopOnException. Before, the message, headers, exchange properties and variables of the split item that reached the threshold, as the steps inside the split had changed them, were copied onto the exchange.

So a doCatch around the split, an onException, an onCompletion, a dead letter channel or the caller of the route now receive the input that was split, instead of that one split item. A route that needs to know which items failed can read the CamelSplitResult exchange property, set as before: SplitResult.getFailures() has the index and the exception of the failed items.

camel-core - shareUnitOfWork: a part sent to seda no longer takes the on completions of the parent exchange

Since Camel 4.14 the parts of a Split, Multicast or Recipient List with shareUnitOfWork use the unit of work of the parent exchange. A part that sent to an asynchronous endpoint without waiting for a reply (such as seda with an InOnly exchange) handed over all the on completions of that unit of work to its copy, including the commit or rollback of the consumer of the parent exchange (such as moving the file, or deleting the SQS message). They then ran when that copy was done, with the outcome of that copy: a file was moved to its done folder although a later part failed the split, or a message was rolled back although the split succeeded.

Now the on completions of the parent exchange stay with the parent exchange, and run when it is done, with its outcome (as the Camel 4.14 upgrade guide describes). Such a part still hands over the on completions it added to its own exchange (this is unchanged from earlier versions), so a resource the part itself opened, such as a file it polled, is still released when its copy is done. On completions added to the unit of work directly, such as by a route onCompletion that the part reaches, now stay with the parent exchange too (before, they went with the seda copy): they are done with the shared unit of work when the split is done, as for a part that does not send to seda.

As the consumer now commits when the split is done, it can commit before the seda copies have been processed. A seda copy that still reads from the input after that must get the content first. This mainly hits Multicast and Recipient List, where each part carries the whole message body (such as the file), while the parts of a Split usually carry content that was already read. For example, when the file consumer moves or deletes the file, convert the body of a file that was not loaded yet with convertBodyTo(String.class) before sending to seda. Or wait for the copy with waitForTaskToComplete=Always on the seda endpoint, which is also what to use when the message must not be committed (moved, deleted, acknowledged) before the asynchronous processing is done: a failure of the seda route then fails the part, and so the split. The previous behaviour, where the consumer commits with the outcome of one seda copy, cannot be configured.

camel-core - Tokenize: the token is literal text unless regex is true

The token of the Tokenize language is now matched as literal text, as the regex option (default false) describes. Before, the token was always compiled as a regular expression, so a delimiter such as |, ., $ or * split every character, did not split, or failed with a PatternSyntaxException. The end token of the pair mode (endToken) is also literal text now.

A route that relies on a regular expression as token, such as \s+ or (\W+)\s*, must now set regex=true:

.split().tokenize("\\s+", true)

In the XML and YAML DSL, a token with the escapes \n, \r or \t (for example \r\n) is turned into the new line, carriage return and tab characters, so it works as before. Before, only a leading \n was turned into a new line.

camel-cyberark-vault - credentials are checked at endpoint start, and a rejected token is retried only once

A cyberark-vault endpoint now fails to start when it has no usable credentials, with a message that names the options to set: the authToken option, the username and apiKey options, or the username and password options, unless the endpoint has a conjurClient. Before, an endpoint with an apiKey and no username started, and its first message failed with a NullPointerException.

When CyberArk Conjur answers HTTP 401, the endpoint and the CyberArk Conjur Vault Property Function authenticate again and retry the call once, and only when they have an apiKey or a password to authenticate with. With only an authToken, the call now fails with the 401 response, instead of retrying with the same token until a StackOverflowError. Configure the username with an apiKey or a password, instead of an authToken, to let the component get a new token when the current one expires: when the authToken is set, the component uses only that token.

camel-main - a failure to start is logged

A Camel Main application that fails to start now logs why, at ERROR: Error starting Camel: …​ with the stack trace, as camel run already did. Before, the failure left main() and only the JVM printed it, on stderr (Exception in thread "main" …​), so the log file of the application did not say why it stopped. The JVM no longer prints that same failure a second time. When the application logs nothing at ERROR (such as without a logging implementation), the JVM prints it as before.

camel-report-maven-plugin:validate / configuration-validate

The catalog now accepts all camel. groups defined in camel-main-configuration-metadata.json (previously only 7 of the 36 groups were recognized, besides camel.component., camel.dataformat. and camel.language.). As a result, camel-report-maven-plugin:validate and the camel-jbang MCP camel_configuration_validate tool now validate 29 additional camel. groups (including camel.server., camel.management., camel.vault., camel.ssl.*, and others).

If your project uses Spring Boot and has options such as camel.metrics.uriTagEnabled (from a Camel starter) that are not in the camel-main schema, camel-report-maven-plugin:validate may report them as "Unknown option", which fails the build when failOnError=true. The camel_configuration_validate MCP tool with runtime=spring-boot reports such keys as checked=false instead.

Group names are now matched with dash- and case-insensitive normalisation, matching how camel-main binds keys at runtime. For example, camel.error-registry.enabled and camel.ai-observability.enabled are now accepted alongside their camel-case equivalents.

If your project uses camel-report-maven-plugin:validate with failOnError=true, a key that was silently ignored before (e.g. a misspelled option under one of the newly accepted groups) may now cause the build to fail. Review the validation output and correct any misspelled keys.

The camel_configuration_validate MCP tool response format has changed:

  • PropertyValidationInfo.accepted is renamed to checked.

  • ConfigurationValidateSummary gains a new unchecked integer field counting lines whose namespace is not recognized as a Camel property (e.g. server.port=8080 or quarkus.* keys). Before, such lines were returned with accepted=false and valid=true; they are now checked=false and are left out of summary.valid and summary.errors.

If you call camel_configuration_validate programmatically and read the JSON response, update field references from accepted to checked and handle the new summary.unchecked field.

camel-semantic - expert artifacts

TypeSafe AI Semantic applications must add org.apache.camel:camel-semantic-typesafe-ai. The camel-typesafe-ai component and TypeSafe AI language remain independently usable and no longer bring in camel-semantic. The new expert artifact includes both dependencies transitively. Existing bean declarations and imports for org.apache.camel.component.typesafeai.TypeSafeAiSemanticAdapter remain valid.

Spring Boot applications using this expert must keep camel-typesafe-ai-starter and add org.apache.camel:camel-semantic-typesafe-ai at the matching Camel version. The existing starter continues configuring the component and TypeSafe AI language; register the Semantic expert explicitly as a named Spring bean. Applications using only the component or TypeSafe AI language need no new dependency.

Replace org.apache.camel:camel-wolf-defender with org.apache.camel:camel-semantic-wolf-defender. The Wolf Defender Java packages, classes, model provisioning and configuration properties are unchanged. Its documentation page is now semantic-wolf-defender.adoc.

Update Maven dependencies and Camel JBang --dep options to the new expert artifact names. Each Semantic evaluation still selects an explicit named registry expert. Provider identities and operation names remain typesafe-ai and wolf-defender; only the reported adapter artifact changes.

TypeSafe AI now rejects explicit media parts nested in structured input, instructions or criteria, which previously could be serialized and sent to the provider. Text and JSON-compatible Map/List state remain supported. Binary objects and image, audio or video parts are rejected before HTTP; plain text and ordinary application field names do not imply media. Records such as {type: image, url: …​, sku: 42} and MIME metadata with a URL alone remain application state. Direct Semantic adapter calls and batches use the same validation. Wolf Defender remains text-only with its existing limits.

camel-semantic - explicit experts

Every evaluation in the preview semantic API now requires an explicit expert registry bean name. Move block-level expert values onto each evaluation in YAML and XML; in Java, move .expert(…​) from SemanticEvaluationsBuilder to each SemanticEvaluationBuilder.

The camel.language.semantic.default-expert and camel.language.semantic.adapter options, SemanticLanguage.setDefaultExpert / setAdapter, automatic selection of a sole expert, and META-INF/services/org/apache/camel/semantic-adapter discovery are removed. Register an expert bean explicitly, including its initialization and destruction methods where needed, and reference it from each evaluation. Expert references accept property placeholders, but do not fall back to class loading. The default state selector is unchanged.

The semantic metadata response no longer includes defaultExpert, defaultError or defaultErrorCode. Operation inspection and completion require an explicit expert reference.

camel-semantic - evaluation-only auditing

The preview semantic audit API no longer records route actions. SemanticAuditDecision, SemanticAudit.get(context).decision(…​) and SemanticObserver.decision(…​) have been removed. Remove the corresponding decision beans and processor calls from routes. Evaluation auditing is automatic under the existing audit configuration; normal application routing remains unchanged. Previously persisted version-1 decision records remain readable and exportable as legacy data. Semantic audit browsing no longer accepts route-action, namespace or application-correlation filters. The console’s target filter is also removed. Newly captured evaluation records use definition instead of target; update consumers and queries to read definition. Previously persisted records retain their original fields. Event-ID detail responses no longer expand a decision’s evidence references automatically; use caseEventId for a bounded associated case or eventIds for explicit bulk retrieval. The original persisted record remains available in record. Use automatic evaluation provenance, including entry/executing route and Camel breadcrumb identifiers. The internal CamelSemanticAuditReferences exchange property is no longer populated. In audit health responses, capturing replaces decisionsEnabled; it indicates that some expert enables evaluation capture. For external readers, retained is unknown instead of a count from the unrelated memory store.

SemanticAuditReader.getAll no longer imposes a 100-ID evidence limit. Callers must bound requests and responses; backend implementations may split retrieval into their own batches. The 100-reference limit on legacy persisted evidence records remains unchanged.

Vert.x 5

Camel now uses Vert.x 5. This affects camel-vertx, camel-vertx-http, camel-vertx-websocket, camel-platform-http-vertx, camel-platform-http-main, camel-platform-http-jolokia, camel-knative-http and camel-oauth. Application code that uses the Vert.x API directly, for example to create a custom Vertx instance, a custom Router handler, or an authentication provider for platform-http, must be migrated as described in the Vert.x 5 migration guide. Notably, the callback variants of asynchronous methods are removed in favour of the methods returning a Future.

A metrics or tracing factory can no longer be set on the VertxOptions given to a component (the MetricsOptions.setFactory and TracingOptions.setFactory methods are removed). Either create the Vertx instance with Vertx.builder() and set it on the component (or bind it to the registry), or register the factory as a io.vertx.core.spi.VertxServiceProvider service.

VertxPlatformHttpRouter.mountSubRouter is removed, as it is no longer part of the Vert.x Router API. Use router.route(mountPoint + "*").subRouter(subRouter) instead.

When the connection of a WebSocket is lost without a close frame, the camel-vertx-websocket consumer no longer passes the resulting exception to the consumer exception handler, and no longer routes it as an ERROR event. It is handled as a WebSocket closed with a close frame: a consumer with fireWebSocketConnectionEvents=true routes a CLOSE event, and a consumer with consumeAsClient=true reconnects. With the default fireWebSocketConnectionEvents=false, the lost connection of a peer is therefore no longer logged as a warning.

When the WebSocket of a consumer with consumeAsClient=true reports any other error, the consumer exception handler now receives that exception. Previously it received the cause of that exception, which could be null.

When the WebSocket that the camel-vertx-websocket producer sends to (a server not managed by Camel) reports an error other than a lost connection, the exchange now fails with that exception. Previously it failed with the cause of that exception, which could be null.

camel-arangodb still uses Vert.x 4, as the ArangoDB Java driver does not support Vert.x 5 yet (see arangodb-java-driver#625). An application that uses camel-arangodb together with one of the components above resolves a single version of the io.vertx artifacts, so either the ArangoDB driver or the other components fail at runtime. The workaround suggested by the driver maintainers is to replace com.arangodb:arangodb-java-driver with com.arangodb:arangodb-java-driver-shaded, which embeds its own relocated copy of Vert.x. The vertx option of camel-arangodb cannot be used in that case.

camel-kubernetes - Kubernetes client 8

camel-kubernetes now uses the fabric8 Kubernetes client 8.0, which brings these changes for applications:

  • The client uses its Vert.x 5 HTTP client (io.fabric8:kubernetes-httpclient-vertx-5) instead of the Vert.x 4 one (io.fabric8:kubernetes-httpclient-vertx). An application that excludes the default HTTP client, for example to use OkHttp or the JDK HTTP client, must now exclude kubernetes-httpclient-vertx-5.

  • The client and its model use Jackson 3 (tools.jackson). Jackson 2 does not apply the serializers of the model, so Kubernetes model objects serialized with Jackson 2 come out in a different shape. For example, a targetPort of 8080 is written as {"value":8080,"intVal":8080}, and a CPU limit of 500m as {"amount":"500","format":"m"}. This affects routes that marshal the result of a camel-kubernetes producer with the camel-jackson data format (marshal().json()), and application code that uses its own Jackson 2 ObjectMapper. Replace the camel-jackson dependency with camel-jackson3, which provides the same jackson data format on Jackson 3 (see JSON Jackson 3), so the route does not change. In Java code, use Jackson 3 or io.fabric8.kubernetes.client.utils.Serialization.

  • Jackson 3 uses the Jackson 2 com.fasterxml.jackson.core:jackson-annotations artifact, and needs version 2.21 or later. An application that pins Jackson 2 to 2.20 or older, for example through an older Spring Boot BOM, fails on the first use of the client with NoClassDefFoundError: com/fasterxml/jackson/annotation/JsonSerializeAs. Use jackson-annotations 2.21 or later; Camel uses 2.22.

See the Kubernetes client 8 migration guide for the other changes.

camel-kubernetes - the namespace option, custom resources and scale

  • The producers of the namespaced kubernetes-* components (config-maps, cronjob, deployments, events, hpa, job, persistent-volumes-claims, pods, replication-controllers, resources-quota, secrets, service-accounts and services) use the namespace endpoint option when the CamelKubernetesNamespaceName header is not set; the header still wins. Before, these producers ignored the option: the get, create, update and delete operations failed without the header, and the list operations listed all namespaces. A route that sets the namespace option on a producer endpoint and lists without the header now lists that namespace only.

  • The kubernetes-custom-resources producer uses the crdName, crdGroup, crdScope, crdVersion and crdPlural endpoint options. Before, it checked them but then built the custom resource definition from the headers only.

  • createCustomResource and updateCustomResource fail the exchange with the KubernetesClientException of a 409 Conflict (the instance already exists, or the update is based on an old resource version). Before, the conflict was logged at INFO and the body was an empty GenericKubernetesResource.

  • scaleDeployment and scaleReplicationController return the replicas of the spec of the scaled resource, the number that was asked for. Before, they returned the replicas of its status, which lags behind until the pods are started or stopped, and failed with a NullPointerException when the resource had no status yet.

  • createNode and updateNode of kubernetes-nodes take the labels of the node from the CamelKubernetesNodesLabels header, the header that listNodesByLabels uses. Before, they read the CamelKubernetesPodsLabels header; a route that sets that header for a node must set CamelKubernetesNodesLabels instead.

  • The openshift-builds, openshift-build-configs and openshift-deploymentconfigs producers take the namespace from the CamelKubernetesNamespaceName header, or else from the namespace endpoint option, in every operation. Before, they ignored the option (used by the openshift-deploymentconfigs consumer only), listBuilds and listBuildConfigs listed all namespaces even when a namespace was set, and listDeploymentConfigs, listDeploymentConfigsByLabels and getDeploymentConfig always used the namespace of the Kubernetes client. Without a namespace, listBuilds and listBuildConfigs still list all namespaces, and the deployment config list and get operations still use the namespace of the client.

  • listBuildsByLabels, listBuildConfigsByLabels and listDeploymentConfigsByLabels without the labels header fail with an IllegalArgumentException that says the labels are required, instead of a NullPointerException.

  • scaleDeploymentConfig returns the replicas of the spec of the scaled deployment config, the number that was asked for. Before, it returned the replicas of its status, which lags behind until the pods are started or stopped, and failed with a NullPointerException when the deployment config had no status yet.

camel-langchain4j-embeddingstore - auto-embedding a TextSegment, a List, a Map or a POJO body, and the REMOVE id

With an embeddingModel and no CamelLangChain4jEmbeddingsEmbedding header, ADD and SEARCH now embed a TextSegment body as is, with its metadata. Before, they embedded and stored its toString().

They now fail the exchange with an InvalidPayloadException for a body that is not text, bytes, a stream, a file or a TextSegment, such as a Map or a POJO. Before, such a body was embedded and stored as its toString(), for example {text=…​}. Convert the body to text before the endpoint, with convertBodyTo(String.class) or a marshal to JSON.

ADD now embeds a List of String or TextSegment as a batch, one entry per element, and returns the List of the IDs. Before, the whole List was embedded and stored as one text (such as [a, b]), and the body was one ID. A List with another element type fails. SEARCH fails on a List body.

REMOVE now fails when the body is not text (a String, bytes or a stream) or a Collection of String IDs. Before, a Map or a number body removed the ID made of its toString(), which usually removed nothing.

camel-jbang - camel run uses MariaDB Connector/J 4

Camel is upgraded to MariaDB Connector/J 4.0 (org.mariadb.jdbc:mariadb-java-client), which is the version camel run downloads for a route or application that uses the MariaDB JDBC driver. The driver has these breaking changes:

  • TLS is enabled by default: sslMode defaults to verify-full. A server without TLS, or with a certificate the client does not trust (such as a self-signed one), now fails the connection. Configure the trust store, or set the sslMode option in the JDBC URL, for example sslMode=trust for a test database with a self-signed certificate, or sslMode=allow.

  • PreparedStatement.executeQuery() rejects a statement that returns no result set. The permitNoResults=true option in the JDBC URL restores the former behavior.

  • setSavepoint() throws in auto-commit mode.

  • AWS IAM authentication moved to the separate org.mariadb.jdbc:mariadb-java-client-aws artifact, which an application using it must now add.

  • Java 17 is the minimum, and the useReadAheadInput option is removed.

See the MariaDB Connector/J changelog for the other changes.

camel-resilience4j, camel-microprofile-fault-tolerance - a circuit breaker in a doTry

A circuit breaker in the steps of a doTry no longer ends that doTry for the steps after it. Before, when the circuit breaker was done, a later routing slip, dynamic router, multicast or recipient list of the same doTry used the error handler of the route (redelivery and the dead letter channel) instead of the doCatch. And after the fallback of the circuit breaker had recovered from a failure, no doCatch of that doTry caught a later failure: the failure was thrown from the doTry. Now such failures go to the doCatch, as for the other steps of the doTry. A failure of the fallback itself is now also caught by the doCatch (before, it was thrown from the doTry).

To have those steps use the error handler of the route, move them out of the doTry block.

camel-aws2-kinesis - each endpoint uses its own client

Each aws2-kinesis endpoint now uses the Kinesis client of its own configuration (region, credentials, uriEndpointOverride, or amazonKinesisClient / amazonKinesisAsyncClient). Before, all endpoints of the component used the client of the endpoint that started first, and stopping one endpoint (for example by removing its route) closed that client for every other endpoint.

Camel now closes only the clients it created. A client set with amazonKinesisClient or amazonKinesisAsyncClient, on the component or on an endpoint, is no longer closed when Camel stops: close it yourself when you no longer need it.

camel-stitch - a response with an empty body, and the default sequence of a message

A response of Stitch with an empty body now sets the CamelStitchCode, CamelStitchStatus and CamelStitchHeaders headers, and the body is an empty message. Before, the exchange kept the request body and got none of these headers. An error response (HTTP status 300 or above) with an empty body now fails the exchange with a StitchException, as any other error response does. Before, it was ignored.

A StitchMessage without a sequence now gets the time it is built (in millis) as its sequence. Before, every message got the same sequence: the time the StitchMessage class was loaded.

camel-stripe - the CamelStripeOperation header chooses the operation

The CamelStripeOperation header now overrides the operation of the endpoint, as documented. Before, the header was ignored and the operation of the endpoint was always used. Remove the header from a message that should use the operation of the endpoint.

camel-telemetry - the span of an asynchronous step ends when the step is done

This applies to camel-opentelemetry2, camel-micrometer-observability and camel-telemetry-dev. The span of a step that calls back from another thread (such as delay, which is asynchronous by default, threads, throttle with asyncDelayed, or a bean that returns a CompletableFuture) now ends when the step is done, and the steps after it are children of the span that contains the step (the route span, or the span of an EIP such as choice). Since 4.17 (since 4.21 with the default options, which trace the core processors) the following steps were children of the span of the asynchronous step, and that span lasted until the end of the route. Around a called route the spans were ended by the wrong events, and with a recipientList or enrich to such a route some spans were never ended. In particular:

  • The trace tree of such routes changes, and the span of the asynchronous step is shorter.

  • A failure in a later step no longer marks the span of the asynchronous step as failed: the error is on the span of the failing step and on the spans that contain it.

  • Steps of an onException that run after the asynchronous step, and custom spans created in a processor after it, are no longer nested under its span.

  • An asynchronous step that fails and is then handled by an onException with handled(true) now has its span marked as an error, because the span ends before the error handler runs. A synchronous step already behaved that way.

This is the tree of Camel 4.16 and older, so there is no option to keep the 4.17 tree. A dashboard that read the time of the rest of the route from the span of the asynchronous step can use the route span instead.

camel-telemetry, camel-tracing - the http.url, url.path and server.address span attributes are presented like camel.uri

This applies to camel-opentelemetry2, camel-micrometer-observability, camel-telemetry-dev, camel-opentelemetry and camel-observation. The http.url, url.path and server.address attributes of a span are now presented the same way as the camel.uri attribute, which shows the endpoint URI as Endpoint.toString() does:

  • http.url, when the message has neither a CamelHttpUrl nor a CamelHttpUri header, is now the HTTP address the endpoint sends to, with the query parameters it sends but without the Camel endpoint options. Before, it was the endpoint URI from the last http: onwards, with all the endpoint options. A camel-http endpoint with an https address now sets the attribute as well, and for a camel-vertx-http endpoint with an https address the value no longer starts with http://https://.

  • url.path no longer includes the user info in front of the host of the endpoint URI.

  • server.address, and http.url taken from the CamelHttpUrl or CamelHttpUri header, are presented as camel.uri is.

Dashboards or queries that use the old values may need a change.

camel-oaipmh - the CamelOaimphOnlyFirst and CamelOaimphIgnoreSSLWarnings headers apply to their message only

The CamelOaimphOnlyFirst and CamelOaimphIgnoreSSLWarnings headers now apply only to the message that has them. Before, the producer wrote them onto the endpoint, so after one message with CamelOaimphOnlyFirst=true every later message sent to that endpoint returned only the first page, and after one message with CamelOaimphIgnoreSSLWarnings=true every later message accepted a self-signed certificate. A message without the header uses the onlyFirst and ignoreSSLWarnings options of the endpoint.

camel-clickhouse - a query in a binary format returns a byte[] body

A query with a binary format (such as RowBinary, Native, Parquet, Arrow or Avro) now returns the result as a byte[] body. Before, the bytes were decoded as UTF-8 text into a String, which replaced the bytes that are not valid UTF-8, so the result could not be read. A query with a text format (such as JSONEachRow, CSV or TSV) still returns a String.

Inserting a List of Map now fails with a ClickHouseException that says why, and so does a List of objects when no class is registered on the Client for the table. Before, both failed with a NullPointerException from the ClickHouse client.

camel-clickup - an event of another kind than taskTimeTrackedUpdated reaches the route

A webhook event of another kind than taskTimeTrackedUpdated, such as taskCreated, now reaches the route as a GenericEvent (getEvent() gives the kind, getProperties() the other fields of the JSON). Before, it failed before the route with 500, and ClickUp kept sending it again. A route that expects only TaskTimeTrackedUpdatedEvent can check the type of the body, as the example in the ClickUp component page does.

camel-cm-sms - the from of an SMSMessage is the sender of the SMS

An SMSMessage with a from is now sent with that sender. Before, the sender was set only when from was empty (to defaultFrom), so a message with its own from was sent with an empty sender. A message without a from is still sent with defaultFrom.

When the validation of an SMSMessage fails, the InvalidPayloadRuntimeException now names the invalid fields in its message, and its cause is an org.apache.camel.ValidationException.

camel-coap - a body that does not convert to a byte array fails

The coap producer sent a POST or PUT body that has no conversion to byte[], such as a Map, a POJO or a number, as an empty payload, without an error, so the data was lost. It now fails the exchange with an InvalidPayloadException. A message without a body is still sent with an empty payload.

The coap consumer answered such a reply body with 2.05 Content and an empty payload. It now answers 5.00 Internal Server Error, as the CoAP component page documents. A route without a reply body still answers 2.05 Content with an empty payload.

Marshal such a body first (for example to JSON) or convert it to a String.

camel-cometd - stream, file and null bodies are published

The cometd producer, and the reply of an InOut cometd consumer, now publish a stream, a Reader or a file body (File, Path or the body of the file consumer) as its content as text. Before, the clients received the text of its toString, such as ByteArrayInputStreamCache@…​ or the path of the file. A cometd consumer in the same JVM now receives such a body as a String.

A null body is now published as a JSON null that the subscribed clients receive. Before, the message had no data, so the clients took it for the reply of a publish and never received it, while the exchange succeeded.

camel-dapr - the producer keeps the message headers, and sends text and stream bodies as bytes

The dapr producer replaced all the headers of the message with the headers of the Dapr response, so every header set before the call was gone afterwards. It now keeps the headers of the message and adds the headers of the response. A CamelDapr* header set before a dapr call therefore also applies to the next dapr call of the route; remove it when the next call must use the options of its endpoint.

The invokeService, pubSub, invokeBinding and state save operations now convert a text body (String) and a stream body (InputStream, Reader) to bytes, which the Dapr client sends as they are. Before, the Dapr client serialized a text body as a JSON string, so hello was sent as "hello" with the quotes, and a JSON text was sent as a quoted, escaped JSON string; a stream body failed. A route that relied on the quoted text must now send the JSON it wants, for example with marshal().json(). Other bodies, such as a Map or a POJO, are still serialized as JSON. The workflow operations give the body to the workflow client as before.

camel-consul - no Optional in the body, and the CamelConsulResult header of every action

The GET_SESSIONS action of consul:kv, the RENEW action of consul:session and the GET action of consul:preparedQuery set the java.util.Optional of the Consul client as the body, and the CamelConsulResult header was always true. The body is now the value itself: the session id as a String, the SessionInfo or the StoredQuery. When there is none, the body is unchanged and CamelConsulResult is false, as with the INFO action of consul:session.

The actions of consul:agent and consul:status now set the CamelConsulResult header, as the other actions do. REGISTER and DEREGISTER of consul:catalog now set it to true instead of false.

The FIRE action of consul:event with a message without body failed with a NullPointerException. It now fires the event without payload.

camel-aws - the HTTP client is no longer looked up on the classpath

The camel-aws components now set the HTTP client of the AWS SDK clients they create for their endpoints: the Apache 5 client (software.amazon.awssdk:apache5-client) for synchronous clients, and the Netty client (software.amazon.awssdk:netty-nio-client) for asynchronous clients. These are the clients the AWS SDK selects itself with the Camel dependencies. Before, unless a proxy or trustAllCertificates was configured, the AWS SDK looked the HTTP client up on the classpath. That lookup fails with java.util.ServiceConfigurationError (…​ not a subtype) when the classpath registers an HTTP client from another copy of the AWS SDK, as on a Kafka Connect worker.

If you replaced these dependencies to use another HTTP client implementation, name it with the AWS SDK system property software.amazon.awssdk.http.service.impl (synchronous clients) or software.amazon.awssdk.http.async.service.impl (asynchronous clients): Camel then leaves the choice of the HTTP client to the AWS SDK.

camel-aws-bedrock - retrieval pagination

The retrieve operation of aws-bedrock-agent-runtime now removes the CamelAwsBedrockAgentRuntimeNextToken header when the response has no continuation token. Previously, an incoming token could remain on the final page. Pagination loops should stop when this header is absent. The CamelAwsBedrockAgentRuntimeRetrieveGuardrailAction header is also removed when the response has no guardrail action, so an action from an earlier page cannot survive.

camel-aws-bedrock - the invoke operations send the body, not its toString()

The invoke operations of aws-bedrock now convert the message body to bytes, so a byte[], an InputStream or a file is sent as its content, and a Map or a Jackson JsonNode is marshalled to JSON. Before, the body was sent as its toString(), so only a String body worked. A message with no body now fails with an InvalidPayloadException, where before the text null was sent to the model.

Other changes of aws-bedrock:

  • invokeTextModel with amazon.titan-embed-text-v2:0 now fails with an IllegalArgumentException that points to invokeEmbeddingsModel, as with the other embedding models. Before, it gave the JSON of the embedding as text.

  • The streaming operations fail with an IllegalArgumentException for an image generation or embedding model, which do not stream. Before, invokeImageModelStreaming and invokeEmbeddingsModelStreaming gave an empty text.

  • With no operation option and no CamelAwsBedrockOperation header, the producer fails with an IllegalArgumentException that says so, instead of a NullPointerException.

  • The CamelAwsBedrockGuardrailTrace header is a GuardrailTraceAssessment, as converse always set it, and converseStream now sets it too. ConverseStreamHandler.StreamMetadata keeps it as a GuardrailTraceAssessment instead of a GuardrailTrace.

  • converseStream with guardrailTrace=true now asks for the guardrail trace.

  • invokeTextModel with an AI21 Jamba model gives the choices of the response, which was null before.

In the same way, aws-bedrock-agent-runtime now sends a Map or a Jackson JsonNode body as JSON text for retrieveAndGenerate, retrieve, invokeFlow, invokeAgent and invokeInlineAgent. Before, a Map was sent as its toString(), such as {question=What is Camel?}, and a JsonNode as pretty-printed JSON.

camel-aws-bedrock - invokeImageModelStreaming and invokeEmbeddingsModelStreaming are deprecated

The invokeImageModelStreaming and invokeEmbeddingsModelStreaming operations of aws-bedrock are deprecated, and will be removed in a later release. The image generation and embedding models do not stream their response, so these operations fail for such a model, and with a text model they are the same as invokeTextModelStreaming. Use invokeImageModel, invokeEmbeddingsModel or invokeTextModelStreaming instead.

camel-weather - the CamelWeatherLocation header is the location that is queried

The latitude and longitude of the current geolocation (a location of current, or no location) are no longer written onto the configuration of the endpoint. Before, they were, so every later query on that endpoint, including one with a CamelWeatherLocation header such as Paris, requested the weather of the current geolocation. An endpoint without a location already did the geolocation when its producer was created, so its CamelWeatherLocation header was never used. The lat and lon options of an endpoint are still used instead of the location.

camel-geocoder - Nominatim sets ZERO_RESULTS when it finds no place

With type=NOMINATIM, an address that Nominatim does not find (it answers []) now sets the CamelGeoCoderStatus header to ZERO_RESULTS. Before, the exchange failed with an IllegalArgumentException. A latitude and longitude with no place (Nominatim answers {"error":"Unable to geocode"}) now sets ZERO_RESULTS too, and another error answer of Nominatim sets ERROR. Before, both set OK with no other headers.

camel-xchange - an endpoint with a method of another service fails

An xchange endpoint now fails when it is created if it has no service or no method, or if the method belongs to another service, such as service=marketdata&method=currencies. Before, its producer did nothing and left the message unchanged. The Currency and CurrencyPair headers and the body can now be text such as BTC and BTC/USDT; text with a currency that XChange does not know fails the exchange.

camel-gson, camel-jsonb - the unmarshal type header needs allowUnmarshallType

The Gson and JSON-B data formats now use the CamelGsonUnmarshalType and CamelJsonbUnmarshallType headers to choose the class to unmarshal to only when the new allowUnmarshallType option is enabled, as the Jackson data formats do with CamelJacksonUnmarshalType. By default the headers are ignored, and the configured unmarshalType is used (a Map for a JSON object when none is configured). A route that sets the header must enable the option, for example with .unmarshal(new JsonDataFormat(JsonLibrary.Gson).allowUnmarshallType(true)) or <json library="Gson" allowUnmarshallType="true"/>.

camel-google-calendar-stream

With syncFlow=false and considerLastUpdate=true, the consumer now follows all result pages in each poll. Previously it only read the first page, which could prevent later events from ever being delivered. In this mode, maxResults controls the API page size; a poll can now deliver more events than this value. Non-sync polling with considerLastUpdate=false continues to read only one page per poll.

camel-google-mail, camel-google-drive, camel-google-calendar, camel-google-sheets - one API client per endpoint

The google-mail, google-mail-stream, google-drive, google-calendar, google-calendar-stream, google-sheets and google-sheets-stream components now create one API client per endpoint, from the options of that endpoint (clientId, clientSecret, refreshToken, accessToken, serviceAccountKey, delegate, scopes and applicationName, plus emailAddress, p12FileName and user on the two calendar components), instead of a single client per component that every endpoint of the component shared. Each endpoint keeps its client for its lifetime. The clientFactory option of a google-drive endpoint is now used to create the client of that endpoint. In addition, a google-mail-stream endpoint configured with neither clientSecret nor serviceAccountKey now fails with an IllegalArgumentException when its client is created, instead of getting a null client and failing at the first API call. The google-mail:update-message-labels data type transformer now uses a client created from the component-level configuration (the camel.component.google-mail.* options), kept until the component stops. Before, it used whichever client the component had already created, for example the one of a google-mail:messages/modify endpoint, so credentials set only on an endpoint URI no longer reach the transformer: set them on the component as well.

camel-grpc - the consumer reply must be the response message of the method

The grpc consumer sent the message body at the end of the route to the client as is. A body of another protobuf message type, such as the unchanged request, was serialized and decoded by the client as the response, with wrong field values and no error. The consumer now converts the reply (each element of a List reply) to the response message type of the called method, and when that is not possible it sends nothing and ends the call with an INTERNAL status error. A route must set the response message of the method as the body. A reply without a body still sends an empty response message.

The same applies to the replies that a producer with toRouteControlledStreamObserver=true sends for a consumer with routeControlledStreamObserver=true: a reply that does not convert ends the call with an INTERNAL status error and fails the exchange. Such a producer also sends each element of a List body as a message, where a List failed before. A CamelGrpcEventType header with a value other than onNext, onCompleted or onError now fails the exchange, where it sent nothing before. An onError event with a body that is not a Throwable now ends the call with an INTERNAL status error and fails the exchange with an IllegalArgumentException, where it failed with a ClassCastException or a NullPointerException and the client waited until its deadline.

camel-hashicorp-vault - cloud without a namespace, the createSecret body, and the secret version

With cloud=true and no namespace, the hashicorp-vault producer now works on the secrets engine itself, as the secret refresh task already did. Before, getSecret failed with Path must not be empty and listSecrets returned an empty list. A hashicorp: property with camel.vault.hashicorp.cloud=true and no namespace now also reads the secret from the engine itself. With cloud=true and a namespace, createSecret and deleteSecret now work on the secret in the namespace (<namespace>/<secretsEngine>), as getSecret and listSecrets do. Before, they worked on the engine outside the namespace.

createSecret now writes a body of text, bytes, a stream or a file that holds a JSON object as the fields of the secret; before, such a body failed. A body that is not a JSON object, and a message without a body, now fail the exchange. Before, a message without a body wrote a new, empty version of the secret.

The CamelHashicorpVaultSecretVersion header of getSecret must be a version number; any other value now fails the exchange with an IllegalArgumentException.

camel-hashicorp-vault - the token is renewed, and requests to Vault time out

The hashicorp-vault endpoint, the hashicorp: properties function and the secret refresh task now renew their token before it expires. With a token, Camel now looks the token up in Vault (auth/token/lookup-self) at its first request, and renews it (auth/token/renew-self) when the token is renewable and has a time to live. Vault’s default policy allows both. A token that cannot look itself up logs a warning, and is used as before, without renewal. Camel never revokes a token it is given; a token it gets from one of the new logins, chosen with the authentication option, is revoked when the endpoint, the function or the task stops.

Requests to Vault now time out after 5 seconds to connect, and after 15 seconds to read the response, which are the defaults of Spring Vault. Before, a request could wait forever.

The hashicorp: properties function now uses the VaultTemplate it is created with. Before, it replaced it with a client of the camel.vault.hashicorp configuration, and failed to start when that configuration had no token. A hashicorp-vault endpoint without a vaultTemplate now fails when it is created if its host, port or scheme is missing, or a setting of its login is missing. Before, a missing host failed only at the first request to Vault. The camel.vault.hashicorp.token option is no longer marked as required, as only the token login uses it.

The hashicorp-secrets dev console now shows the login (Token, AppRole, Kubernetes or JWT) instead of OAuth Token.

camel-hivemq - a body that does not convert to a byte array fails the exchange

The hivemq producer published a message body that has no conversion to byte[], such as a Map, a POJO or a number, as an empty payload, without an error, so the data was lost. It now fails the exchange with an InvalidPayloadException. Marshal such a body first (for example to JSON) or convert it to a String. A message without a body is still published as an empty payload.

camel-jcache - a JCacheManager closes only what no other manager uses

JCacheManager.close(), which a jcache endpoint calls when it stops, closed its cache, its CacheManager and the CachingProvider, which is shared by the whole JVM, and so the caches of every other endpoint, repository and CamelContext of that provider. It now closes each of them only when no other JCacheManager uses it. A JCacheManager that was closed no longer opens its cache: getCache() then throws IllegalStateException; before, it returned the closed cache, whose operations throw IllegalStateException. This matters for code that calls a JCacheManager (for example one created by a custom JCacheManagerFactory) after it was closed.

camel-jgroups-raft - a body that does not convert to a byte array fails the exchange

The jgroups-raft producer skipped a message body that has no conversion to byte[], such as a Map, a POJO or a number, without an error, so nothing was set in the replicated log and the exchange succeeded. It now fails the exchange with an InvalidPayloadException. Marshal such a body first (for example to JSON) or convert it to a String. A message without a body is still skipped.

camel-keycloak - a failed create or delete fails the exchange, and a pojoRequest body is converted

The producer operations that give the JAX-RS Response of Keycloak as the body, such as createUser, deleteUser, createGroup, createClient and createOrganization, now fail the exchange with a jakarta.ws.rs.WebApplicationException when Keycloak answers with a status other than 2xx, such as 409 (already exists) or 404 (not found). Before, the exchange completed and the route had to check the status of the Response. deleteOrganization, removeOrganizationMember and unlinkOrganizationIdentityProvider fail the same way. The Response body is now closed: its status and headers, such as Location, can still be read, but not its entity. The bulk operations count such a status as a failed item.

With pojoRequest=true, a body that is not the Keycloak representation of the operation is converted: a Map or JSON text is mapped to it. A body that does not convert fails with an InvalidPayloadException; before, the operation did nothing and the body was kept. The elements of the List of bulkCreateUsers and bulkUpdateUsers are converted the same way; an element that does not convert fails the exchange before any call to Keycloak.

camel-keycloak - JWKS public key resolution rate limiting

KeycloakSecurityPolicy now rate limits the refresh of the public keys from the JWKS endpoint of Keycloak (/protocol/openid-connect/certs), so that a burst of tokens with unknown kid headers, or a key rotation, no longer contacts the endpoint once per request.

The new option minJwksRefreshInterval is the minimum interval in seconds between two refresh attempts, 10 by default. A token with an unknown kid triggers a refresh only when no attempt was made within that interval. Otherwise the token is rejected, because its key is not in the cache, and the endpoint is contacted again only once the interval has elapsed. A failed attempt counts as an attempt.

The cached keys are refreshed on a request once they are older than jwksRefreshInterval, in seconds and 300 by default. There is no background refresh.

camel-splunk-hec - bytes, stream and file bodies are sent as text

The producer now sends a byte array, an InputStream, a Reader or a file body (File, Path or the file of the file consumer) as text in the event. Before, a byte array was sent as a base64 string, a File as its path, and a stream failed after part of the request had already been sent.

The JSON of the event is now written before the request is sent, with a Content-Length instead of a chunked request body. A body or a header that cannot be written as JSON fails the exchange without sending anything to Splunk.

camel-spring-ai-embeddings - a List body always gives a List of vectors, and a Map or POJO body fails

A List body with one text now gives a List<float[]> with one vector, as a List of more texts does, and sets the CamelSpringAiEmbeddings and CamelSpringAiEmbeddingInputTexts headers. Before, it gave a single float[] and set the CamelSpringAiEmbedding, CamelSpringAiEmbeddingIndex and CamelSpringAiEmbeddingInputText headers, as for a String body. A String body still gives a single float[].

Each element of a List body is now read as text, and a null element fails with an IllegalArgumentException. Before, a List of other objects failed with a ClassCastException in the embedding model. An empty List now gives an empty List without calling the embedding model.

The body, and each element of a List body, must now be text, bytes, a stream, a Reader, a file or a Spring AI Document (its text is embedded). Any other value, such as a Map, a POJO or a number, fails the exchange with an InvalidPayloadException. Before, such a body was embedded as its toString(), for example {a=1, b=2}, and a Document too. Convert the body to text before the endpoint, with convertBodyTo(String.class) or a marshal to JSON.

This also applies to bodies that Camel can convert to text but that are not in the list above, and that were embedded as their text before: a char[], a Netty ByteBuf (for example from netty-http with disableStreamCache=true), and a custom type with its own type converter to String. A List with such an element fails too, as do a Set and an array of texts (only a java.util.List is embedded as a batch). Add convertBodyTo(String.class) before the endpoint for such a body, or convert each element of the List to a String.

camel-spring-ai-vector-store - DELETE takes the ids from the body first, and the embeddings headers go with a vector body

The DELETE operation now takes the ids from the body: an id, a Document, or a List of ids or of Document. The CamelSpringAiVectorStoreDocumentIds header is used only when the message has no body. Before, the header won over the body, and as ADD and SIMILARITY_SEARCH set this header to their own output ids, a later DELETE in the same route deleted those documents instead of the ids in the body. A route that sets the header and sends a body that is not the ids should remove the body (for example setBody(constant(null))) or put the ids in the body. Deleting what SIMILARITY_SEARCH found still works, as its body is the List of Document found. A List with an element that is not a String or a Document now fails.

The ADD operation now uses the CamelSpringAiEmbeddingInputText and CamelSpringAiEmbeddingInputTexts headers of spring-ai-embeddings only with a float[] or a List of float[] body. Before, a header left over from an earlier spring-ai-embeddings call replaced a Document or text body. A float[] body without the text header, a List of float[] with a different number of texts, and a List with an element that is not a Document or a String now fail with an IllegalArgumentException. Before, a vector without its text was stored as the text of float[].toString(), extra vectors or texts were dropped, and other elements were skipped.

SIMILARITY_SEARCH with a float[] body (after spring-ai-embeddings) now searches for the text of the CamelSpringAiEmbeddingInputText header, and fails without it. Before, it searched for the text of float[].toString().

camel-spring-batch - the CamelSpringBatchJobName header needs jobFromHeader=true

The producer now takes the job name from the CamelSpringBatchJobName header only when the endpoint has jobFromHeader=true. Before, the header chose which job was launched also with the default jobFromHeader=false, instead of the job named in the endpoint URI. Set jobFromHeader=true on the endpoint to keep picking the job from the header.

A message header with a null value is no longer turned into a job parameter. Before, such a header failed the job launch, because Spring Batch does not accept a job parameter without a value.

camel-salesforce - a Map or POJO body is sent as JSON, and raw fails on a body it cannot send

The REST operations (such as createSObject, updateSObject and upsertSObject) now marshal a Map or another object that is not an SObject DTO as JSON. Before, they sent its toString(), for example {Name=x}, which is not JSON. Text, bytes, a stream or a file in the body are sent as is, as before.

The raw operation now fails with an InvalidPayloadException when the body cannot be converted to an InputStream, for example a Map. Before, it sent the request with an empty body.

camel-spring-rabbitmq - confirm=auto waits for simple publisher confirms

With the default confirm=auto, an InOnly producer now also waits for the publisher confirm when the connection factory has publisher confirms of type SIMPLE (for example spring.rabbitmq.publisher-confirm-type=simple), as it did for type CORRELATED. Before, it sent such messages without waiting, so a message that RabbitMQ negatively acknowledged, or did not confirm, was lost without an error. Each send now waits up to confirmTimeout (5 seconds by default). It fails with a java.util.concurrent.TimeoutException when RabbitMQ negatively acknowledges the message, and with an org.springframework.amqp.AmqpTimeoutException when RabbitMQ does not confirm it within confirmTimeout. Set confirm=disabled to send without waiting, as before.

camel-cyberark-vault - verifySsl and certificatePath are applied, and getSecret no longer sets the CamelCyberArkVaultSecretValue header

The verifySsl and certificatePath options of the cyberark-vault endpoint, and the camel.vault.cyberark.verifySsl and camel.vault.cyberark.certificatePath properties of the CyberArk Conjur Vault Property Function, now apply to the HTTPS connections to CyberArk Conjur. Before, they were ignored, and the default trust store of the JVM was always used.

  • With certificatePath, the certificates in that file (PEM or DER) are trusted instead of the default trust store of the JVM, and the endpoint or the function fails to start when the file cannot be read. Remove certificatePath to keep the default trust store of the JVM.

  • With verifySsl=false, neither the certificate nor the host name of the server is checked. The option is now marked as an insecure SSL setting, so the security policy reports camel.component.cyberark-vault.verifySsl=false, which fails the startup with camel.main.profile=prod. Set certificatePath to the certificate of the server or of its CA instead.

The getSecret operation now sets the secret only as the message body, and no longer sets the CamelCyberArkVaultSecretValue header. Before, a createSecret later in the same exchange stored the value of that header instead of its own body. A route that reads the secret from the CamelCyberArkVaultSecretValue header after getSecret has to read the body instead, or copy the body into a variable right after getSecret, for example with .setVariable("mySecret", body()), and read it later with ${variable.mySecret}. A variable stays in the exchange, while producers such as HTTP, JMS or Kafka send the headers on to the next system. The createSecret operation still stores the CamelCyberArkVaultSecretValue header when the route sets it.

camel-rocketmq - the headers are named CamelRocketMQ, and waitForSendResult is true by default

The header names of camel-rocketmq were misspelled CamelRockerMQ…​; they are now CamelRocketMQ…​. The Java field names of RocketMQConstants are unchanged; only the header string values have changed.

Previous value New value

CamelRockerMQTopic

CamelRocketMQTopic

CamelRockerMQTag

CamelRocketMQTag

CamelRockerMQKey

CamelRocketMQKey

CamelRockerMQOverrideTopicName

CamelRocketMQOverrideTopicName

CamelRockerMQOverrideTag

CamelRocketMQOverrideTag

CamelRockerMQOverrideMessageKey

CamelRocketMQOverrideMessageKey

CamelRockerMQBrokerName

CamelRocketMQBrokerName

CamelRockerMQQueueId

CamelRocketMQQueueId

CamelRockerMQStoreSize

CamelRocketMQStoreSize

CamelRockerMQQueueOffset

CamelRocketMQQueueOffset

CamelRockerMQSysFlag

CamelRocketMQSysFlag

CamelRockerMQBornTimestamp

CamelRocketMQBornTimestamp

CamelRockerMQBornHost

CamelRocketMQBornHost

CamelRockerMQStoreTimestamp

CamelRocketMQStoreTimestamp

CamelRockerMQStoreHost

CamelRocketMQStoreHost

CamelRockerMQMsgId

CamelRocketMQMsgId

CamelRockerMQCommitLogOffset

CamelRocketMQCommitLogOffset

CamelRockerMQBodyCrc

CamelRocketMQBodyCrc

CamelRockerMQReconsumeTimes

CamelRocketMQReconsumeTimes

CamelRockerMQPreparedTransactionOffset

CamelRocketMQPreparedTransactionOffset

Routes that use the constants (for example setHeader(RocketMQConstants.OVERRIDE_TOPIC_NAME, …​)) work without changes. Routes that use a header by its literal name (for example CamelRockerMQOverrideTopicName in an XML or YAML route, or ${header.CamelRockerMQKey} in Simple) must be updated to the new name; the previous names are no longer read. The generated Endpoint DSL header accessors are renamed accordingly (rockerMQTopic() → rocketMQTopic(), rockerMQOverrideTopicName() → rocketMQOverrideTopicName(), and so on).

The waitForSendResult option is now true by default. Before, it was false, but the producer still waited for the send result and failed the exchange when the send failed: it only reported the exchange as done synchronously while calling the callback later, which breaks the asynchronous routing contract. The default behaviour is therefore unchanged. With waitForSendResult=false an InOnly exchange now continues as soon as the message is handed to the RocketMQ client, and a failed send is only logged (at WARN) and no longer fails the exchange.

camel-azure-cosmosdb - the consumer starts the change feed processor again when its start fails

When the change feed processor of an azure-cosmosdb consumer cannot start (Cosmos DB cannot be reached, the lease container does not exist yet, a role assignment is not applied yet), the consumer now tries again, after 1 second and then with a backoff of up to 30 seconds, until it starts or the consumer stops; before, the route stayed started and never received a change. The first failure of each start still goes to the ExceptionHandler of the consumer (with bridgeErrorHandler=true it is routed to the error handler once), and each further failed attempt is logged as one WARN line.

Its readiness health check (consumer:<routeId>, with the consumer health checks of Camel enabled, the default) is now the initial state (camel.health.initial-state, DOWN by default) while the first attempt to start runs, and DOWN while the change feed processor cannot start, with the failure of the last attempt; before, it was UP. To leave the check out, as before, set camel.health.exclude-pattern=consumer:<routeId>.

camel-test-infra-azure - the azure.container property holds the Floci AZ image

The Azure test-infra runs its new local Data Lake Storage Gen2 service (camel infra run azure storage-datalake) on Floci AZ, and the azure.container property of camel-test-infra-azure-common now holds the Floci AZ image. The Blob and Queue services still run on Azurite, whose image is now set with the azure.storage.blob.container and azure.storage.queue.container properties: to run them on another Azurite image, set these instead of azure.container.

camel-vertx-websocket - consumers on the same host and port must use the same SSLContextParameters

All vertx-websocket consumers on the same host and port are served by one HTTP server, which is created from the sslContextParameters of the first consumer to start. A consumer started later with different sslContextParameters (including one with none while the server uses some, or the other way round) used to start and be served with the first consumer’s settings. Such a consumer now fails to start with an IllegalArgumentException naming the host and port. Give every consumer on a host and port the same SSLContextParameters instance (for example the same bean reference, or the global SSL context parameters), or move consumers that need other settings to another port.

camel-as2 - the asynchronous MDN allow-list matches the host and port

The asyncMdnAllowedHosts option now matches the full authority - host and port - of the asynchronous MDN delivery address, rather than the host alone. Each entry is a host with an optional port in host:port form (IPv6 literals use bracket notation, for example [::1] or [::1]:8443). A bare-host entry (for example partner.example) now matches only the default port for the scheme (80 for http, 443 for https); to deliver to a non-default port, list it explicitly (for example partner.example:8443).

A deployment that lists a bare host and receives asynchronous MDNs on a non-default port must add the port to the entry.

camel-asterisk - QUEUE_STATUS and SIP_PEERS return the events of the list

The QUEUE_STATUS and SIP_PEERS actions of the asterisk producer now wait for the whole list and set the org.asteriskjava.manager.ResponseEvents as the body: getResponse() is the response and getEvents() the events, such as QueueParamsEvent, QueueMemberEvent and PeerEntryEvent. Before, the body was only the ManagerResponse saying that the list will follow, and the events were lost. A route that read that response from the body now finds it in ${body.response}. EXTENSION_STATE still sets the ManagerResponse as the body.

camel-jbang - camel validate and camel overview report the same endpoints no route consumes

camel validate source and camel overview now use the same rules for a direct:, seda: or disruptor: endpoint that a route sends to and no route consumes, so they report the same endpoints on the same project:

  • disruptor: endpoints are checked too, and camel overview no longer treats disruptor:q as seda:q (they are different endpoints at runtime); vm: and direct-vm:, which do not exist in Camel 4, are no longer joined with seda: and direct:.

  • camel validate counts a poll or pollEnrich as consuming its endpoint, and a toD without an expression, an enrich with a constant endpoint, a rest verb, and the dead letter channel of an error handler as sending to it. It reads the flows of an Integration resource, and stays quiet for a Pipe.

  • camel overview stays quiet, as camel validate does, when a route consumes an endpoint only known at runtime (from: direct:{{name}}), and no longer reports a to whose uri holds an expression.

So camel validate can report an endpoint it did not report before.

The file tools of camel mcp, camel ask and the TUI AI agents (camel_validate_source, camel_get_files, camel_edit_file, camel_write_file) now resolve symbolic links before they check that a file is inside the project directory. A file reached through a link in the project that leads out of it, or through a link to nothing, is refused for reading and for writing, where it used to be read or written. Name the files of such a folder through its real directory instead. An absolute file inside the project directory is now accepted, as the same file named relative to it.

camel-ftp, camel-jsch, camel-mina-sftp - the SFTP and SCP clients negotiate the post-quantum key exchange

camel-ftp and camel-mina-sftp now declare org.bouncycastle:bcprov-jdk18on as a compile dependency, and camel-jsch picks it up through camel-ftp.

Both SSH stacks propose a hybrid post-quantum key exchange ahead of the classical groups - mlkem768x25519-sha256 for jsch; sntrup761x25519-sha512, also under its sntrup761x25519-sha512@openssh.com name, then mlkem768x25519-sha256, mlkem1024nistp384-sha384 and mlkem768nistp256-sha256, in that order, for Apache MINA SSHD - but both implement ML-KEM through the Bouncy Castle lightweight API (jsch can also use the JDK from Java 24 on). On the Java 21 baseline neither had it on the runtime classpath, so the hybrid groups never reached the proposal and every SFTP or SCP connection silently fell back to a classical key exchange.

These endpoints therefore now perform a different key exchange than before against a server that offers a hybrid group. Which one depends on the server: OpenSSH before 9.9 offers only sntrup761x25519-sha512@openssh.com, which mina-sftp negotiates and sftp does not propose unless it is added to keyExchangeProtocols; OpenSSH 9.9 and later also offers mlkem768x25519-sha256, which sftp negotiates, while mina-sftp still negotiates sntrup761x25519-sha512 because it proposes that first. A deployment that must keep the previous behaviour can pin the classical groups with keyExchangeProtocols on sftp and mina-sftp, and with kex on ssh. scp (camel-jsch) has no such option: there the proposal is set for the whole JVM with the jsch.kex system property, for example -Djsch.kex=curve25519-sha256,ecdh-sha2-nistp256,diffie-hellman-group16-sha512. Note that these options replace the proposal rather than adding to it, so an endpoint that already pins a list of classical groups is unaffected - and will not negotiate a post-quantum exchange until a hybrid group is added to that list.

With Bouncy Castle on the classpath, Apache MINA SSHD also registers it as the security provider of all its cryptography in camel-mina-sftp, not only of the key exchange: key factories, key pair generation, key agreement, signatures and digests now come from Bouncy Castle rather than from the JDK providers, which matters on a JVM configured for FIPS. The registration is switched off with the -Dorg.apache.sshd.security.provider.BC.enabled=false system property.

camel-ftp, camel-mina-sftp, camel-jsch, camel-smb, camel-azure-files - checksumFileAlgorithm writes a checksum again

Since 4.18 the producers of the remote file components (ftp, ftps, sftp, mina-sftp, scp, smb, azure-files) accepted checksumFileAlgorithm but wrote no checksum file and did not set the CamelFileChecksum header; when an earlier file producer of the route had set that header, they wrote its checksum, the checksum of another payload. They now calculate the checksum of the bytes they upload, write the checksum file (unless checksumWriteFile=false) and set the header, as in 4.14. A checksum header set before the producer is no longer used by any file producer. scp and azure-files upload the bytes of the body without the charset option, while the checksum applies it: do not set charset with checksumFileAlgorithm there. FTP uploads in ASCII mode unless binary=true, and the server may then change the line ends of the stored file, so its checksum can differ from the checksum of the uploaded bytes.

camel-file - a done file with a fixed name is kept for the files a poll left

A done file with a fixed name (such as doneFileName=ready) is no longer deleted while a poll left files of its directory for the next poll (beyond maxMessagesPerPoll, or not started); before, they were left until a new done file was written. Meanwhile the polls consume every file of the directory, as they do whenever the done file is there. This also applies to pollEnrich and ConsumerTemplate.receive, which take one file per call: the done file is now kept until the last file of the directory is received, where before the first received file deleted it. This applies to camel-ftp, camel-sftp, camel-smb and camel-azure-files as well.

camel-file - poll and pollEnrich evaluate the fileName on the current message

poll and pollEnrich with a file based component (file, ftp, ftps, sftp, smb and azure-files) evaluate a dynamic fileName that does not use the file functions on the current message, so fileName=${body} and fileName=${body[customer]}.json read its body. Before, the consumer evaluated it for each file found, with that file as the body, so ${body} was the file found. A fileName that uses the file functions (${file:…​}, ${date:file:…​}) is still evaluated for each file found, and headers, variables and exchange properties work as before. A fileName that read the file found through its headers, such as ${header.CamelFileName}, must use the file functions instead (${file:name}).

A flink:datastream endpoint whose callback returns a DataStream no longer fails with "collect mode not supported for Flink DataStreams." when collect is not set: the DataStream is the message body and the CamelFlinkDataStream header, as with collect=false. An explicit collect=true still fails. The dataStream and dataStreamCallback options of the flink component are now used by endpoints that do not set them, as dataSet and dataSetCallback already were.

camel-micrometer - a counter increment or decrement that does not give a number fails the exchange

A counter with the increment or decrement option (such as increment=${body} or increment=${header.amount}) added 1 to the counter when the expression did not give a number, for example a Map, a POJO or a null body, or a missing header. It now fails the exchange with an IllegalArgumentException, and the counter is not updated. A counter with neither option still adds 1.

camel-mina-sftp - the local work file is deleted when the exchange is done

With localWorkDirectory, camel-mina-sftp now deletes the local work file of a consumed file when the exchange is done, also when it failed, as camel-sftp and camel-ftp do. Before, every consumed file stayed in the local work directory. A file producer in the route now moves the local work file to its target instead of copying it, while the message body still names the local work file. So a later step that reads the body again, such as a second file producer or a convertBodyTo, fails because the file is gone, and another exchange that keeps the java.io.File body (for example wireTap, seda without waiting, or aggregate) finds it deleted. Convert the body before the first file producer or before handing it on, for example with convertBodyTo(byte[].class) (which reads the file into memory). Routes that cleaned the local work directory themselves can stop doing so.

camel-olingo4 - filterAlreadySeen keeps the type of a complex or collection value

With filterAlreadySeen=true, reading a complex property gave a java.util.List of its ClientProperty instead of the ClientComplexValue, and an empty List when it was read again. A collection property gave a java.util.List of the values not read before instead of a ClientCollectionValue. Now a complex value is one result: it is the ClientComplexValue, or null when the same value was read before. A collection value stays a ClientCollectionValue with only the values not read before. The consumer sends no message for a poll where nothing new is left in the collection, as it already did for an entity set. Routes that read the List must read the Olingo value instead.

camel-openstack - potential breaking change

The four remaining Exchange header constants of SwiftConstants have been renamed to follow the Camel naming convention used by the rest of the component, completing the rename done in 4.21. The Java field names are unchanged; only the header string values have changed.

Constant Previous value New value

SwiftConstants.CONTAINER_METADATA_PREFIX

X-Container-Meta-

CamelOpenstackSwiftContainerMetadata

SwiftConstants.VERSIONS_LOCATION

X-Versions-Location

CamelOpenstackSwiftVersionsLocation

SwiftConstants.CONTAINER_READ

X-Container-Read

CamelOpenstackSwiftContainerRead

SwiftConstants.CONTAINER_WRITE

X-Container-Write

CamelOpenstackSwiftContainerWrite

The previous values were kept in 4.21 on the understanding that they were part of the Swift HTTP contract. They are not: the container producer passes the values to the openstack4j CreateUpdateContainerOptions, which builds the X-Container-Meta-, X-Versions-Location, X-Container-Read and X-Container-Write names of the Swift request itself. The request Camel sends to Swift is therefore unchanged by this rename.

Routes that reference the constants symbolically (for example setHeader(SwiftConstants.CONTAINER_READ, …​)) continue to work without changes. Routes that set one of these headers by its literal string value (for example setHeader("X-Container-Read", …​) in an XML or YAML route) must be updated to the new value (setHeader("CamelOpenstackSwiftContainerRead", …​)); the previous names are no longer read.

The generated Endpoint DSL header accessors on the Swift HeaderNameBuilder are renamed accordingly (xContainerMeta() → openstackSwiftContainerMetadata(), xVersionsLocation() → openstackSwiftVersionsLocation(), xContainerRead() → openstackSwiftContainerRead(), xContainerWrite() → openstackSwiftContainerWrite()).

camel-core - Split and Multicast child producers start with their route

Split and Multicast EIPs now start their child output producers during the route’s own start, instead of during CamelContext initialization. The visible changes are:

  • The producers now start when the route starts, not during CamelContext initialization. A CamelContext that is only built or initialized (without starting routes) no longer starts them.

  • Each route’s producers now start in startupOrder with their route, instead of ahead of all routes.

  • The producers of an autoStartup=false route are no longer started when the context starts; they start when the route itself is started (regardless of which route controller is used).

camel-core - Forced route-stop cut-off marks exchange as interrupted in any nested EIP

An exchange that a forced route stop cuts off inside a nested EIP (split, multicast, filter, choice, …​) of the stopped route is now marked as interrupted (exchange.getExchangeExtension().setInterrupted(true)) after the cut-off WARN is logged. This prevents nested stopped error handlers from each emitting their own WARN for the same exchange. The interrupted flag also causes the error handler to skip redelivery, onException routes, and the dead letter channel — the same behavior that DirectProducer applies (CAMEL-25502) when a route is stopped while an exchange is waiting for a consumer. A calling route that is still running (for example through direct) then also no longer runs its onException, redelivery or dead letter channel for that exchange. If your application relied on onException or the dead letter channel handling a RouteStoppingException from a nested EIP of the stopped route, it will no longer trigger on a forced route stop.

camel-opentelemetry-metrics - a counter increment or decrement that does not give a number fails the exchange

A counter with the increment or decrement option (such as increment=${body} or increment=${header.amount}) added 1 to the counter when the expression did not give a number, for example a Map, a POJO or a null body, or a missing header. It now fails the exchange with an IllegalArgumentException, and the counter is not updated. A counter with neither option still adds 1. The CamelMetricsCounterIncrement and CamelMetricsCounterDecrement headers are documented as long, which is how they are read.

camel-optaplanner - one SolverManager per endpoint, and consumers get the events of their own CamelContext

With configFile and no SolverManager (neither the solverManager option nor the CamelOptaPlannerSolverManager header), the producer created a new SolverManager for every exchange and never closed it, so its threads were never released. The endpoint now creates one SolverManager on first use and closes it when the endpoint stops. As the exchanges of the endpoint share it, solving a problem while another one with the same problemId is still being solved now fails with The problemId (1) is already solving, as it already did with a SolverManager you configure. Use a different problemId (an endpoint per problem) to solve several problems at the same time.

A consumer now gets the best solutions of the producers of its own CamelContext only. Before, it also got the ones of producers in any other CamelContext of the JVM with the same problemId. A consumer now also gets the best solutions of a producer that solves synchronously (async=false), not only of the ones that solve asynchronously.

camel-file - the checksum of checksumFileAlgorithm is the checksum of the written file

With checksumFileAlgorithm, the file producer calculated the checksum from the body before it was written. With charset set, it also decoded that body with the default charset of the JVM instead of the charset of the exchange. So with charset, the checksum (the CamelFileChecksum header and the checksum file) did not match the written file as soon as the encoding changed a byte, a body read with another charset (such as the bytes of a file consumed with charset=ISO-8859-1) was written with its non-ASCII characters replaced, and a String body could fail with an UnmappableCharacterException; with fileExist=Append and appendChars, the checksum left out the appended characters. The checksum is now calculated from the bytes written to the file, and the body is decoded as it is without checksumFileAlgorithm. Checksums of such files differ from the ones written before.

camel-flatpack - with splitRows each message has its own row, and a failing row fails the producer

With splitRows=true (the default), every row message shared the one parsed DataSet, so the next row moved the row of a message that was not yet converted. The body of each row message is now its own read-only DataSet with only that row (org.apache.camel.component.flatpack.DataSetRow): it converts to a Map as before, converting it to a List gives that one row instead of all the rows, and the methods that change the DataSet (such as setValue or remove) throw an UnsupportedOperationException.

An exception in the route that consumes the rows was lost, and the producer carried on with the next rows. The exception now fails the producer, and the rows after the failing row are not sent.

Converting a DataSet that is not on a row (the whole DataSet with splitRows=false) to a Map now fails with an IllegalStateException instead of a NullPointerException; convert it to a List.

With jailStartingDirectory enabled (the default), the sftp and mina-sftp consumers now resolve a listed file that the server reports as a symbolic link, and skip it when its target is outside the starting directory or cannot be resolved (such as a dangling link), as the file consumer does. A link whose target contains a backslash (\) is skipped too. Before, such a link was consumed like any other file, wherever it pointed. A skipped file is logged at WARN the first time and at DEBUG on later polls. Links resolving inside the starting directory work as before. Set jailStartingDirectory=false to keep following links to other directories.

camel-pg-replication-slot - the LSN of a message is in the CamelPgReplicationSlotLsn header

The consumer no longer sets the exchange id to the log sequence number (LSN) of the message. Several messages can have the same LSN (for example the BEGIN and the first change of a transaction), so exchange ids repeated. The exchange has a generated exchange id now, as with any other consumer, and the LSN is in the new CamelPgReplicationSlotLsn header (a String such as 0/16B3748). Use ${header.CamelPgReplicationSlotLsn} instead of ${exchangeId} to read the LSN.

camel-pqc - symmetricKeyLength defaults to 256

The symmetricKeyLength option of the pqc component and of the pqc data format now defaults to 256 instead of 128. The default KEM is ML-KEM-768, a NIST category 3 algorithm producing a 256-bit shared secret, and deriving a 128-bit key from it threw that margin away.

A route that leaves symmetricKeyLength unset therefore now produces a 256-bit key. Two consequences:

  • A key encapsulated by 4.24 cannot be used by a peer still running the old default, and material encrypted with the pqc data format by one version cannot be read by the other. Set symmetricKeyLength=128 explicitly on both ends while they are mixed.

  • The algorithms that cannot take a 256-bit key now fail unless symmetricKeyLength is set explicitly. Those are RC2 and CAST5 (40, 64, 128), DESEDE (128, 192), and GRAIN128, HC128, SEED and SM4 (128 only). Conversely CHACHA7539, GOST28147, GOST3412-2015 and HC256 accept 256 only, and no longer need an explicit value.

    The pqc component validates the length when the route starts and fails there with Invalid symmetric key length 256 for algorithm <name>. Valid key lengths: […​].

    The pqc data format does not check the length when it starts, so it fails on the first message instead, when the cipher rejects the key. Of the algorithms the data format accepts, only SEED and SM4 are affected. Note also that the algorithm itself can come from the CamelPQCSymmetricAlgorithm header, so a route whose configured algorithm is fine can still fail per message on an override.

camel-pqc - an unknown hybridKdfAlgorithm is rejected

The hybrid KEM operations now fail with an IllegalArgumentException when hybridKdfAlgorithm is not one of HKDF-SHA256, HKDF-SHA384 or HKDF-SHA512. Before, any other value silently fell back to HKDF-SHA256, so a typo such as HKDF-SHA-512 went unnoticed - and if only one end of an exchange carried the typo, both ends still reported success while deriving the key with different digests.

The HKDF info bytes of the hybrid KEM are also now encoded explicitly as UTF-8, where they previously used the platform default charset. The info string is the symmetric algorithm name, so this has no effect for the usual values such as AES, which are pure ASCII. A deployment running on a JVM whose default charset cannot encode that name identically to UTF-8 would, however, derive a different key than the previous version, and could not decrypt material produced by it.

camel-pqc - a stateful signature key is claimed for one process

XMSS, XMSSMT, LMS and HSS are one-time-signature schemes: signing twice from the same index breaks the key. The index lives inside the private key, so two processes that load the same key from a shared store - two pods on one volume, two replicas reading one vault secret - each advance their own copy and hand out the same index.

Before it first signs with a stateful key, the pqc producer now claims it from the configured KeyLifecycleManager, through the new KeyLifecycleManager.claimStatefulKey(String) and releaseStatefulKey(String) methods. Both have a default implementation, so a custom manager still compiles; the default grants no claim.

  • FileBasedKeyLifecycleManager takes an exclusive FileLock on a <keyId>.lock file in the key directory, so a second process reading that directory is refused.

  • InMemoryKeyLifecycleManager grants the claim - its keys never leave the process.

  • The vault-backed managers (AWS Secrets Manager, Azure Key Vault, Hashicorp Vault) cannot offer a lease Camel can take, so they do not grant it. An endpoint signing with a stateful key through one of them now fails, with a message naming the key.

Set the new allowSharedStatefulKey=true option on the endpoint to sign anyway, when a single signer is guaranteed outside Camel (one replica, an external lock, a leader election). A warning is logged once per key.

Nothing changes for the stateless signature algorithms (ML-DSA, SLH-DSA, Falcon and the rest), for the KEM operations, or for an endpoint that signs with a stateful key and no KeyLifecycleManager - that case persists no index at all and keeps the warning it already had.

camel-pqc - the BouncyCastle providers stay registered when an endpoint stops

Stopping a pqc endpoint no longer removes the BC and BCPQC providers from java.security.Security.

java.security.Security is JVM-global, and the endpoint only added a provider when it was absent, so stopping one endpoint could remove a provider it had not installed - breaking the other pqc endpoint of the same route, other CamelContext instances, components such as camel-crypto, camel-ssh and camel-xmlsecurity, and any non-Camel code in the same JVM.

An application that relied on the providers being de-registered on shutdown must now remove them itself with Security.removeProvider("BC") and Security.removeProvider("BCPQC").

camel-printer - a body that does not convert to a stream fails the exchange

The lpr producer submitted a print job with no data, without an error, for a message body that has no conversion to InputStream, such as a Map, a POJO or a number, and for a message without a body. It now fails the exchange with an InvalidPayloadException. Send the document as text, bytes, a stream or a file.

camel-printer - a doc flavor the print service does not support fails the exchange

The lpr producer skipped the print job, without an error, when the print service did not support the doc flavor of the flavor and mimeType options, and the exchange completed as if the document was printed. It now fails the exchange with a javax.print.PrintException that names the doc flavor and the print service, also with sendToPrinter=false. Set the flavor and mimeType options to a doc flavor that the printer supports.

camel-debezium - the row flattened by ExtractNewRecordState is the body

With the Debezium ExtractNewRecordState single message transformation (ExtractNewDocumentState for MongoDB), the body is now the flattened row (or document) as a Struct. Before, the body was null, as the component only read the after field of the change event, which the transformation removes. Any record whose value is a Struct without an after field is now the body, except the events of Debezium itself (schema change, heartbeat and transaction metadata events), which keep a null body. The CamelDebeziumOperation header is read from the __op field or header that the transformation adds with add.fields=op or add.headers=op.

The debezium-mongodb consumer no longer reads the patch field for an update, which Debezium removed in 2.0. The changed fields of an update are now in the new CamelDebeziumUpdateDescription header.

The Struct to Map type converter now also converts a Struct inside a list or a map.

camel-docker - the streaming operations give their result, and their failures fail the exchange

The operations imagebuild, imagepull, imagepush, containerattach, containerlog, containerwait and execstart now fail the exchange when they fail. Before, an error such as a missing build context was only logged and the exchange completed with the body unchanged. A failed build or push, reported by Docker in the output of the operation, now also fails the exchange.

The body after these operations changed:

  • imagebuild: the id of the built image, also for a build with a tag. Before, it was null for a tagged build. The body can now also be a byte[] or another body that converts to an InputStream (the tar archive of the build context), besides an InputStream or a java.io.File.

  • containerattach, containerlog and execstart: the output of stdout and stderr as a String. Before, the body was the docker-java callback, and the output was lost.

  • containerwait: the exit code of the container as an Integer. Before, it was the docker-java callback.

  • imagepull and imagepush: the last PullResponseItem or PushResponseItem of the progress. Before, it was the docker-java callback.

The events consumer now gives the start of the event stream to Docker in seconds, as the Docker API expects, so the past events of CamelDockerInitialRange (in milliseconds) are received. Before, the time was in milliseconds.

DockerConstants.DEFAULT_CMD_EXEC_FACTORY now names the Netty factory, which is the default of the cmdExecFactory option. Before, it named the Jersey factory. The default itself did not change; camel-docker now adds the Netty native transports for arm64, so the default works on Apple silicon and Linux on arm64.

camel-pubnub - FIRE and SETSTATE with an empty body fail without calling PubNub

The FIRE and SETSTATE operations with an empty body now fail with a CamelException (Can not fire empty message or Can not set state with an empty body) and do not call PubNub, as PUBLISH does. Before, the call to PubNub still ran, so FIRE with a null body could end with the PubNubException Message Missing instead. The message of the SETSTATE failure was Can not publish empty message.

The PNMessageResult body of the consumer now converts to a String: a string message as its text, any other message as JSON. Before, the conversion gave the toString() of the PNMessageResult object.

camel-jbang - camel validate normalize writes one file per input file to an output directory

camel validate normalize <files> --output=<dir> now writes one normalized file per input file into the directory, with the name of the input file, instead of merging all the routes into normalized.yaml. A top-level onException of a file then keeps applying to the routes of that file only, as it does in the source. This applies to a single input file too: camel validate normalize orders.yaml --output=out/ now writes out/orders.yaml instead of out/normalized.yaml. normalized.yaml is only written for what was not loaded from one of the input files. Input files with the same name in different directories are written as <parent directory>-<name>. Scripts that read <dir>/normalized.yaml must read the per-file outputs instead.

The command now refuses to write over one of its input files (for example --output=. in the directory of the routes) and exits with an error.

With --output=<file> or without --output, the routes are still merged, and the command now warns, on the standard error, when the top-level onException of one file applies to the routes of the other files in the merged output.

camel-pqc - generateSecretKeyEncapsulation stamps the JCE algorithm name on the secret key

The SecretKeyWithEncapsulation produced by generateSecretKeyEncapsulation now carries the mapped JCE algorithm name, as the extractSecretKeyEncapsulation and extractSecretKeyFromEncapsulation operations already did. Before, it carried the raw enum name.

This is only visible for the two algorithms whose enum name differs from the JCE name: DESEDE now yields DESede (was DESEDE), and GOST3412_2015 now yields GOST3412-2015 (was GOST3412_2015). The raw names are not resolvable cipher algorithms, so a key labelled with one could not be passed to Cipher.getInstance(key.getAlgorithm()), and the generate and extract ends of the same exchange disagreed on the key’s identity. Code asserting on the old value must be updated.

generateSecretKeyEncapsulation now also rejects a missing symmetricKeyAlgorithm with Symmetric Algorithm needs to be specified, as the extract operations already did, instead of failing later inside BouncyCastle with Missing argument.

camel-jbang - camel ask asks before a call that changes something, reaches out or runs code

camel ask now asks on the terminal before a file write (showing the diff), a message to an endpoint the integration does not have, SQL that changes data, an expression in a language that runs code, stopping or restarting the integration, and a camel command that changes something. Without a terminal to ask on, as in a script, such a call does not run: pass --yes to let it run without asking.

The model gets the shared file tools (camel_get_files, camel_edit_file, camel_write_file), which validate what they write, instead of the read_file, list_files and write_file tools of camel ask. A local model gets a core set of tools (--tools=auto, the default), a hosted model the full set. The default of --max-iterations is now 25 instead of 10, and --model llama3.2 now selects that model also when a model better at tool calling, such as qwen3.6, is installed.

camel-pqc - the usage-count and PENDING_ROTATION key rotation signals now fire

Two of the three documented rotation signals of KeyRotationScheduler previously never triggered. Both now work, so a deployment that already configured them will start rotating keys where before nothing happened.

  • maxKeyUsage (and camel.component.pqc.key-rotation-max-usage) read usageCount, which was only advanced for the stateful signature algorithms (XMSS, XMSSMT, LMS, HSS). For ML-DSA, SLH-DSA, Falcon and the KEM keys the counter stayed at zero forever. A signing operation now advances it for every key, provided a keyLifecycleManager is configured and the exchange carries a CamelPQCKeyId header.

  • The scheduler’s default key filter accepted only ACTIVE keys, so a key an operator had marked PENDING_ROTATION was skipped before needsRotation() was consulted. The default filter now accepts ACTIVE and PENDING_ROTATION. A custom filter set with setKeyFilter is unaffected.

Review any configured maxKeyUsage threshold before upgrading: a long-running deployment may rotate on the first check once the counter starts advancing.

Recording the usage also costs a write. A signing endpoint with a keyLifecycleManager and a CamelPQCKeyId header now updates the key metadata in the backing store on every signature, where a non-stateful key previously wrote nothing. Against AWS Secrets Manager, Azure Key Vault or HashiCorp Vault that is a remote call per message. A failure to read or write that metadata does not fail the exchange for a non-stateful key - it is logged once per producer and the signature is returned - because losing a usage count only delays a rotation. Leave the CamelPQCKeyId header off the signing exchanges, or use maxKeyAge or nextRotationAt, if the write is not wanted.

The key encapsulation operations do not record usage, so maxKeyUsage still never triggers for a KEM key.

camel-pqc - a failed stateful signature no longer carries the signature headers

Signing with one of the stateful hash-based algorithms (LMS, HSS, XMSS, XMSSMT) consumes a one-time index inside the private key. That advanced key is now persisted through the keyLifecycleManager before the signature is placed on the message, instead of after.

Such an exchange already failed when the key state could not be stored, so what changes is the content of the failed message: it no longer carries CamelPQCSignature, nor the hybrid CamelPQCHybridSignature, CamelPQCClassicalSignature and CamelPQCPqcSignature headers. A route that caught the failure with onException(…​).handled(true), or routed it to a dead letter channel, could previously read the signature off the failed message and pass it on; it can no longer do so. That is the point of the change - a signature whose index advance was never recorded must not escape, because after a restart the key resumes at the previous index and can sign a second time with an index already used.

Signing a stateful key without a keyLifecycleManager, and signing any key without a CamelPQCKeyId header or with no stored metadata for the key, now logs a warning once per producer rather than being silently ignored. A non-stateful key without a keyLifecycleManager stays silent, as there is no maxKeyUsage threshold to miss. For a stateful key the warning names the index-reuse risk; for any other key it names the usageCount that will not advance.

camel-jooq - a message with no body fails

The jooq producer now fails with an InvalidPayloadException when the message body is null and the query option is not set. Before, operation NONE (the default) created a new, empty record of the entityType and stored it, so a message with no body inserted an empty row (or failed with a database constraint error). Operations EXECUTE and FETCH failed with a NoSuchMethodException for org.jooq.Query.<init>(). Send the record, or the org.jooq.Query, in the message body. The operation option also no longer lists CONSUME, which was never a valid value.

camel-jsonpath - easy predicates with <= and >= match the boundary value

An easy predicate (such as store.book.price <= 10 or $.store.book.price >= 10) now matches the boundary value. Before, price <= 10 ran as price < 10 and price >= 10 as price > 10, so a message with a price of exactly 10 was not matched, and a content-based router sent it to another branch. A route that worked around this (for example with price <= 10.01) can go back to the plain boundary. Also, an operator word inside a quoted value (title == 'Java in Action') is now part of the value, and the type-safe operators === and !== are passed to JsonPath as written (before, they ran as == and !=).

camel-jbang - camel get endpoint shows the scope of an endpoint

camel get endpoint replaces the REMOTE column with SCOPE, which tells three kinds of endpoint apart: remote (an external system, as before), internal (Camel plumbing that stays inside the integration, such as direct, seda or log, as the catalog marks its component internal) and local (neither, such as exec or bean). Scripts that read the REMOTE column of the table must read SCOPE instead. The --json output keeps remote and adds internal.

camel-pqc - the CamelPQCOperation header is validated, and two header types are corrected

A CamelPQCOperation header selecting an operation the endpoint holds no key material for is now rejected with an IllegalArgumentException naming the requested operation, the configured one, and what is missing. Before, an override crossing between the signature and the key encapsulation operations dereferenced a null and surfaced as a bare NullPointerException. Routes relying on such an override have to configure a separate endpoint for the other operation; the override itself keeps working within a family and for the operations that need no key material.

Two header declarations also carried the wrong javaType in the component metadata, which is visible to tooling and in the generated header table. CamelPQCOperation is org.apache.camel.component.pqc.PQCOperations (was String) and CamelPQCSecretKey is javax.crypto.SecretKey (was Boolean). The runtime values are unchanged - only the declared types were wrong.

camel-jbang - finding integrations by name or PID

All commands find running integrations the same way:

  • A PID given to a command must be a running Camel integration (a live process with a status file in ~/.camel). Before, any PID was used as is, so camel stop 1234 --kill killed whatever process had that PID.

  • The commands no longer find an integration whose Camel has stopped (phase 9 or later), such as the launcher process of an integration that runs in a child process. camel ps does not list it anymore.

  • The --timeout of camel cmd send, camel cmd receive, camel eval expression, camel transform message and the camel semantic commands also takes a duration such as 30s (a number is still millis).

camel-pqc - KeyLifecycleManager behaviour aligned across the implementations

Four differences between the KeyLifecycleManager implementations are resolved. All four are corrections, but each is observable.

  • updateKeyMetadata on the AWS Secrets Manager, Azure Key Vault and HashiCorp Vault managers no longer re-stores the key pair. It previously read the key back and wrote it again, creating a new version of the private key secret on every status change - with the default max_versions = 10 of a Vault KV v2 mount, ten metadata edits purged every historical version of the private key. It also meant revokeKey threw for a key whose private secret was already gone. The file-based manager already wrote metadata only.

  • FileBasedKeyLifecycleManager.listKeys() now scans the key directory instead of returning an in-process cache filled once at construction. A key file written after start-up - by another process, a restored backup or a sidecar - was previously invisible, which silently stopped KeyRotationScheduler from rotating it.

  • The deleteKeyState operation now fails with IllegalStateException when no keyLifecycleManager is configured, as every other key lifecycle operation already did. It previously reported success having deleted nothing.

  • exportKey with PEM and includePrivate = true produces a document that importKey can read back. The combined export carries both a PUBLIC KEY and a PRIVATE KEY block, and the parser stripped only one pair of armour lines, leaving the other in the Base64 input and failing with Illegal base64 character 2d. The export format itself is unchanged, so documents written by earlier versions are unaffected.

  • importKey resolves the KeyFactory with the BouncyCastle provider that serves the algorithm, as the file-based manager’s key loading already did. On JDK 24 and later the JDK itself implements ML-DSA and ML-KEM and is listed before BouncyCastle, so the provider-less lookup used until now returned a JDK key, and signing with it through the component’s BouncyCastle Signature failed with unknown private key passed to ML-DSA. A key imported through the AWS Secrets Manager, Azure Key Vault, HashiCorp Vault or in-memory manager is now a BouncyCastle key on every JDK.

PEM handling is also now explicitly UTF-8 rather than the platform default charset.

camel-jbang - exit codes, and errors on stderr

The Camel CLI has a documented set of exit codes (see Exit Codes and Output): 0 success, 1 failed, 2 usage error, 3 not found, 66 file not found, 70 internal error and 124 timed out. A script that checks the exit code of a command must take these changes into account:

  • A command that ends with an unexpected exception exits with 70 instead of 1, and prints ERROR:, the message and the stack trace on stderr. Add --verbose or set CAMEL_CLI_DEBUG=true to print the stack trace of other errors too.

  • Commands that exited with 255 now exit with 1, 2, 3, 66 or 124: camel bind, camel debug, camel dependency update, camel eval expression, camel infra log, camel transform message and camel update run. camel debug exits with the exit code of the debugged integration when it ends.

  • camel semantic exits with 124 instead of 4 when the integration does not reply in time.

  • camel generate schema exits with 2 for an empty argument, 3 when the class is not found, 1 when the schema cannot be generated and 70 for an unexpected error, instead of 4, 2, 3 and 4.

  • camel transform dataweave exits with 2 without --input or --expression, and with 66 when the input path does not exist or holds no .dwl files, instead of 1.

  • camel cmd sql exits with 124 instead of 1 when the query does not answer in time.

  • A source file of camel cmd route-diagram, camel cmd route-structure and camel cmd route-topology that does not exist exits with 66 instead of 1. The parts of a Java route the parser could not read are printed as a warning on stderr (also with --json, which lists them in unread as before).

  • A file or directory that does not exist exits with 66 instead of 1: a source file or --source-dir of camel run, the route file of camel explain and camel harden (checked before the LLM is contacted), and the pom.xml of camel dependency runtime.

  • A name that does not exist exits with 3 instead of 1: the Kamelet of camel init --from-kamelet, the service of camel infra run, the key of camel config get, the name given to camel doc, and the plugin of camel plugin delete.

  • camel cmd memory-leak and camel restart exit with 3 when no running integration, or more than one, matches the name. camel cmd memory-leak exits with 124 when the integration does not reply in time.

  • camel init without a file, or with --from-kamelet and a file that is not a .kamelet.yaml file, camel infra run with a malformed property, and camel kubernetes delete without --name exit with 2 instead of 1.

  • A command that runs its default sub-command (camel config, camel dependency, camel get, camel infra, camel plugin, camel top, camel update and camel version) exits with the exit code of that sub-command, instead of always 0.

  • The first use of the command of a Camel plugin (such as camel kubernetes) installs the plugin and runs the command, instead of asking to run the command again and exiting with 0.

Errors, warnings and tips are printed on stderr instead of stdout, so stdout only has the output of the command, such as JSON. If a script reads the error text of a command from stdout, read stderr instead. The list of plugins that are not installed, after camel and camel --help (no longer after the help of a command, such as camel run --help), is printed on stderr, and only in an interactive terminal.

camel-pqc - parameterSpec accepts MAYO and SNOVA, and the FIPS advisory no longer flags two aliases

parameterSpec can now select a named parameter set for MAYO and SNOVA. Both were rejected on the grounds that they have no name-addressable BouncyCastle parameter spec, which is not the case: MayoParameterSpec.fromName and SnovaParameterSpec.fromName both exist, and neither algorithm is stateful. The stateful hash-based signatures (XMSS, XMSSMT, LMS, HSS) are still rejected, as they genuinely have no such spec.

DILITHIUM and SPHINCSPLUS no longer produce the "not part of NIST FIPS 204/205" warning on hybrid endpoints. Both are aliases that resolve to ML-DSA and SLH-DSA, so the warning was recommending the algorithm already in use. FALCON, MAYO, SNOVA, XMSS and XMSSMT still warn.

The five default key material classes that generated their key pair with the provider’s default RNG (Falcon, MAYO, SNOVA, LMS, HSS) now use the shared SecureRandomHelper like the other sixteen. No key format or parameter set changes; only the randomness source is now uniform.

camel-docker - the connection and credential headers need the allowConnectionAndCredentialsFromHeaders option

The producer of the docker component took the Docker daemon to connect to, how to connect to it, and the registry credentials from the CamelDockerHost, CamelDockerPort, CamelDockerSocketEnabled, CamelDockerSecure, CamelDockerTlsVerify, CamelDockerCertPath, CamelDockerCmdExecFactory, CamelDockerUsername, CamelDockerPassword and CamelDockerServerAddress headers when they were set, over the options of the endpoint. These headers are now ignored unless the endpoint sets the new allowConnectionAndCredentialsFromHeaders=true option (disabled by default), and the producer uses the host, port, socket, secure, tlsVerify, certPath, cmdExecFactory, username, password and serverAddress options of the endpoint.

A route that relies on these headers, for example one that sends the registry username and password as headers to docker:auth or docker:imagepush, must set allowConnectionAndCredentialsFromHeaders=true on the endpoint, or on the component (camel.component.docker.allowConnectionAndCredentialsFromHeaders=true) to apply it to all its endpoints.

The other headers, such as CamelDockerContainerId, CamelDockerEmail and CamelDockerRequestTimeout, still take precedence over the options of the endpoint, as before. The events and stats consumers do not change: they always used the options of the endpoint.

camel-cli-connector - every action with a request id gets an answer

An action written to ~/.camel/<pid>-action-<requestId>.json now always gets an answer in ~/.camel/<pid>-output-<requestId>.json, so a tool no longer waits for its timeout:

  • an action without a result (such as a route stop) answers {"status":"success"},

  • an action that is unknown, throws an exception or reports an error answers {"status":"failed","error":"…​"}, with "reason":"not-found" when what it was asked to work on does not exist (such as No route matching: route9, and the new No processor matching: …​).

The WebSocket transport adds the same reason to its result frame. The shared ~/.camel/<pid>-action.json used by older Camel CLI versions is unchanged. A tool built on the file transport that relied on actions without a result writing nothing, or that waited for a timeout, sees these answers now. The Camel MCP route tools, for example, now report a route stop as done instead of a timeout.

camel-jbang - JSON output

With --json, stdout is exactly one JSON document, also when the command fails, so a script or an AI agent can parse it (see JSON output):

  • An error prints {"status":"error","code":3,"message":"…​"} on stdout, with the exit code of the command in code, and the ERROR: line on stderr as before. Before, stdout was empty or had text.

  • A list with nothing in it prints [] instead of nothing, such as camel ps, camel get …​ and camel top …​ when no integration is running, and camel catalog …​ when the filter matches nothing. The message about a filter that matches nothing is printed on stderr.

  • camel top route --json and camel top group --json print JSON instead of a table, with the load averages in load01, load05 and load15.

  • camel get route --error --json prints the routes with an error, and their lastError, as JSON instead of tables. camel get health --trace --json puts the stackTrace and metadata of a check in the JSON, instead of printing them as text after it.

  • camel get count --total --json and camel get count --fail --json print JSON instead of the comma separated counts.

  • camel infra get --json and camel infra stop --json print JSON instead of text, camel infra run --background --json prints the pid as JSON, and camel infra run --json only prints the properties of the service. camel infra get and camel infra stop only see services that are running, and exit with 3 when a name matches none.

  • camel overview --format=json only prints the JSON on stdout: the Saved message of --save and --ai, and the progress and the prompts of --ai, go to stderr, and --ai no longer prints the summary before the JSON. camel overview --apply-descriptions --format=json prints the changed files as JSON.

  • camel doctor exits with 1 when a check fails (Maven cannot resolve artifacts, or more than one installation of the Camel CLI is found). A missing container runtime or Ollama, and ports in use, are warnings, which do not fail it.

New --json options: camel doctor (the status OK, WARN or FAIL of each check), camel validate yaml, camel validate properties and camel validate source (valid, file and errors of each file, as the MCP tool camel_validate_source answers), camel doc (the documentation the MCP tool camel_catalog_doc gives), camel config get, camel config list, camel plugin get and camel plugin list.

camel-jbang - camel cmd actions report failures and timeouts

The commands that act on a running integration now report how the action went in their exit code (see Exit Codes and Output):

  • An action without a result waits until the integration confirms it, and exits with 1 when it fails, 3 when the route or processor does not exist, and 124 when the integration does not answer within --timeout (10 seconds by default; a number is millis, or use a duration such as 30s): camel cmd start-route, stop-route, suspend-route, resume-route, start-group, stop-group, enable-processor, disable-processor, gc, reload, reset-stats, logger --logging-level and camel trace --action=start/stop. Before, they wrote the action and exited with 0. An integration on an older Camel version takes the action without confirming it.

  • A command that waits for a result exits with 124 when the integration does not answer in time, and with 1 when it answers with an error, instead of a message and 1, or nothing and 0: such as camel cmd thread-dump, heap-histogram, heap-dump, browse, span, stub, sql, load, memory-leak, route-dump, route-structure, route-diagram, route-topology, camel get bean, source, route-controller, startup-recorder, camel top source, camel get error --diagram and camel get history --diagram. When the integration has no data to show (a thread dump, the route controller, top source), or OpenTelemetry is not enabled (camel cmd span), the command exits with 1 instead of 0.

  • A name or pid that matches no running integration exits with 3, instead of doing nothing and exiting with 0. Without a name (all integrations) and nothing running, the command exits with 0 and prints No running Camel integrations on stderr.

  • camel eval expression with a name or pid that matches no running integration exits with 3, instead of starting a new Camel to evaluate in: only without a name, or with --isolated, a new Camel evaluates the expression. An evaluation that fails exits with 1, and the Using existing running Camel integration line is printed on stderr.

  • camel cmd send exits with 1 when the exchange failed and 124 when the endpoint did not answer in time, also without a name (sending from a local Camel), which exited with 0 before. camel transform message exits with 1 when the transformation fails and 124 when it does not answer in time.

  • camel cmd load without --source, a header that is not in the key=value form (camel cmd send, camel eval expression, camel transform message), and an unknown --action (camel cmd receive, camel trace) exit with 2.

  • The --filter of camel cmd route-dump, camel cmd route-structure and camel get source takes several names separated by comma, as its description says, and a filter that matches no route exits with 3.

The commands that wait for the integration have a --timeout option, and the ones that return data have a --json option: camel cmd thread-dump, heap-histogram, heap-dump, browse, span, stub, sql, logger, route-dump, camel get bean, source, route-controller, startup-recorder, camel top source and camel trace (the status of tracing). camel get history --json prints one JSON array instead of one document per line.

camel-jbang - camel get, top and ps: not found, unreadable status, and the JSON of the listings

  • camel get …​, camel top …​ and camel ps with a name or pid that matches no running integration exit with 3, instead of printing nothing and exiting with 0. Without a name and with nothing running, they exit with 0 and print No running Camel integrations on stderr (and [] with --json).

  • An integration whose status file a listing cannot read, such as one of an older Camel version without a value the listing needs, is skipped with a WARN: on stderr, instead of failing the listing of every integration.

  • camel get health takes a name or pid, like the other listings.

  • camel get endpoint, consumer and producer --limit keep the first rows after sorting (they kept the first rows found), and camel get processor --limit counts routes, as its description says.

  • camel get activity shows how long ago an exchange happened (it showed decades), and camel dirty no longer keeps the files of another pid that starts with the same digits (pid 12 kept 123-status.json), nor removes the files of a process that is alive.

The JSON of the listings (--json) changes, so that a script can use the values without parsing text:

  • pid and the counters are numbers instead of text: total, failed, inflight, mean, min, max, last, delta and throughput. A value that is not there is null, instead of an empty or - text.

  • Values that combined several numbers are split: sinceLast (1s/2s/-) into sinceLastStarted, sinceLastCompleted and sinceLastFailed; the route of camel get context (3/4) into routesStarted and routesTotal; its reload into reloaded and reloadFailed; and in camel top context the load into load01, load05 and load15, heap into heapUsed, heapCommitted and heapMax (bytes), nonHeap into nonHeapUsed and nonHeapCommitted, gc into gcCount and gcTime (millis), and threads into threads and peakThreads. In camel get health, rl becomes readiness and liveness, rate becomes total, success and failure, and since becomes sinceLast, sinceStartSuccess and sinceStartFailure.

  • ready is a boolean (camel ps, camel get context), and camel ps has failed instead of fail.

  • Next to age (such as 5m3s), the listings have uptime in millis. Durations are millis: duration and elapsed of camel get blocked, inflight and internal-task, and time of camel get groovy, which lists its classes as an array.

  • The id of a route is routeId (it was route in camel get blocked, circuit-breaker, inflight and kafka), and the state is status (it was state in camel get circuit-breaker). camel get consumer has total instead of poll. Every row of camel get processor has the name of its integration.

camel-jbang - camel get route, processor, metric and health

  • camel get route --error --limit shows that number of routes with an error (it limited the routes first, and then showed the ones with an error among them), and --note shows the notes of the routes with an error too.

  • camel get processor --filter shows the matching processors with their route, and so with the name and pid of their integration (it showed the processors alone). --filter-mean leaves out the faster processors too, as its description says (it left out the faster routes only).

  • camel get metric shows the distribution summaries: an integration that has one was skipped with a warning. --filter takes a pattern such as timer*, as in its help (it showed nothing), and --custom leaves out the metrics of Camel in the default naming of Micrometer, such as camel.exchanges.total, not only in the classic naming (CamelExchangesTotal).

  • camel get health --level other than full or default exits with 2 (it showed every check, as full does).

camel-jbang - camel stop and camel restart wait for the integration

  • camel stop waits until the integration has stopped, up to --timeout (60 seconds by default), and exits with 124 when it has not stopped in time (use --kill to stop it at once). Before, it asked the integration to stop and exited with 0 at once; --no-wait does that. An integration without a pid file in ~/.camel is stopped with SIGTERM, which did nothing before.

  • camel stop and camel restart with a name or pid that matches no running integration exit with 3.

  • camel restart waits until the integration has stopped (terminating it when it does not stop within --timeout), and does not start it again while the old process is alive (exit 124). It waits until the new integration has started, and keeps its output in a log file in ~/.camel, which it prints (the log was removed when the command ended). A new integration that stops right away prints the end of its log and exits with 1; one that is still starting after --timeout exits with 124 and is left running.

camel-jbang - camel cmd browse of an endpoint that cannot be browsed

camel cmd browse --endpoint with an endpoint that the integration cannot browse, or that it does not have, exits with 3 (it printed an empty table and exited with 0).

camel-jbang - no command waits for a terminal that is not there

Without an interactive terminal, such as in a script, a CI job (the CI environment variable is set) or an AI agent:

  • --watch without --duration shows the output once, with a WARN: on stderr, instead of repeating until Enter is pressed. The new --duration option (such as --duration=30s) keeps watching for that long, and then exits with 0.

  • camel log, camel trace and camel cmd receive do not follow, unless --follow or --duration is given. camel infra log prints the end of the logs and exits, and has --follow and --duration too.

  • camel hawtio does not open a web browser, and prints the URL of the web console instead. --openUrl is now named --open-url (the old name still works).

  • camel debug, camel nano, camel shell, camel edit, camel tui (except with --record), camel ask without a question, camel run --prompt and camel get history --it exit with 2 and requires an interactive terminal on stderr. camel debug used to wait at 100% CPU.

--follow and --open-url without a value turn the option on. As boolean options that are on by default, they turned it off (use --follow=false to turn it off). In a terminal, the end of the input (Ctrl+D) stops a command that watches or follows, like Enter does.

camel-jbang - camel cmd route-diagram and camel cmd span

  • camel cmd route-diagram --highlight with node ids that no route has exits with 3 (it drew the diagram with nothing highlighted). Its errors are printed on stderr, and a graphical --theme in a terminal without graphics exits with 2.

  • camel cmd span --trace with a trace id that matches no trace exits with 3 (it printed a message and exited with 0), and an unknown --sort key of the traces exits with 2 (it sorted the newest first).

camel-jbang - camel run: startup in the background, and input errors

  • camel run --background waits up to 60 seconds for the integration to start (it was 20 seconds), and --background-wait takes how long, such as --background-wait=2m (true and false still work). An integration that is still starting after the wait exits with 124 and is left running; before, the command exited with 0.

  • The output of an integration in the background goes to ~/.camel/<pid>-run.log, whose path is printed. The log is kept while the integration runs (it was removed once the integration had started), and camel stop removes it. With --background-wait=false the output goes to the log as well, instead of a pipe that nobody read.

  • An integration that loads no routes exits with 1 and No routes found in: <files>, instead of running without doing anything, except with --source-dir, --empty or in dev mode, where files can be added later.

  • camel run and camel dev with --max-seconds, --max-messages or --max-idle-seconds exit at the limit also in dev mode, which stopped the routes and kept running. A limit from the configuration (camel.main.durationMax…​) still only stops the routes in dev mode.

  • A file that does not exist exits with 66 and File does not exist: <file> for every kind of file, before anything is printed. Files together with --source-dir, and --background with a runtime that does not support it, exit with 2.

  • Only a run of nothing but Citrus tests exits with 2 and says to run them with camel test run, before anything is printed: camel run test/orders.citrus.it.yaml, or camel run test/ of a folder that holds only Citrus tests. Before, it ran an integration with no routes. Citrus tests given with routes, such as by camel run *, are skipped with a line each (Skipping <file>: a Citrus test, not a route (run it with camel test run <file>)), and the routes run. A directory or --source-dir with a test folder runs as before. The camel_run tool of the AI tools does the same: it starts nothing for Citrus tests only, and its answer lists the tests it skipped.

camel-jbang - camel explain and camel harden

  • The LLM configuration, the progress and the prompts of --show-prompt are printed on stderr, so stdout has only the answer of the LLM (with a header per file), such as for camel explain route.yaml > explanation.md.

  • An unknown --format exits with 2 (it was ignored).

  • camel harden --api-type=openai prints the suggestions (it printed only the header): the OpenAI-compatible service answers in one go, also with --stream.

  • An API key of eight characters or fewer is not shown in the configuration (the last four characters of a key were).

camel-jbang - the AI tools report failures as errors

  • camel ask runs a camel command for the model with a timeout of 60 seconds, and gives it the exit code, stdout and stderr as JSON (it gave the output only, and the exit code only when there was no output). A command that runs until it is stopped is not run, and the model is told the form that returns, such as camel run --background.

  • The MCP tools of camel mcp and camel tui --mcp answer a failure with isError, instead of a normal answer that says what went wrong: camel_run of an integration that stops while it starts (status is failed), a restart that fails (camel_control), an action the integration answers with an error or does not answer (such as stopping a route that does not exist), and camel_runtime_ai_trace when the integration does not answer. The text of the error has what the answer had, such as the output of the integration.

  • camel_control restart in camel tui --mcp waits until the integration has started again, or failed to start (it answered at once), and camel_control stop-routes and start-routes answer with what was done instead of the answer of the integration.

camel-jbang - the errors of a streamed answer of an LLM

  • When the streamed answer of an LLM fails, as with camel explain, camel ask or camel overview --ai against Ollama, the error is printed on stderr: it was printed on stdout after the answer so far. A failure without a message of its own, such as a refused connection, has the message of its cause instead of null.

  • camel ask with an OpenAI-compatible server that gives the arguments of a tool call as a JSON object, instead of JSON text, calls the tool with these arguments (it called it without arguments).

camel-jbang - camel ask: the exit code and the output of a question

  • camel ask exits with 1 when the call of the LLM fails, such as with an error of the model or a refused connection: it exited with 0 without an answer.

  • camel ask with a question prints only the answer on stdout. The model it uses, the integration it asks about, the note about a small model, and the output of --show-tools and --show-stats go to stderr.

camel-jbang - an LLM given with --url is the only one used

camel ask, camel explain and camel overview --ai fail when the LLM given with --url does not answer: they went on to another provider, such as the Ollama of the host, or a hosted provider with an API key in the environment, and sent the prompts there. The AI panel of the Camel TUI does the same with the URL of its settings. Without --url the provider is detected as before.

camel-jbang - exit codes and errors of more commands

  • camel export does not clean the export directory when it is the current directory, or one that contains it, unless --clean-dir is given: --dir=./, --dir=$PWD and --dir=.. deleted the files of the project, as only --dir=. was recognized.

  • camel harden --url with an LLM that cannot be reached exits with 1, instead of sending the route to a local Ollama.

  • camel sbom with an unknown --sbom-format or --sbom-output-format exits with 2 (it exited with 0 without doing anything), and exits with 124 when Maven does not finish in time.

  • camel update run --upgradeTimeout applies (the output was read to the end before the wait), and exits with 124 when it is reached.

  • camel version list exits with 1 when the versions cannot be downloaded. camel version set --reset removes the Kamelets version too, and camel version get shows a configuration with only the Kamelets version.

  • camel validate yaml and camel validate properties with no file to validate exit with 66.

  • camel doc looks a name up in the catalog of Camel before it downloads the Kamelet catalog, so a component is documented also offline. camel catalog kamelet has --repos.

  • camel kubernetes logs exits with 3 when no pod with the label shows up, and with 1 when reading the log fails. camel kubernetes run prints the end of the Maven output when the build or deployment fails (it was hidden without --verbose), and exits with 124 when the pod is not running after 10 minutes.

  • --jvm-args keeps a quoted value with spaces as one argument.

camel-jbang - processes started by the CLI stop with it

  • When camel run of a Spring Boot or Quarkus project (and camel debug) ends, such as on Ctrl+C, the processes started by Maven stop too: the app could outlive Maven, as it runs in a JVM of its own. A process that does not stop within 30 seconds is killed (it was left running).

  • A plugin that cannot be loaded ends the command with exit code 1 and the error, as before, but no longer exits the JVM while the plugins load, which ended a program that embeds the CLI.

camel-jbang - camel overview and camel transform route

  • camel overview --format other than markdown or json exits with 2 (it printed the markdown).

  • camel transform route --format other than xml, yaml or java exits with 2 (it ran the routes and printed nothing), and a route that cannot be read, such as invalid YAML, exits with 1 and the error of the parser (it failed with an internal error, 70, and a stack trace).

camel-xml-io, camel-yaml-io, camel-java-io - route dumps keep options set to their default value

The XML, YAML and Java route dumps (camel transform route, camel validate normalize, camel.main.dumpRoutes, the developer console) now write these options whenever they are set, also with their documented default value: the binding options of the rest configuration, a rest and its verbs (such as bindingMode="off"), the options of a redelivery policy, of a resilience4jConfiguration and of a faultToleranceConfiguration, shutdownRoute / shutdownRunningTask of a route, and apiDocs of a rest verb. Left out, such an option took the value of the parent level when the dump was loaded (the rest configuration or the rest, the error handler, the global circuit breaker configuration, the CamelContext), so the route behaved differently, or, for apiDocs, a verb with apiDocs="true" under a rest with apiDocs="false" was left out of the OpenAPI document.

camel-yaml-dsl - a value under parameters: with an & is kept whole

In the YAML DSL, a value under parameters: that has an & is now kept whole in the endpoint uri. Before, the value was cut at the &, and the rest was read as another option. For example, with filterFile: "${file:name} contains 'R&D'" the route failed to start. A lenient component, such as a Kamelet, got only the text before the &, without an error.

Camel now puts such a value in RAW(…​) in the uri, as it does for a secret option. When the value has )&, Camel uses RAW{…​}. The value is then taken as it is written, so a ` in it stays a `. A value without an & is read as before, so a ` in it is a space (such as in a cron expression written with `).

This applies to the endpoints of from, to, toD, wireTap, poll and switch, and to the kamelet EIP. Camel does not change a value that starts with # (a bean reference), a value in RAW(…​) or RAW{…​} already, or a path parameter, such as directoryName when the uri is only file: an & does no harm in the path. A route dump, such as camel validate normalize, writes such a value in RAW.

Camel sees the & only when it is written in the YAML as the value of an option. When the & comes from a property placeholder, or is in a value of a map option (such as additionalProperties), write the value in RAW yourself, such as RAW({{name}}).

camel-jbang - camel get bean, get route-controller and top source

  • camel get bean --dsl shows the configuration of each property next to that property: it paired them by position, which showed the configuration of another property, or failed with an internal error (70) with --nulls=false. An unknown --scope exits with 2 (it showed every bean).

  • camel get route-controller shows back-off times and attempts that do not fit in an int (it showed other numbers), and no longer shows Starting Routes: false for the default route controller, which does not tell.

  • camel top source --json has the counters total, mean, min, max and last as numbers instead of text, as the other listings.

camel-jbang - a boolean option given without a value is on

A boolean option that is on by default is now on when it is given without a value, like any other boolean option: camel export --maven-wrapper exports the Maven wrapper, camel config set --global key=value writes the global configuration, camel cmd receive --show-body shows the body, and camel kubernetes run --dev --cleanup removes the deployment when it stops. Such a flag turned the option off before (picocli sets a flag to the opposite of its default). Turn the option off with =false, such as --maven-wrapper=false, which works as before.

camel-jbang - an unknown sort key is a usage error

The commands that sort their rows by --sort, such as camel ps, camel get route, camel cmd thread-dump or camel catalog component, exit with 2 and list their keys when the key is unknown: camel ps --sort=nmae printed the integrations unsorted. A row without the value of the key now comes first, instead of failing the command.

camel-jbang - camel catalog kamelet

camel catalog kamelet --filter no longer fails with an internal error (70) when a Kamelet has no description. An unknown --type exits with 2 (it showed nothing), a --filter that matches nothing says so on stderr, and --json prints the Kamelets as JSON, as the other camel catalog commands do.

camel-jbang - camel init does not overwrite a file

camel init exits with 1 when the file it creates exists already, and leaves the file as it is (it overwrote the file before). Use --force to overwrite it. The same holds for the files camel init downloads from GitHub or a gist, of which it writes none when one of them exists. When the template is picked in the terminal (camel init without a file), the command asks there whether to overwrite the file.

camel-jbang - camel get history

  • camel get history --diagram and --it exit with 2 when several integrations have a message history, and ask for the name or pid of one (they printed a message on stdout and exited with 1). The errors of --diagram are printed on stderr, and a graphical --theme in a terminal without graphics exits with 2.

  • camel get history --json --mask masks the secrets of the endpoints in the JSON too, as in the table.

camel-jbang - exit codes of config set, dependency list, jolokia and cmd memory-leak

  • camel jolokia with a name or pid that matches no running integration, or more than one, exits with 3, as camel hawtio does (it exited with 1, and printed the second case on stdout).

  • camel config set without a key (=value stored a value with an empty key) or without a value exits with 2, and prints the error on stderr.

  • camel dependency list with an unknown --output exits with 2.

  • camel cmd memory-leak --min-size takes a size in bytes such as 10B, and a value that is not a size exits with 2 (both turned the filter off).

  • camel cmd send --reply to a route that fails with an exception without a message, such as a NullPointerException, prints the failed exchange and exits with 1, instead of failing with a ClassCastException.

  • camel get source of an integration with routes without a source location, such as routes added by a RouteBuilder, no longer fails.

  • Versions with four parts, such as the Quarkus platform version 3.27.3.1, compare as versions in the version checks of the CLI (3.27.3.1 came after 3.27.4), and a -SNAPSHOT always comes before its release, as in Maven (4.24.10-SNAPSHOT came after 4.24.10).

camel-jbang - camel dependency copy, update run and get error

  • camel dependency copy deletes the JAR files an earlier copy left in --output-directory, and keeps its other files: it deleted the whole directory (with --output-directory=. the project). It exits with 1 when a dependency cannot be resolved or copied (it printed the error and exited with 0), and with 2 when --output-directory is a file.

  • camel update run --runtime=quarkus --download=false without --quarkusMavenPluginVersion exits with 2, and a version without upgrade recipes exits with 1: both failed with an internal error (70).

  • camel get error --last shows the newest error of all the integrations (it showed the newest error of the integration with the lowest pid). An --ago that is not a duration, and a --handled that is not true or false, exit with 2: the first showed every error, the second none. The errors of --diagram are printed on stderr, and a graphical --theme in a terminal without graphics exits with 2.

camel-jbang - camel version list

  • camel version list prints the Last updated note on stderr, so stdout has the table only.

  • A --date-format that is not a date pattern, and a --from-date or --to-date that is not a date in the yyyy-MM-dd format (such as 2025-01-31), exit with 2: the first showed the dates unformatted, and the others were ignored.

camel-jbang - the shared action file is deprecated for plugin commands

CamelCommand.getActionFile and CamelCommand.getOutputFile, the action and output files that all the clients of an integration share, are deprecated. A plugin command sends an action with ActionClient instead, which gives each action a file of its own, so that two clients cannot overwrite each other’s action or answer, and uses the shared files only for an integration before Camel 4.21. camel debug sends its commands this way too.

camel-jbang - camel log, trace, cmd receive, cmd send, transform message and cmd memory-leak

  • camel log, camel trace --action=dump and camel cmd receive --action=dump exit with 3 when the name or pid matches no running integration, and they do not follow (in a script or an AI agent, or with --follow=false): they printed nothing and exited with 0.

  • camel cmd receive --action=dump without a name shows the messages of the running integrations (it showed nothing). With --output=json it prints each received message once, as one JSON document per line with its exchangeId: it printed the batch of messages a line of the received file holds once for each message in it. --since, --grep and --mask apply to the JSON too.

  • camel trace and camel cmd receive exit with 2 for an unknown --sort key, as the other listings do.

  • camel trace --tail shows the end of the last exchange as completed, not as returning from its route.

  • camel cmd send and camel transform message print the error and exit with 1 when the integration fails the action itself (they failed with an internal error, 70).

  • camel cmd memory-leak exits with 1 when the integration fails the action, such as when it cannot record, or has no jfr-memory-leak dev console: it printed nothing and exited with 0. --status shows the progress of a recording (it said the recording started), and after a recording that its results can be queried (it said that no samples were captured).

camel-jbang - camel cmd eval with quotes and slashes

camel cmd eval, and the tools that evaluate an expression in a running integration (camel ask, and camel_eval_expression of camel mcp and camel tui --mcp with an integration selected), send the expression and the body as they are. They escaped them, which the integration does not undo: an expression with a double quote failed, such as ${body} == "hello", and a slash (as in every XPath) or a quote in the body became part of the text.

camel-jbang - the runtime tools of camel ask and the MCP servers

  • send_message of camel ask, and tui_send_message of camel tui --mcp, send the headers given as key=value lines, as their description says: the integration got the lines as one text, which it could not read as headers, and failed the message. A line that is not key=value is an error of the tool.

  • get_circuit_breakers has the calls of the fallback, the calls that timed out and the calls a full bulkhead rejected, as camel get circuit-breaker shows them.

  • get_route_analysis, get_eip_stats and detect_config_drift have the answers of the integration as JSON, instead of as JSON text inside their JSON.

camel-jbang-mcp - migration and dependency tools

The camel_migration_compatibility tool no longer takes the camelComponents argument, which it never used. It now takes the Java requirement from the JDK versions of the target release in the catalog (the same data as camel_version_list): Camel 4.19 and later no longer report Java 17 as a blocker, and javaCompatibility also lists the supportedVersions. A target version newer than every release in the catalog has no Java requirement: the tool says so in warnings instead of taking the one of the newest release.

camel_migration_recipes returns commands that run: the Spring Boot command activates org.apache.camel.upgrade.CamelSpringBootMigrationRecipe, the recipes artifact is the <major>.<minor>.0 release of the target version, the OpenRewrite plugin is no longer pinned to a version, and the Quarkus command runs the quarkus-maven-plugin at the Quarkus platform version of the new optional quarkusPlatformVersion argument (or the latest one), with -Drewrite=true for a full run.

In camel_dependency_check and camel_dependency_security_audit the dependencies are the declared ones: a component only in dependencyManagement or in the dependencies of a plugin no longer counts, and Spring Boot starters and Camel Quarkus extensions count as the component they provide. The version status of camel_dependency_check compares the project with the latest Camel release, added as latestVersion, instead of the catalog version of the server. The security audit no longer audits the server’s catalog version when the pom.xml sets no Camel version: it audits the camelVersion argument, else lists every advisory of the declared components, and says so in warnings. Its reachable is null when no routes are given, advisories without components are returned as uncheckedAdvisories, and clean is true only when none of them apply.

camel-jbang-mcp - camel_openapi_mock_guidance is removed

The camel_openapi_mock_guidance MCP tool is removed. Call camel_openapi_scaffold with missingOperation=mock instead: its answer explains the mode and lists the camel-mock/ files, one per path of the spec. The scaffold and camel_openapi_validate now follow the rules of camel-rest-openapi: an operation without operationId is routed to direct:GENOPID_<METHOD><path> with / replaced by . and the braces of path parameters by (direct:GENOPID_GET.pets._petId for GET /pets/{petId}), and a mock file has the request path as its name, without the HTTP method (camel-mock/pets/42.json for GET /pets/42). The security scheme diagnostics of camel_openapi_validate are now all warnings with a SECURITY_<type> code (SECURITY_APIKEY, SECURITY_HTTP, …​), as camel-rest-openapi enforces none of them, and OPENAPI_31 is reported as information instead of a warning.

camel-jpa, camel-hazelcast - an exchange marked rollback only is not consumed

When a route marks the exchange rollback only (for example with markRollbackOnly() or markRollbackOnlyLast()) there is no exception on the exchange, and these consumers treated it as a success. The JPA consumer deleted the entity (or called its @Consumed method) and committed the poll, and the transacted (transacted=true) Hazelcast SEDA consumer committed its transaction, so the message was removed from the queue. Now a rollback only exchange is not consumed: the JPA consumer leaves the entity in the table, so the next poll consumes it again, as long as it still matches the query. With transacted=false the other entities of the poll are still consumed (without logging a WARN), and so is what a @PreConsumed method changed before routing; with transacted=true the whole poll is rolled back, as for a failed exchange. The Hazelcast SEDA consumer rolls back its transaction, so the message stays in the queue.

A message that the route marks rollback only every time was removed before, and is now consumed again at every poll (JPA), or taken from the queue again at once, without a delay (transacted Hazelcast SEDA, as for a failed exchange). Handle such a message in the route instead, for example by moving it aside, rather than marking it rollback only every time.

camel-jpa - a consumer query that selects values delivers them

A JPA consumer whose query selects values that are not entities, such as a column (select x.address from Customer x), now delivers them with the default consumeLockEntity=true. Before, the lock of each value failed and the value was skipped, so the route received no message, with only a DEBUG log. Such a value cannot be deleted or marked with @Consumed, so each poll delivers the same values again, whatever consumeDelete is: a route that received nothing now gets the whole result on each poll. Make the query exclude the rows already handled, or select the entity instead. With the default consumeDelete=true a WARN is logged once; set consumeDelete=false to avoid it.

camel-a2a - caller profile and task owner

  • When a security requirement of the agent card combines several schemes, such as an API key and an OAuth token, the caller profile (CamelA2AUserProfile) is now the one of the token, whatever the order of the schemes. Before, it was the profile of the first scheme, so a requirement that listed the API key first only gave {"scheme": "apiKey"} to the route, and the tasks belonged to the API key.

  • A token without a sub claim is now identified by the username that a token introspection response gives, else by its client_id or azp claim (such as a client credentials token), so the tasks of different users and clients are kept apart. A valid token that has none of these claims is now rejected with HTTP 401.

  • The CamelOAuthTokenValidationResult exchange property is now set on the exchange that the route processes, as documented. Before, it was only set on the incoming HTTP exchange.

camel-hazelcast - the transacted SEDA consumer waits for an exchange that continues on another thread

A Hazelcast transaction can only be committed or rolled back by the thread that began it. When the route of a transacted (transacted=true) Hazelcast SEDA consumer continued on another thread (an asynchronous producer, or threads()), the consumer tried to commit or roll back the transaction on that thread, which failed with Transaction cannot span multiple threads! (logged as a WARN and ignored). The message was neither removed nor put back: a failed message was not taken again, and the messages, also the processed ones, were put back into the queue when the application left the Hazelcast cluster, so they were consumed again. Now the consumer thread waits for the exchange to complete and ends the transaction itself, so it takes the next message only then; use concurrentConsumers to process several messages at the same time. The exchange must complete within the Hazelcast transaction timeout (2 minutes by default, TransactionOptions): when it takes longer, the commit fails with TransactionTimedOutException, which is logged at ERROR, the transaction is rolled back and the message is consumed again, also when the route succeeded.

camel-hazelcast - the reliable topic consumer goes on after it lost messages

A hazelcast-topic consumer with reliable=true that fell behind the ringbuffer of the reliable topic (messages overwritten, or expired by its time to live, before the consumer read them) stopped receiving messages for good, while the route stayed started: Hazelcast removed its listener. The consumer now skips the lost messages, logs a WARN with their number, and goes on with the oldest message still in the ringbuffer. When Hazelcast cancels the listener for another reason, such as a destroyed topic, the consumer reports it to its exception handler (logged at WARN by default); restart the route to receive messages again. A listener cancelled because the Hazelcast instance or the CamelContext is shutting down is only logged at DEBUG.

camel-sql, camel-mybatis, camel-jooq - an exchange marked rollback only last is not consumed either

The SQL, MyBatis and jOOQ consumers already left a message unconsumed when the route marked the exchange rollback only with markRollbackOnly(). They now leave it unconsumed when the route marks it rollback only last with markRollbackOnlyLast() too, as the JPA and Hazelcast SEDA consumers do: the SQL consumer runs its onConsumeFailed statement instead of onConsume (and no statement when onConsumeFailed is not set), the MyBatis consumer does not run its onConsume statement, and the jOOQ consumer does not delete the entity, so the next poll reads the row again. With transacted=true the SQL and MyBatis consumers break out of the batch and roll back. markRollbackOnlyLast() reaches the consumer only when no transacted() EIP has consumed the flag, so the consumer’s own transaction is the last one.

camel-sql - sql and sql-stored with batch=true: the update count and a single Map body

With batch=true the CamelSqlUpdateCount (sql) and CamelSqlStoredUpdateCount (sql-stored) headers no longer add the negative values a JDBC driver returns for an item whose update count it does not know (Statement.SUCCESS_NO_INFO, which HSQLDB returns for stored procedure calls). Such a batch reported a negative count before, for example -4 for two items; it now reports the sum of the known counts, which can be 0.

A single Map body is now one batch item, and so is a single Map in the CamelSqlParameters header with useMessageBodyForSql=true (sql) or the CamelSqlStoredParameters header with useMessageBodyForTemplate=true (sql-stored). Before, its entries were used as the batch items and the call failed (sql: no value found for the named parameters, sql-stored: an SQLException).

camel-jta - an exchange marked rollback only last rolls back its transaction

In a transacted() route that uses a JTA transaction manager, markRollbackOnlyLast() now rolls back the transaction of that transacted() block, as it already did with the camel-spring transaction error handler. Before, the transaction was committed, and the marker was then cleared, so nothing showed that the rollback was skipped. The marker is still cleared afterwards, so an outer transacted() block does not see it.

If the transacted() block in which the exchange is marked rollback only last joins the transaction of an outer block (for example a PROPAGATION_MANDATORY block inside a PROPAGATION_REQUIRED block), that shared JTA transaction is now marked rollback only, so the commit of the outer block fails (in Camel Quarkus with CamelException: Unable to commit transaction) where it used to commit, as with camel-spring (UnexpectedRollbackException). To roll back only the inner block, use PROPAGATION_REQUIRES_NEW for it.

camel-keycloak - the CamelKeycloakOperation header needs the allowOperationHeader option

The CamelKeycloakOperation header took the place of the operation of the keycloak endpoint, so a message could make the endpoint run another operation than the one it is configured with. The header is now ignored unless the endpoint sets the new allowOperationHeader=true option (disabled by default), and the endpoint runs its configured operation. A route that picks the operation with the header must set allowOperationHeader=true on the endpoint, or on the component (camel.component.keycloak.allowOperationHeader=true) for all its endpoints.

An endpoint without an operation, which took the operation from the header, now fails with an IllegalArgumentException that asks for the operation, unless it sets allowOperationHeader=true.

camel-thrift - SSL/TLS settings of the consumer, and errors sent to the client

  • A consumer configured with negotiationType=SSL and the default synchronous=false now fails to start. It used to start a server without SSL/TLS. SSL/TLS requires synchronous=true, as on the producer.

  • The SASL negotiation type, which is not implemented, now makes the producer and the consumer fail to start. They used to behave as with PLAINTEXT.

  • The SSL/TLS consumer now creates its server socket from the SSL context of sslParameters, so all of its settings apply, including the trust managers and the server parameters. For example, serverParameters.clientAuthentication=REQUIRE now requires a client certificate. Before, the consumer only used the key store, the protocol and the cipher suites, and never asked for a client certificate. The key managers are required, and as in the other components the key is read with their keyPassword: set it when the key is protected by a password. Before, the consumer read the key with the password of the key store.

  • A new muteException consumer option, enabled by default, sends a generic TApplicationException of type INTERNAL_ERROR ("Internal error processing <method>") to the client instead of an exception that the Thrift method does not declare. Before, the asynchronous consumer sent the message of the exception, and the synchronous consumer sent a TApplicationException as it was, such as one returned by another Thrift service that the route called. An exception declared in the IDL is still sent as it is. Set muteException=false to send the exception as before.

camel-mina - sync=false is one-way, and the reply of a textline consumer

A consumer with sync=false no longer writes the body the route ends with back to the client. Before, it wrote it back (over TCP and UDP), so a client that only sends got its own message echoed. Use sync=true (the default) when the client expects a reply.

A producer with sync=false now leaves the body and headers of an InOut exchange as they were. Before, the exchange ended with an empty message.

A consumer with textline=true now converts the reply to a String before writing it, as the producer does with the request. Before, a byte[] or a stream reply was written as its toString().

A consumer with disconnectOnNoReply=false now keeps the session open when the route ends with no body, and logs it at noReplyLogLevel (WARN by default). Before, the consumer always closed the session and logged at DEBUG.

camel-thrift - the reply of the consumer must convert to the return type of the method

For a method with a return type, the consumer now fails the call with a TApplicationException when the route leaves no body (type MISSING_RESULT), or a body that does not convert to the return type (type INTERNAL_ERROR). Before, the synchronous consumer sent 0 (or false) for a method that returns a number (or a bool) and the route left no body, and the asynchronous consumer sent a null result, or no response at all for a method that returns a number. The body is still ignored for a void method.

camel-netty - errors sent to the client

A new muteException consumer option, enabled by default, makes a consumer with sync=true write a generic exception (java.lang.Exception with the message Exchange processing failed and no stack trace) back to the client when the exchange failed, instead of the exception of the route. With textline=true the client now receives the line java.lang.Exception: Exchange processing failed, where it used to receive the class name and the message of the exception of the route. With an encoder that accepts any object, the serialised exception no longer carries the class, message, cause or stack trace of the exception of the route. Set muteException=false to write the exception as before. transferExchange=true is not affected.

camel-netty - replies with the default codec, useByteBuf and sync=false

With the default codec (no textline, no custom encoders or decoders) a consumer with sync=true now converts a reply that is not a String (or a ByteBuf) to a String before writing it, as with textline=true. Before, such a reply, including the exception of a failed exchange, could not be written and the client got no reply. A failed exchange now replies java.lang.Exception: Exchange processing failed (see muteException above). When the reply cannot be written, the consumer closes the TCP channel instead of leaving the client waiting.

With useByteBuf=true a body that cannot be converted to a ByteBuf (such as a File, a Map or a POJO) now fails the exchange with an InvalidPayloadException, where nothing was sent before. A consumer with useByteBuf=true now also converts a reply set as the out message to a ByteBuf. NettyPayloadHelper.getIn and getOut declare InvalidPayloadException.

With sync=false the producer now ignores a message the remote server sends back. Before, that reply was set on the exchange after it had already continued routing. The producer now also leaves the body and headers of an InOut exchange as they were. Before, the exchange continued with an empty message. This also applies to netty-http.

camel-netty - a reply that cannot be written, and allowSerializedHeaders on the reply

When writing the reply fails (for example the encoder cannot encode it), the consumer now closes the channel (not for UDP), so the client does not wait for a reply that never comes. Before, the failure was only logged.

With transferExchange=true and allowSerializedHeaders=true, the consumer now also includes serializable header, property and variable values in a reply set as the out message, as it already did for any other reply. The decoder of the other side must accept their classes.

With transferExchange=true, the producer and the consumer no longer transfer the headers and exchange properties that hold Netty transport objects of their own side: CamelNettyRemoteAddress, CamelNettyLocalAddress, CamelNettySSLSession, CamelNettyChannelHandlerContext, CamelNettyMessageEvent and CamelNettyChannel. Before, with allowSerializedHeaders=true, the socket address headers (java.net.InetSocketAddress) were transferred, and the default deserialization filter of ObjectDecoder, which rejects java.net classes, made the other side fail to decode the message unless the route removed them.

camel-netty - with transferExchange the consumer keeps its own connection headers

With transferExchange=true the connection headers CamelNettyRemoteAddress, CamelNettyLocalAddress, CamelNettyChannelHandlerContext, CamelNettySSLSession and the CamelNettySSLClientCert* headers (and the CamelNettyRemoteAddress and CamelNettyChannel exchange properties) are now always the ones of the receiving side: the connection it accepted is the source of truth. Before, the headers of the transferred exchange replaced them. Such a header in the transferred exchange is no longer used, also when the receiving side has no value for it (for example the CamelNettySSLClientCert* headers on a connection without a client certificate). This also applies to the reply the producer receives. Send any such value of the sender under a header name of your own.

camel-netty - transferExchange requires explicit endpoint configuration

NettyPayloadHelper now checks transferExchange=true on the endpoint configuration before reconstructing exchange data from a DefaultExchangeHolder payload.

Before, a receiver with an object decoder silently reconstructed the exchange even when transferExchange=false. Now, when transferExchange is disabled (the default), the incoming DefaultExchangeHolder is left as an opaque body (both on the consumer side and for the producer’s reply via setOut).

In addition, the legacy two-argument methods NettyPayloadHelper.setIn(Exchange, Object) and setOut(Exchange, Object) are deprecated and no longer unmarshal DefaultExchangeHolder payloads. Callers should migrate to the three-argument overloads passing the NettyEndpoint.

camel-netty-http - a response that cannot be built

When no HTTP response can be built from the reply of the route (for example the body cannot be converted), the consumer now answers with HTTP Status 500 and closes the connection. The body is empty, or the stack trace of the exception with muteException=false. Before, no response was sent and the client waited until it timed out.

A List reply is now written as its toString(), as a Map or a POJO already was. Before, no response was sent.

camel-odata - the body of CREATE and UPDATE, and a response that is not a JSON object

CREATE and UPDATE send JSON text given as a byte array, a stream or a file as it is, as they do for a String. Before, a byte array was sent as a JSON array of numbers, and a stream or a file failed.

CREATE and UPDATE now fail with an IllegalArgumentException when the body is null or empty. Before, they sent a POST or PATCH request with no content. Set the body to {} to send an empty JSON object.

A response that is a JSON array or a plain value is now the body as it is parsed (a List, a number, a String or a boolean). Before, the producer failed with Expected an OData JSON object response.

camel-telegram - webhook requests are verified with a secret token

In webhook mode, Camel now registers the webhook with a secret token, which Telegram sends in the X-Telegram-Bot-Api-Secret-Token header of every webhook request, and rejects a webhook request that does not carry the token with HTTP 403. The token is the value of the new webhookSecretToken option or, when that option is not set, a value derived from the authorization token. A client that calls the webhook endpoint directly, for example in a test, must send the header too.

When Camel registers the webhook at startup (webhookAutoRegister=true, the default), nothing needs to change: each upgraded instance registers the webhook with the token, and the instances that are not upgraded yet ignore the header.

When the webhook is registered in another way, through the WebhookRoutePolicy or outside Camel, the registration made with the previous version has no secret token, and the upgraded instances reject every update until the webhook is registered again with it. Register the webhook again first, then upgrade the instances that receive the updates: instances of the previous version ignore the header. With the WebhookRoutePolicy, run the registration with the new version. Outside Camel, set webhookSecretToken, and pass the same value as the secret_token parameter of setWebhook.

The default webhook path is a hash of the endpoint URI, so adding an option to the URI, such as webhookSecretToken, changes the path. When the webhook is not registered by Camel at startup, set webhookPath to keep the path that is registered.

To restore the previous behaviour, set webhookSecretTokenEnabled=false: the webhook is then registered without a secret token, and webhook requests are not verified.

camel-paho-mqtt5 - a failed message with manualAcksEnabled is delivered again or acknowledged

With manualAcksEnabled=true, a message whose exchange failed was left unacknowledged. For a QoS 1 message the broker never sent it again during the connection, and after receiveMaximum such messages it sent the consumer nothing more (the client completes the QoS 2 flow on its own, so QoS 2 messages did not stop the consumer). Now, for a QoS 1 message on a session that the broker keeps after the disconnect (cleanStart=false and sessionExpiryInterval greater than 0), the consumer connects again after a failed exchange (after one second, doubled for each failed exchange in a row up to maxReconnectDelay, and back to one second after any exchange that succeeds; logged at WARN), and the broker sends the message again. In all other cases (a clean session, which is the default, a QoS 2 message, or cleanStart=false without sessionExpiryInterval, where the session ends with the connection) the broker never sends the message again, so the failed message is acknowledged and the failure is left to the Camel error handler. A consumer that uses a client set with the client option leaves a failed QoS 1 message on a session that the broker keeps unacknowledged, as before; as the consumer does not connect that client, set the cleanStart and sessionExpiryInterval of the endpoint to match its connection options.

camel-iggy - messageExpiry is sent to Iggy in microseconds

The messageExpiry option is in seconds, but its value was passed unchanged to Iggy, which reads the message expiry of a topic in microseconds, so a topic created by Camel (autoCreateTopic=true) expired its messages a million times too early (messageExpiry=3600 gave 3.6 ms instead of one hour). The value is now converted to microseconds. Topics created before keep their expiry: change it on the Iggy server. If you set messageExpiry in microseconds to work around this, set it in seconds now, or newly created topics keep their messages a million times longer than intended.

camel-iggy - binary payloads, and the body of the consumer

The iggy producer now sends a byte[], InputStream, ByteBuffer or file body as its bytes. Previously it converted it to text first, which corrupted a payload that is not valid UTF-8. Other bodies are still sent as UTF-8 text. A null body now fails with an InvalidPayloadException, a List body with a null element fails with a clear message, and an empty List body sends nothing (all three failed inside the Iggy client before).

The iggy consumer now sets the payload as a byte[] body. Previously it was a String decoded with the default charset of the JVM, which corrupted binary payloads. Camel converts the byte[] to a String where a String is needed, with the charset of the exchange (CamelCharsetName) if set, but code that reads the body with getBody() and expects a String must now use getBody(String.class) or add convertBodyTo(String.class) to the route.

camel-jira - the newIssues consumer reads every new issue

A poll of the newIssues consumer read only the newest maxResults (default 50) of the issues created since the previous poll, and the older ones were never consumed. A poll now reads every page of the search and can route more than maxResults issues; maxResults is the page size of that search (at most 50). There is no upper bound, so the first poll after a long outage or a large import can send many search requests and route many issues in one batch.

On Jira Cloud the Jira consumers now read the pages of a search with the next page token, as Jira Cloud cannot page by start index: with maxResults above 50, a search with more than 50 results failed on the second page with UnsupportedOperationException.

camel-langchain4j-ingest - a body that is not text fails

In the default text modality, the langchain4j-ingest producer now ingests the text of a LangChain4j TextSegment or Document body, and fails the exchange with an InvalidPayloadException for a body that is not text, bytes, a stream, a Reader or a file, such as a Map or a POJO. Before, such a body was ingested as its toString(), for example TextSegment { text = …​ }. Convert the body to text before the endpoint.

camel-netty-http - an ambiguous request path is rejected when a security constraint is configured

A netty-http consumer configured with a securityConstraint now rejects, with HTTP 400, a request whose path contains . or .. segments (including their percent-encoded %2e form). Such a path is ambiguous: the security constraint, the consumer dispatch and the route may each interpret it differently, so with matchOnUriPrefix=true and a specific inclusion such as /admin/*, a request like /foo/../admin/x (or its encoded /foo/%2e%2e/admin/x form) could be matched against the inclusions and exclusions in a different form than the one the route handles. Rejecting it up front keeps the constraint decision and the routed path consistent. A well-behaved HTTP client normalizes dot-segments before sending, so normal traffic is unaffected.

For the same reason, such a consumer also rejects with HTTP 400 a request whose path contains an empty segment (//), a ; path parameter (also as %3b) or a \ (also as %5c). A backend the route forwards the request to may collapse the empty segment, strip the path parameter or treat the backslash as a /, and so act on a different path than the one the constraint was checked against. A route that needs matrix parameters (;) must not configure a securityConstraint on the same consumer.

This applies only when a securityConstraint is configured; a consumer without one is unchanged, and continues to receive the request path as sent.

camel-hashicorp-vault - properties without engine use the secret engine

A hashicorp: property without an engine, such as {{hashicorp:mysecret}} or {{hashicorp:mysecret#field}}, now reads the secret from the secret engine. Before, it read from the engine of the hashicorp: property resolved before it (or from a null engine), and {{hashicorp:mysecret#field}} always resolved to no value. Properties with an engine, such as {{hashicorp:secret:mysecret#field}}, are not affected.

A hashicorp: property without an engine and with a version, such as {{hashicorp:mysecret@2}}, now reads version 2 of the secret mysecret in the secret engine. Before, the @2 was kept as part of the secret name.

A hashicorp: property with a field of a secret that does not exist, such as {{hashicorp:secret:missing#field}}, now resolves to no value, as it already did without a field, so its default value or an optional placeholder ({{?hashicorp:secret:missing#field}}) applies. Before, without a default value, it failed with a NullPointerException.

camel-jetty, camel-servlet - mapHttpMessageBody=false leaves the message body null

With mapHttpMessageBody=false on a jetty or servlet consumer, the HTTP request body is not read and the message body is null, also when the route accesses it. Before, the body was not read up front, but it was still read from the request when the route first accessed it. As before, no attachments, Java serialized objects or form fields are mapped.

A route that needs the request body must keep mapHttpMessageBody=true (the default). A route that sets no reply body now replies with an empty body, instead of with the request body.

camel-aws2-ddb - aws2-ddbstream reads every shard to its end

The aws2-ddbstream consumer now reads a closed shard to its end before it reads its child shards, and uses streamIteratorType only for its start position. Before, records left in a closed shard could be skipped, and after a shard rotation the consumer started again from streamIteratorType: with FROM_START it routed records of the stream again, and with FROM_LATEST it skipped records already written to the new shards.

camel-beanio - BeanIOSplitter decodes files and streams with the charset of the exchange or its encoding

BeanIOSplitter read a file body with the default charset of the JVM, and a stream or byte array body with the charset of the exchange or the default charset of the JVM; its encoding option was not used. A file consumed with charset=ISO-8859-1, or a splitter with setEncoding(StandardCharsets.ISO_8859_1), had every non-ASCII character replaced. The splitter now decodes a file, stream or byte array body with the charset of the exchange (the CamelCharsetName header or exchange property, as set by the charset option of the file consumer), else with its encoding (default: the default charset of the JVM). A String or Reader body is read as before, and other text (such as a StringBuilder, which failed before) is read as text.

camel-beanio - BeanIOSplitter keeps the records after a record that the error handler handled

When a custom BeanIOErrorHandler calls handleErrorAndAddAsResult for an invalid record, the BeanIO reader goes on and returns the next record in the same read. BeanIOSplitter replaced that next record with the result of the error handler, and of two records handled in a row it kept only the last result. Those records were not split, and nothing was logged. The splitter now returns the results of the error handler in their order, followed by the record that the reader returned, as the beanio data format does when it unmarshals into a list. A route that splits data with handled invalid records now gets every valid record.

The splitter also sets its error handler before it reads the first record. Before, an invalid, unexpected or unidentified first record failed the split even with ignoreInvalidRecords, ignoreUnexpectedRecords, ignoreUnidentifiedRecords or a custom error handler; now the first record is handled as the others are.

camel-bindy - a number field without precision keeps all its decimals when marshalled

A BigDecimal, Double or Float field whose @DataField (or @KeyValuePairField) has no precision and no pattern was marshalled with at most three decimals, rounded: 1.23456 was written as 1.235 and 0.000123 as 0 (since Camel 4.22, CAMEL-24168; up to Camel 4.21 the default precision = 0 wrote these fields without any decimals). It is now written with all its decimals, so a record that is unmarshalled and marshalled again is the same text. A Double that is the result of a calculation is written with all its digits too (0.1 + 0.2 as 0.30000000000000004, before 0.3). Set precision to get a fixed number of decimals, as before.

camel-cxfrs - an unchanged request body as reply, and a List body of the HTTP API producer

With the Default binding style, a route that does not set a reply body replied with the request MessageContentsList, which failed with HTTP 500 "No message body writer has been found for class MessageContentsList" (or was written as a one element array by a JSON provider that writes any object). Now a MessageContentsList reply of one element is the response entity, and an empty one replies with no content (204). When the resource method returns a collection, the MessageContentsList is still written as that collection. A MessageContentsList of several parameters (a resource method with more than one parameter) still fails with HTTP 500: set the reply body in the route.

The producer with the HTTP centric client API (httpClientAPI=true) sent only the first element of a List body. Now a List body is sent as a whole as the request entity, also a List of one element. Of a MessageContentsList (such as the body from a cxfrs consumer with the Default binding style) the first element is still sent. To send one element as before, set that element as the body.

camel-stax - the text mode of xtokenize gives the text as an XML parser reads it

With mode t, xtokenize returned the characters between the tags of the element as they are written: entity and character references were not decoded (AT&T instead of AT&T), and the content of a CDATA section was left out. The text is now decoded and includes the content of CDATA sections, so a route that decoded the entities itself must stop doing so. With a group greater than 1, the texts in the <group> element are escaped as XML text, so the group is still well-formed XML.

camel-smooks - allowExternalEntities applies to Smooks Source bodies and to a reader declared without a class

The Smooks component and SmooksProcessor now parse a message body that is already a Smooks Source (org.smooks.io.source.StringSource, ReaderSource, StreamSource, ByteSource or URLSource) with the same XML reader settings as the other message bodies, so external entities in it are only resolved when allowExternalEntities is true. Before, such a body was handed to Smooks as it was. The bytes of a StreamSource, ByteSource or URLSource are decoded with the exchange charset, or UTF-8, as before. These bodies are now parsed into a DOM before Smooks processes them, which uses more memory for very large inputs. DOMSource and JavaSource bodies are unchanged.

A Smooks configuration whose <reader> declares no class only sets handlers, features or parameters of the default XML reader. The Smooks component, SmooksProcessor and the Smooks data format now apply allowExternalEntities to such a configuration as they do to a configuration without a <reader>, so the handlers and features of that reader no longer take part in parsing the XML unless allowExternalEntities is true. Configurations that declare a reader class, such as EDI, CSV, JSON, DFDL or a custom reader, are unchanged.

A document with a document type declaration (<!DOCTYPE …​>) is now processed with these settings for every body type, String, byte[] and InputStream bodies included, which failed before: the entities of its internal subset are expanded, and the declaration itself is not passed on to Smooks.

Set allowExternalEntities=true on the endpoint or on the data format for documents that rely on external entities or an external DTD.

camel-smooks - a failure of a bean route with a correlation id fails the Smooks processing

A <camel:route> of the Smooks configuration with a correlationIdName and correlationIdPattern ignored a failure of the endpoint it routes the bean to. The failure now fails the Smooks processing with a SmooksException, as it already did for a bean route without a correlation id.

camel-platform-http-main - management server settings on a shared port

When the HTTP server (camel.server.enabled=true) and the HTTP management server (camel.management.enabled=true) have the same port (both default to 8080), the HTTP server serves the management endpoints. It cannot apply these options of the management server, which were ignored without notice:

  • camel.management.host, when set to another host than the one of the HTTP server

  • camel.management.path, when set to another path than the one of the HTTP server

  • camel.management.useGlobalSslContextParameters=true, when the HTTP server does not use the global SSL configuration

  • camel.management.authenticationEnabled=true, with the basic or JWT authentication options

Camel now fails to start when the management server shares the port and one of them is configured, with a message that lists them. Set camel.management.port to another port, so that the management server runs a server of its own and applies them. To keep a single port, remove them and configure the HTTP server instead: for example, authentication configured on the HTTP server for /* also applies to the management endpoints.

camel-ibm-cos - the producer takes the bucket from the CamelIBMCOSOverrideBucketName header (Breaking change)

The producer used the bucket named in the CamelIBMCOSBucketName header instead of the bucket of its endpoint. The consumer sets that header to the bucket an object was consumed from, so a route from one bucket to another (from("ibm-cos:source…​").to("ibm-cos:target…​")) stored each object back in the source bucket, and with the default deleteAfterRead=true the consumer then deleted it there. The producer now ignores CamelIBMCOSBucketName and uses the bucket of the endpoint, or the bucket named in the new CamelIBMCOSOverrideBucketName header (IBMCOSConstants.OVERRIDE_BUCKET_NAME), as camel-aws2-s3, camel-minio and camel-google-storage do since 4.10. A route that sets CamelIBMCOSBucketName to choose the bucket of a producer operation (for example createBucket or deleteBucket) must set CamelIBMCOSOverrideBucketName instead.

camel-xslt-saxon - external resource access is restricted for a Source message body

XsltSaxonAggregationStrategy now configures its Saxon TransformerFactory the same way the xslt-saxon endpoint configures the factory it creates: FEATURE_SECURE_PROCESSING is enabled and ACCESS_EXTERNAL_DTD and ACCESS_EXTERNAL_STYLESHEET are set to an empty string. Previously the strategy used the Saxon defaults, so the same stylesheet ran with different settings depending on whether it was used from the endpoint or from the strategy. On Saxon PE/EE, FEATURE_SECURE_PROCESSING also disables reflexive Java extension functions in the stylesheet.

In addition, both the strategy and the xslt-saxon endpoint now parse a message body that is already a javax.xml.transform.Source still to be parsed, for example a StreamSource or the body after a convertBodyTo(Source.class), with a reader that does not resolve references to external resources declared in the document. A DOMSource, a StAXSource, or a Source that already carries its own reader is left unchanged, and a declaration internal to the document is still applied. The other body types (String, byte[], InputStream) are unaffected, as they already reach the transformer through Camel’s StAX/SAX conversion.

On the xslt-saxon endpoint, secureProcessing now controls both: setting secureProcessing=false (for example so a Saxon PE/EE stylesheet can call Java extension functions) also restores the previous, unrestricted parsing of a Source body. The option is marked as a security knob (security=insecure:dev, insecureValue=false), so camel.main.profile=prod reports it when it is turned off. XsltSaxonAggregationStrategy has no switch for the Source parsing: setTransformerFactory(…​) replaces the factory, but the strategy’s XsltSaxonBuilder keeps secureProcessing=true, so the body is still parsed with the restricted reader.

camel-ical - each unmarshal parses with its own calendar builder

The ical data format used one ical4j CalendarBuilder for all exchanges. Two exchanges that were unmarshalled at the same time could get the events of each other, or the calendar of the other exchange. Each unmarshal now uses its own builder.

A builder also has its own time zone registry, so a VTIMEZONE of one calendar is no longer used for the calendars unmarshalled after it. A date with a TZID that is not a standard time zone and that its calendar does not define is now read as a local date-time without a zone (getDate() returns a LocalDateTime); before, it was read with the definition of an earlier message, if there was one. A calendar that defines a TZID an earlier message has defined differently is now read with its own definition.

camel-flatpack - marshal writes the first row of the list

With the default ignoreFirstRecord=true, marshalling a List<Map<String, Object>> skipped the first map, so the first data row was missing from the output, for delimited and fixed-length files alike (the header line of a delimited file is written from the column names, not from the first map). Every map of the list is now written, and ignoreFirstRecord only applies to unmarshal. A route that set ignoreFirstRecord=false to work around this is not affected; a route that added a dummy first map now gets it in the output too.

camel-crypto - verify fails on a message without body, and the key header always wins

crypto:verify now fails with a SignatureException when the message has no body. Before, a null body was verified as an empty payload, so a signature of an empty payload passed. crypto:sign still signs a null body as an empty payload.

The CamelSignaturePublicKeyOrCert header now always wins over the publicKey, certificate or keystore of the crypto:verify endpoint. Before, a PublicKey in the header was ignored when the endpoint had a certificate (or a keystore with a certificate for the alias); a Certificate in the header already won.

A stream body is now cached by crypto:sign and crypto:verify, so the next step of the route gets the body also when stream caching is turned off. Before, the stream was read and closed, and the next step got an empty body.

camel-csv - the maps of a list are written in the columns of their keys

When no header is set (or headerDisabled=true), marshalling a List<Map<String, Object>> wrote the values of each map in the iteration order of that map, so a row whose map had its keys in another order, or had no entry for one of the keys, put its values in the wrong columns. Within one message body, each key now keeps its column: the columns are the keys of the first map, followed by the keys of later maps that the first one does not have, in the order they first appear (the rows written before such a key appears have fewer fields). A map without one of these keys gets a null value in its column (an empty field, or the nullString when it is set), where before the following values moved one column to the left. Each message body starts again with no columns, as before. Set header (with headerDisabled=false) to get a fixed set and order of columns.

This also changes the output of a body that mixes record types on purpose, such as a header row {type=H, date=d}, detail rows {type=D, sku=s, qty=q} and a trailer row {type=T, count=n} (common in flat-file exchanges): it was written as H,d, D,s,q and T,n, and is now written as H,d, D,,s,q and T,,,,n. To keep the previous output, convert the rows to List before marshalling, or set a custom marshallerFactory (marshallerFactoryRef in XML and YAML) whose CsvMarshaller returns the values of each map. Every row now has all the columns seen so far in the body, so rows that each have keys of their own (sparse or pivoted data) give about N²/2 fields for N rows, where the output was linear in the input before.

camel-cxf - message bodies of the POJO producer and of CXF_MESSAGE, and replies that do not convert

The cxf producer in POJO mode split a body that is not a List or an Object[] into several parameters, the way the Split EIP does: a String with a comma ("Hello,World") became two parameters, a byte[] one parameter per byte. Now a List (or another Collection) or an Object[] holds the parameters, and any other body, such as a String, a byte[] or a Map, is the one parameter. A route that relied on a comma-separated String (or another iterable value) to give several parameters must now set a List of the parameters as the body.

In CXF_MESSAGE mode, the producer and the consumer reply sent a body that is not a SOAPMessage or a javax.xml.transform.Source as an empty SOAP envelope. Such a body (for example the envelope as a String, byte[], InputStream or DOM Document) is now converted to a DOMSource and sent; a body that cannot be converted fails the exchange.

A consumer reply in PAYLOAD mode that does not convert to a CxfPayload, or in RAW mode that does not convert to an InputStream, was sent as an empty soap:Body or an empty response. It is now a SOAP fault. A null reply is unchanged.

camel-github2 - the commit consumer routes the commits beyond the newest 100

The commit consumer read only the 100 newest commits of the branch at each poll, so when more than 100 commits were pushed between two polls, the older ones were never routed. A poll now reads the commits up to the last routed one (at most 1000, the number of commits the consumer remembers), and can route more than 100 commits. When the last routed commit is no longer on the branch, only the 100 newest commits are considered, as before. GitHub lists the commits by committer date, newest first, so this covers a linear history: the commits that a merge brings in keep their older dates and can be listed after the last routed commit, and they are not routed, as before.

Each routed commit costs one GitHub API request for its author, plus one for each author or committer not looked up before, so a poll that catches up on many commits can send about 1000 requests instead of about 100. When the rate limit is reached, the GitHub client waits inside the poll until the limit resets (its default rate limit handler), and stopping the route waits for that poll.

camel-git - CamelGitCommitId is the sha of the commit

Breaking change: the CamelGitCommitId header set by the commit consumer (git:…​?type=commit) is now the sha of the commit as a String, as the header is documented. Before, it was the JGit RevCommit object, whose String form is not the sha, so the header could not be given to the cherryPick operation. A route that read the header as a RevCommit or an ObjectId must now read it as a String (ObjectId.fromString gives the ObjectId).

The commit and commitAll operations now set the CamelGitCommitId header to the sha of the new commit. The body is unchanged. The header replaces a CamelGitCommitId already on the message, such as the one set by the commit consumer, or one set for a later cherryPick.

The checkout operation without a tagName now checks out a branch that already exists. Before, it always created the branch, and failed with RefAlreadyExistsException when the branch existed. With a tagName, it still creates the branch from the tag.

camel-milo - the OPC UA client validates the server certificate

When the endpoint it selects uses a security policy other than None, the milo-client and milo-browse components now validate the certificate of the OPC UA server. Before, any server certificate was accepted, and no option allowed to validate it.

Configure the trusted certificates with the new trustListDirectory option: put the certificate of the server, or of the CA that issued it, in the trusted/certs directory of the trust list. A server certificate that does not chain up to a trusted certificate is written to rejected/certs, from where it can be moved to trusted/certs once it has been checked. The trust list also refuses a server certificate outside its validity period, revoked by a revocation list of the trust list, or whose application URI differs from the one of the server. A server certificate issued by a CA is refused with Bad_CertificateRevocationUnknown when the trust list holds no revocation list of that CA: add the CRL of the CA (an empty one is enough) to trusted/crl or issuer/crl. A custom validator can be configured with the new certificateValidator option instead.

With neither option, the client does not connect to such an endpoint. It logs a WARN message Failed to connect: The certificate of the OPC UA server cannot be validated, followed by the endpoint and its security policy, and tries again after reconnectTimeout (10 seconds by default). A producer fails with IllegalStateException: No connected, caused by the same message. A server certificate refused by the trust list or by a certificateValidator is logged at WARN as Failed to connect, because a certificate or security check failed. To restore the previous behaviour, set the new trustAllCertificates option to true. This is insecure: the client then does not authenticate the server.

The certificate that the user name and password are encrypted with is checked by the same validator. Endpoints that use the security policy None are not affected.

camel-ssh - known_hosts entries are matched as in OpenSSH

When knownHostsResource is set, the entries of the known_hosts file are now matched as OpenSSH matches them, against the host name or address the client connects to, written host for port 22 and [host]:port for another port:

  • A host pattern must match that name as a whole. An entry for another host whose name or address contains the configured one is no longer used.

  • A pattern such as db.example.com only applies to port 22. A server on another port needs an entry such as [db.example.com]:2222, or a pattern that matches that form, such as *.

  • The host is used as it is written: the host configured on the endpoint, or the HostName that ~/.ssh/config gives for it. An endpoint configured with a host name no longer uses entries listed only under its address, an endpoint configured with an address no longer uses entries listed under the name that a reverse lookup of the address returns, and an IPv6 address such as ::1 is matched as written instead of in its expanded form.

  • Every entry of the host is considered, so a host can be listed with several keys. Wildcard, negated and hashed patterns, comments and @revoked entries are supported. @cert-authority entries are ignored.

  • A known_hosts resource that cannot be read, or that has a line that cannot be read (for example an unknown @ marker, a missing key, or a key that is not valid base64), now makes every connection fail, whatever the value of failOnUnknownHost. Previously such a resource was treated as having no entry, and such a line was skipped.

As before, a server whose host has entries without a marker is rejected when it presents a key that none of them lists, whatever the value of failOnUnknownHost, which only applies to a host without such an entry: @revoked and @cert-authority entries do not list a host. Check that the known_hosts file has an entry for the host name or address, and the port, that each endpoint is configured with.

camel-zipfile - ZipAggregationStrategy keeps entries with the same name

ZipAggregationStrategy replaced an entry of the zip file when a later message had the same entry name, for example two files orders.csv from different folders of a recursive file consumer (the folders are not kept by default), so the content of the earlier file was lost. The later entry is now added with a number before its extension: orders (1).csv, orders (2).csv, and so on. Entry names can therefore differ from the file names (or the CamelFileName header). Use preserveFolderStructure=true to keep the folders in the entry names instead.

camel-zip-deflater - gzipDeflater and zipDeflater limit the decompressed size

The gzipDeflater and zipDeflater data formats have the maxDecompressedSize option of the zipFile and tarFile data formats, with the same default of 1073741824 bytes (1 GiB). An unmarshal of a message that decompresses to more than that now fails with an IOException ("The InputStream entry being copied exceeds the maximum allowed size"). Routes that unmarshal larger messages set a higher limit, or -1 for no limit, for example <gzipDeflater maxDecompressedSize="-1"/> or unmarshal(dataFormat().zipDeflater().maxDecompressedSize(-1).end()). The limit counts the whole decompressed message. For example, when a .tar.gz file is unmarshalled with gzipDeflater and then split into its entries, the limit applies to the whole tar archive, not to each entry as the limit of tarFile does.

camel-zookeeper - the producer waits for every write and fails on a body it cannot store

The zookeeper producer now waits for ZooKeeper to confirm a write or a delete of an InOnly exchange too, and fails the exchange when it failed. Before, an InOnly exchange was sent without waiting, and a failure, such as a znode that does not exist without create=true, a stale CamelZooKeeperVersion header, or a delete of a missing znode, was only logged. An InOnly exchange still keeps its body.

A body that cannot be converted to bytes, such as a Map, a POJO or a number, now fails the exchange with an InvalidPayloadException. Before, the znode was overwritten with no data. A null body still stores no data.

A DELETE of a znode that does not exist no longer creates it when create=true is set; it fails the exchange with a NoNodeException, as it did without create=true.

With listChildren=true, a failed write or delete of an InOut exchange now fails the exchange, instead of being replaced by the listing of the child znodes.

camel-milo - each client connection has thread pools of its own

A milo-client or milo-browse connection now handles the outcome of its write, method call, read and browse requests on a thread pool of its own named MiloClient, and runs its reconnects on a single thread named MiloClientReconnect. Before, it used the shared scheduled executor of Milo (milo-shared-scheduled-executor) for both. A route that continues after an awaited request now runs on a thread of the MiloClient pool. The pool has up to as many threads as there are processors, which end after they have been idle for the keep-alive time of the default thread pool profile (60 seconds). With virtual threads enabled and more than one processor, it starts a virtual thread for each task instead. The MiloClientReconnect thread starts with the first reconnect of the connection, and its reconnects run one after the other. The threads of a connection end when it is closed.

A MiloClientConnection created outside an endpoint needs a CamelContext, set with setCamelContext, before it is used.

camel-mllp - the producer converts a body that is not a String or a byte array

The mllp producer now converts a body that is not a String or a byte[], such as an InputStream or a file, to a byte[] with the type converter, and encodes a CharSequence such as a StringBuilder in the charsetName charset. A body that cannot be converted, such as a Map, fails the exchange with a MllpInvalidMessageException. Before, such a body was sent as an empty MLLP message: with InOut the exchange then failed with a MllpAcknowledgementTimeoutException after the receive timeout, and with InOnly it completed without an error.

After sending a String body, the CamelCharsetName exchange property is now the charset the message was sent in. Before, it was set to charsetName even when the charset of the exchange was used to encode the message.

camel-lra - only an LRA hosted by the configured coordinator is joined

The LRA saga service now joins, completes or compensates an LRA only when its URL is under the configured LRA coordinator (coordinatorUrl followed by coordinatorContextPath). A message whose Long-Running-Action header holds the URL of another coordinator now fails, instead of the saga service calling that URL.

If your participants reach the same coordinator under different URLs, for example with different host names, list the other coordinator URLs, including the context path, in the new allowedCoordinatorUrls option (camel.lra.allowedCoordinatorUrls).

This also applies to the LRAs the service starts itself: when the coordinator returns LRA URLs under another base URL than coordinatorUrl, for example behind a proxy, starting a saga fails until that base URL is added to allowedCoordinatorUrls.

camel-lra - warning for unsigned participant callbacks, and reserved saga option names

The LRA saga service now logs a warning at startup when no callbackSecret is configured: the callbacks that the LRA coordinator sends to the participant endpoints are then not verified, so these endpoints must only be reachable by the coordinator. The Saga EIP documentation describes how to sign them.

A saga option named like a parameter of the participant callback URLs, in any case (Camel-Saga-Compensate, Camel-Saga-Complete, Camel-Saga-Signature or Camel-Saga-Lra), now fails the creation of its route, with or without a callback secret. The value of such an option could not be read back from the callback before.

camel-openai, camel-ai-tool - AI events now use CamelEvent.Type.Ai

The events fired by the AI components now share a common contract, org.apache.camel.ai.AiEvent (a CamelEvent of the new CamelEvent.Type.Ai), instead of each being a component-specific CamelEvent.Type.Custom event. Events that relate to a single exchange implement the exchange-backed subtype AiEvent.ExchangeAiEvent (the root AiEvent itself is not an ExchangeEvent). This lets an EventNotifier recognise AI activity — and route it to metrics or tracing — by type and by the AiEvent sub-interfaces, without depending on an AI component.

The events shipped in 4.23.0 are affected:

  • camel-ai-tool: AiToolAuthorizationDeniedEvent now implements AiEvent.ToolCallDeniedEvent;

  • camel-openai: OpenAIAgenticLoopStartedEvent, OpenAIAgenticToolCallExecutedEvent and OpenAIAgenticLoopCompletedEvent now implement AiEvent.AgentLoopStartedEvent, AiEvent.ToolCallExecutedEvent and AiEvent.AgentLoopCompletedEvent.

Their getType() now returns CamelEvent.Type.Ai rather than CamelEvent.Type.Custom, and they are now CamelEvent.ExchangeEvent`s (via `AiEvent.ExchangeAiEvent). An EventNotifier that recognised these events by event.getType() == CamelEvent.Type.Custom must match on CamelEvent.Type.Ai instead, or — preferably — on event instanceof org.apache.camel.ai.AiEvent (or one of its sub-interfaces), which no longer requires a dependency on the AI component. The concrete event classes and all their accessors are unchanged, so code that references them directly is not affected.

Because the shipped AI events are now ExchangeEvent`s (via `AiEvent.ExchangeAiEvent), a notifier that filters on instanceof ExchangeEvent now also receives them. AI events are their own category: a new ignoreAiEvents flag (EventNotifier.setIgnoreAiEvents(boolean), default false) opts a notifier out, and ignoreExchangeEvents does not drop AI events (so an AI-observability notifier can keep ignoreExchangeEvents=true for performance). Camel’s own exchange-oriented notifiers (the debugger, the dev-console event ring, the micrometer and OpenTelemetry metrics exchange notifiers) set ignoreAiEvents=true so AI events do not reach them; a custom exchange-focused notifier that should not see AI events should do the same.

camel-oauth

OAuthBearerTokenProcessor no longer creates or updates the OAuth session, and no longer sets the camel.oauth.session cookie nor the CamelOAuthSessionId header: a bearer token authenticates the request that carries it, and nothing else. The authenticated UserProfile is available in the CamelOAuthUserProfile exchange property (OAuth.CAMEL_OAUTH_USER_PROFILE). A route that read the profile from the OAuth session after OAuthBearerTokenProcessor must read it from that exchange property instead.

When the message comes from platform-http (on Vert.x or on Spring Boot), from a servlet based HTTP consumer (camel-servlet, camel-jetty) or from camel-netty-http, the bearer token and the session cookie are now read from the Authorization and Cookie headers of the HTTP request only. A query or form parameter of the same name, which these consumers also map into the message headers, is no longer used for them, and neither is an Authorization or Cookie message header that the route sets itself. For example .setHeader("Authorization", simple("Bearer ${header.X-Forwarded-Access-Token}")) before OAuthBearerTokenProcessor now results in a 401 response. A route that takes the token from elsewhere can validate it with OAuthHelper.validateOAuthToken from camel-support instead.

For any other message, such as one from camel-undertow, the processors keep reading the message headers, but leave out a value that also appears in the query string of the request.

InMemorySessionStore no longer logs session identifiers. Additionally, OAuthCodeFlowCallback rotates the session identifier upon successful authentication, replacing the pre-login session identifier with a new session identifier and emitting an updated session cookie. The default rotateSession method of OAuthSessionStore keeps the session unchanged, so a custom OAuthSessionStore must override it to rotate the session identifier on login.

The Jakarta servlet backend (ServletOAuth) now validates the aud (audience) claim of access tokens evaluated by UserProfile and OAuthBearerTokenProcessor. Tokens are validated against configured expected audiences on JWTOptions (via config.getJWTOptions().addAudience(String) or config.getJWTOptions().setAudience(List<String>)), falling back to the configured camel.oauth.client-id when no custom audience is configured. Tokens whose audience does not match or tokens without an audience claim are rejected with OAuthException.

For OpenID Connect / Keycloak login flows, Keycloak access tokens commonly carry aud=account by default unless an audience protocol mapper is configured on the client. Applications upgrading that use Keycloak for authorization code login flows should configure an audience mapper on the client in Keycloak or configure the expected audience on JWTOptions (for example, config.getJWTOptions().addAudience("account")).

camel-platform-http-vertx - the charset parameter of the request Content-Type in any case

The consumer now reads the charset parameter of the request Content-Type whatever the case of its name, as camel-undertow and camel-netty-http do: a body sent with Content-Type: text/plain; Charset=ISO-8859-1 is decoded as ISO-8859-1 and CamelCharsetName is set. Before, only charset= in lower case was read and such a body was decoded as UTF-8. An empty charset parameter (charset= or charset="") now counts as no charset, as in camel-undertow, camel-netty-http and camel-http-common (CAMEL-25355); before, converting the body to a String failed with 500.

camel-platform-http-vertx - a number, a boolean, a character or an enum reply is written as text

A reply body that is a number, a boolean, a character or an enum is now written as its text, in the charset of the response Content-Type (UTF-8 otherwise), as camel-undertow does: setBody(constant(42)) replies with 42. Before, an Integer, Long, Short, Float or Double was written in its binary form (42 became the 4 bytes 00 00 00 2A), and a boolean, a character or an enum failed with 500. A StringBuilder or another CharSequence is now also written in the charset of the response Content-Type, as a String is, rather than in the charset of the exchange. To send the binary form of a number, convert the body to byte[] first.

camel-quickfix - an InOut consumer replies only to application messages, and an InOut producer needs CorrelationCriteria

A consumer with exchangePattern=InOut now sends a reply only for an AppMessageReceived event, with the body the route ends with (a new FIX message, or the received message changed in place), and a null body sends no reply. Before, every event was answered with the body the route ended with, so the AdminMessageSent event of the Logon sent the Logon again until a StackOverflowError. A route that does not change the body still sends the received message back, so set a null body for a message that must not be answered. A reply that is not a FIX message (such as a Map) is no longer sent: the error goes to the exception handler of the consumer (logged at WARN by default), and the request counts as received. Before, it failed inside QuickFIX/J, which then asked the counterparty to resend the request over and over. No reply is sent when the exchange failed.

A producer with an InOut exchange now fails before it sends the message when the CorrelationCriteria exchange property is not set, with an IllegalArgumentException that names the property. Before, the message was sent and the exchange failed with ExchangeTimedOutException, as no reply could be matched. Send with InOnly when no reply is expected.

A producer body that does not convert to a FIX message (such as a stream, a Map or no body) now fails with an InvalidPayloadException that names the body type, instead of a NullPointerException.

camel-report-maven-plugin - Java routes are read with the Java DSL parser

The validate and route-coverage goals read Java routes with the Java DSL parser of camel-java-io instead of camel-route-parser (Roaster). XML routes are read as before. The parser reads every configure() method of a source file, including those of a route builder created inside a test, so validate can find and validate endpoints it did not see before. An endpoint uri that is only a method call, such as from(inboxQueue()), is not validated (it was validated as a property placeholder without a component before); with logUnparseable=true it is logged.

camel-jbang - the route-parser plugin is removed

The route-parser plugin (camel route-parser) and its camel-jbang-plugin-route-parser artifact are removed. Use camel cmd route-structure MyRoute.java instead: it prints the structure of the routes of a Java source, read without compiling it, and of YAML and XML sources too. If you installed the plugin, remove it with camel plugin delete route-parser.

camel-jbang-mcp - the camel_component_properties tool is removed

The camel_component_properties MCP tool is removed. It listed the endpoint options of a component as camel.component.<scheme>.<option> keys, which camel run and camel_configuration_validate reject: only the component options can be set that way. Use camel_catalog_doc instead: each component option in its answer carries its application.properties key as configKey.

camel_catalog_doc without optionsFilter now lists the required options and the first 20 other options of a component in full, and the rest by name in moreOptions (limit lists more in full), and an answer is cut at 18 000 characters with a marker. An option of both the endpoint and the component is listed once, with scope set to endpoint,component and its configKey, instead of twice, and matchedOptions counts option names. A name that only matches a word of an EIP title (such as retry) no longer returns that EIP: the answer is a not-found with suggestions.

camel-wasm - the endpoint runs WASI command modules (Breaking change)

The wasm endpoint now runs WASI Preview 1 command modules (the body is standard input, standard output becomes the body, the exit code and standard error are in CamelWasmExitCode and CamelWasmStderr) and no longer supports the custom alloc/dealloc and JSON message ABI. The URI path is now the module (wasm:classpath:upper.wasm); the function name and the module option are removed, and a header reaches the module only when listed in environmentHeaders. To migrate, turn the function into a program that reads standard input and writes standard output, built for wasm32-wasip1 (Rust) or with GOOS=wasip1 GOARCH=wasm (Go).

The endpoint runs every exchange on a new instance of the module, so a module can no longer keep state from one message to the next. The 4.23 note that a failed call discards the Wasm instance no longer applies.

The wasm language is removed: the wasm expression of the Java, XML and YAML DSLs, WasmExpression of the model, and WasmLanguage, WasmFunction, WasmSupport, Wasm.FN_ALLOC and Wasm.FN_DEALLOC of camel-wasm. Call the module with the wasm endpoint as a route step instead, for example .to("wasm:classpath:transform.wasm"), built as a WASI command module as described above, and use its output (the new message body) in the next steps.

The following API of camel-wasm is removed or changed with the old ABI:

  • WasmComponent.getModule() and setModule(String), and WasmConfiguration.getModule() and setModule(String): the module is the path of the endpoint URI, available as WasmEndpoint.getModule().

  • The functionName path parameter of the endpoint: the third argument of the WasmEndpoint constructor is now the module.

  • WasmSupport.deserialize(byte[], Exchange).

  • The constructor WasmProducer(Endpoint, String, String) is replaced by WasmProducer(WasmEndpoint).

camel-wasm has a new dependency, run.endive:compiler, which brings in ASM (org.ow2.asm). It is used by the new compile option of the endpoint (default false). The module is now loaded once per endpoint and no longer by every producer, so stopping and starting a route does not read the module again: remove the route and add it again, or restart the application, to load a module that has changed.

camel-google-text-to-speech - listVoices lists the voices of all languages unless languageCode is set

The listVoices operation of google-text-to-speech now lists the voices of all languages when the languageCode option is not set. Before, it always listed only the voices of en-US, the default value of the option, which is meant for synthesize. To keep listing the voices of one language only, set the option on the endpoint, for example google-text-to-speech:listVoices?languageCode=en-US.

With pojoRequest=true, the producer now runs the operation of the endpoint, or of the CamelGoogleCloudTextToSpeechOperation header: listVoices takes a ListVoicesRequest as body and gives the List of the voices. Before, it always called synthesize with a SynthesizeSpeechRequest body, whatever the operation. A listVoices endpoint with pojoRequest=true now fails with an InvalidPayloadException for a body that is not a ListVoicesRequest. The path of the endpoint must therefore name the operation, synthesize or listVoices, unless the header does: an endpoint such as google-text-to-speech:speak?pojoRequest=true, which synthesized before, now fails with an IllegalArgumentException. A CamelGoogleCloudTextToSpeechOperation header left on the message by an earlier step now applies with pojoRequest=true too.

camel-web3j - the consumer subscribes again after an error

When the subscription of a consumer ends with an error (for example a request to the node that fails inside BLOCK_OBSERVABLE or TRANSACTION_OBSERVABLE, or a node that cannot be reached when the route starts), the consumer now subscribes again every 15 seconds until it succeeds. The error is routed as before, and logged at WARN; the failed attempts that follow are logged at DEBUG. Previously the consumer received nothing more while the route stayed started. A new subscription starts at the latest block, so the blocks between the error and the new subscription are not routed. REPLAY_BLOCKS_OBSERVABLE, REPLAY_TRANSACTIONS_OBSERVABLE and an ETH_LOG_OBSERVABLE with fromBlock set to a block number are not subscribed again, as that would route the same blocks or logs again.

camel-web3j - ETH_GET_TRANSACTION_RECEIPT gives the TransactionReceipt, and the block options default to latest

The ETH_GET_TRANSACTION_RECEIPT operation now sets the TransactionReceipt as the body, or null when the node has no receipt for the transaction, like the ETH_GET_TRANSACTION_BY_* operations do with the Transaction. Before, the body was an Optional<TransactionReceipt>: a route that unwraps the Optional must now use the body as it is.

The atBlock, fromBlock and toBlock options now default to latest, as documented. Before, they were not set, and the operations that take a block, such as ETH_GET_BALANCE and ETH_GET_BLOCK_BY_NUMBER, failed with a NullPointerException unless atBlock or the CamelWeb3jAtBlock header was set, and so did the REPLAY_BLOCKS_OBSERVABLE and REPLAY_TRANSACTIONS_OBSERVABLE consumers without fromBlock and toBlock.

The operation option defaults to transaction, which is not an operation of the producer nor of the consumer. A consumer without an observable operation now fails to start with a message that lists them, and accepts the operation in lower case, as the producer does. A producer without the option and without the CamelWeb3jOperation header now fails the exchange with a message that asks for one.

camel-google-vertexai - generateMultimodal sends the body as media, and fails without a MIME type

When the CamelGoogleVertexAIMediaData and CamelGoogleVertexAIMediaGcsUri headers are not set, the generateMultimodal operation now sends a body that is not the text prompt as media: bytes, an InputStream, a File, a Path or the file of a file consumer, read with the type converter. The MIME type is the one of the CamelGoogleVertexAIMediaMimeType header or, for a file, the one of its file name extension. Before, only a byte[] body with that header was used: a stream, a file, and bytes without the header were dropped without an error, and the model got the prompt alone.

Media without a MIME type now fails the exchange with an IllegalArgumentException, for the body as well as for the CamelGoogleVertexAIMediaData header (which was dropped too). A String body is the prompt only when the CamelGoogleVertexAIPrompt header is not set: with that header, it is media as well, and needs the MIME type header. An empty or blank String, and an empty byte array, are not sent: the request then has the prompt only. A route that sets the CamelGoogleVertexAIPrompt header while the body holds something else it does not mean to send must now remove the body.

camel-google-vertexai - headers that were declared but not used

The CamelGoogleVertexAISystemInstruction, CamelGoogleVertexAISafetySettings and CamelGoogleVertexAIAnthropicVersion headers were documented but not read: they are now applied. CamelGoogleVertexAISafetySettings takes a List of com.google.genai.types.SafetySetting, the type of the client that the generate operations use (the docs named com.google.cloud.aiplatform.v1.SafetySetting), and another type fails the exchange. CamelGoogleVertexAIAnthropicVersion takes the place of the anthropicVersion option for a rawPredict or streamRawPredict request of publisher anthropic that has no anthropic_version.

The generate operations now set the CamelGoogleVertexAISafetyRatings header, a List of com.google.genai.types.SafetyRating (the docs named com.google.cloud.aiplatform.v1.SafetyRating), and the CamelGoogleVertexAIContentBlocked header, which is true when the safety filters blocked the prompt (there is then no text).

The CamelGoogleVertexAIChatMessages header is deprecated: the producer never read it, and generateChat sends the prompt only, without a conversation history.

camel-xmpp - one endpoint per participant and user, and one connection per account

XMPP URIs that differ only in the participant or the user are now separate endpoints. Before, the component could return an endpoint that was created for another participant or user, and messages went to the wrong participant.

All the consumers and producers of one account (the same host, port, service name, user, password, resource and login settings) now share one XMPP connection, also across endpoints. So an account has one session on the server instead of one per endpoint, and the server no longer closes one of them with a conflict error. The connection is closed when the last route that uses the account stops. With the connectionConfig option, endpoints share a connection only when they refer to the same ConnectionConfiguration instance (for example the same bean in the registry).

XmppEndpoint.createConnection() now creates a new connection on every call, instead of returning the connection of the endpoint, and an endpoint no longer disconnects a connection when it stops.

camel-xmpp - a consumer with a participant routes only the chat messages from that participant

A consumer with a participant (for example xmpp://superman@jabber.org/joker@jabber.org) and no room now routes only the chat messages from that participant. Before, it routed the chat messages of every sender to the account, and every consumer of the account routed each of them. To route the chat messages of every sender, leave out the participant (xmpp://superman@jabber.org), and read the sender from the CamelXmppFrom header.

camel-google-bigquery - a grouped exchange with a body that is not a row fails as a whole

When one exchange of a grouped exchange (Exchange.GROUPED_EXCHANGE) sent to google-bigquery has a body that is not a Map or a List of Map objects, the grouped exchange now fails with an IllegalArgumentException, and none of its rows is inserted. Before, the exception was set on that one exchange of the group, which nothing looked at: its row was dropped without notice, the other rows were inserted, and the grouped exchange completed as a success. Filter or convert such bodies before the aggregation.

A List body with an element that is not a Map now fails the same way, with an IllegalArgumentException instead of a ClassCastException. That case, and an exchange of the group with an empty CamelGoogleBigQueryTableId header, now fail the grouped exchange before any row is sent: before, the rows of the tables that came earlier in the group were already inserted.

camel-google-bigquery - the connectionFactory option of the component is used

The connectionFactory option of the google-bigquery and google-bigquery-sql components is now used by the endpoints that do not set their own. Before, it was ignored, and each endpoint built a default client from its own options, such as serviceAccountKey.

camel-pulsar - a consumer that cannot subscribe fails the start of the route

With subscriptionType Shared, Failover or Key_Shared, a consumer that could not subscribe when the route started (for example a broker that could not be reached within the operation timeout of the Pulsar client) was only logged at ERROR, and the route started with fewer consumers than numberOfConsumers, or with none, and never subscribed them again. Now the consumers that did subscribe are closed and the route fails to start with the error of the Pulsar client, as it already did with Exclusive. When the route starts with the CamelContext, the CamelContext then fails to start, unless the supervising route controller (camel.routecontroller.enabled=true) is used, which starts such a route again. With messageListener=true (the default) the consumers that did subscribe may already have received messages before they are closed, and messages they received but did not acknowledge can be delivered again once the route has started.

camel-google-functions - callFunction fails the exchange when the function fails

The callFunction operation of google-functions now fails the exchange with a CamelExchangeException when the CallFunctionResponse has an error, which Google Cloud Functions sets when the execution of the function was not successful. Before, the exchange went on as a success, with an empty body. The CallFunctionResponse is still in the CamelGoogleCloudFunctionsResponseObject header, so an error handler (onException, or doTry and doCatch) can read its error and execution id. A route that read the error from that header after the call must handle the exception instead.

The body of callFunction is now marshalled to JSON when it is a Map, a List or another Java object, instead of being sent as its toString(). Text, bytes, a stream, a file, XML (a DOM Node or a Source), a ByteBuffer, an enum or a character are still converted to text, as before. The JSON comes from a plain Jackson ObjectMapper, so a POJO that needs custom JSON, such as one with java.time fields, must be marshalled in the route first. A message without a body fails with an InvalidPayloadException instead of a NullPointerException.

camel-vertx-http - a header with several values is sent as several headers

A message header whose value is a java.util.Collection or an object array (as the HTTP consumers map a request header that occurs more than once) was sent as one HTTP header with the toString() of the value, for example [a, b]. The producer now sends one HTTP header per value. A collection or an object array that is empty, or that holds only null values, now sends no header (an empty List was sent as the header value []).

Every other value is converted to one String and sent as one header as before: a String (also when it contains commas), a byte[] and the other primitive arrays, a Map, and a Vert.x JsonObject or JsonArray (sent as JSON). A route that needs the old single header can set the header to a String, for example with String.valueOf(list).

camel-google-mail - messages/attachments removed

The messages/attachments method is removed. It was the accessor of the attachments of a message and not a request, so every call failed with a ClassCastException; a route with a google-mail:messages/attachments endpoint now fails when it starts. Read attachments with the attachments API, such as attachments/get.

camel-vertx - standalone TrustManager on SSLContextParameters is applied

When configuring TLS through SSLContextParameters with a standalone TrustManager instance (via TrustManagersParameters.setTrustManager(…​)), Vert.x components now wrap and apply the configured TrustManager. Previously, only TrustAllTrustManager.INSTANCE and KeyStore-backed configurations were applied, and other standalone TrustManager instances were not set on the underlying Vert.x options.

When both a TrustManager and a keystore are configured on TrustManagersParameters, the TrustManager is now used, as with the other components that use SSLContextParameters; before, the keystore-based trust options were used.

camel-vertx - a stream body is sent as a Buffer, and the consumer replies with a failure when the body has no codec

When a vertx consumer receives a message that expects a reply and the route ends with a stream body (such as a received Buffer, which is a stream with stream caching enabled, that the route did not change), the consumer now replies with its content as a Buffer. When the reply body has no codec on the event bus (such as a POJO that is not Serializable), the consumer now logs the error and sends a failure reply (a ReplyException with failure type RECIPIENT_FAILURE, failure code 500 and the message Exchange processing failed). Before, both sent no reply, and the sender waited until its reply timeout (30 seconds by default).

The vertx producer now sends a stream body as a Buffer too, so a route from one vertx endpoint to another works with stream caching enabled. Before, it failed with No message codec for type.

camel-knative-http - a header with several values is sent as several headers

A message header whose value is a java.util.Collection or an object array (as the consumer maps a request header that occurs more than once) is now sent as one HTTP header per value. The Knative HTTP producer sent it as one HTTP header with the toString() of the value, for example [a, b], and the reply of the consumer sent only its last value. A collection or an object array that is empty, or that holds only null values, sends no header (the producer sent an empty List as the header value []).

Every other value is one header. A String is sent as before, also when it contains commas, and the reply still sends no header for a blank String. A byte[] is now converted to a String with the type converter (the producer sent its toString(), such as [B@1dc9f5ad, and the reply only its last byte as a number). The producer sends a Map and a Vert.x JsonObject or JsonArray as before, as their toString() (JSON for the Vert.x types). The reply now does the same: it sent only the last entry of a Map or JsonObject in the form key=value, and only the last element of a JsonArray.

A route that needs the old single header of the producer can set the header to a String, for example with String.valueOf(list).

camel-google-sheets - spreadsheets/developerMetadata, sheets and values removed

The developerMetadata, sheets and values methods of the spreadsheets API are removed. They were accessors of sub-resources and not requests, so every call failed with a ClassCastException; a route with one of these endpoints now fails when it starts. Read and write the values of a spreadsheet with the data API, such as data/get and data/update.

camel-http - a header with several values is sent as several headers

A message header whose value is a java.util.Collection or an object array (as the HTTP consumers map a request header that occurs more than once) was sent as one HTTP header with the values in the form [a, b]. The producer now sends one HTTP header per value, as camel-vertx-http and camel-knative-http do. Null values are skipped, and a collection with one value is sent as one header with that value, as before.

Every other value is sent as one header. A String is sent as before, also when it contains commas. A byte[] or a char[] is now converted to a String with the type converter, for example abc (it was sent as [97, 98, 99]), and a Map is sent as its toString(), such as {k1=v1, k2=v2} (it was [k1=v1, k2=v2]). The other primitive arrays, an Iterator, a java.util.stream.Stream and an Iterable that is not a collection are sent in the form [a, b] as before. A route that needs the old single header can set the header to a String, for example with String.valueOf(list).

This also applies to camel-graphql, which sends its requests with camel-http.

camel-core - Switch case values and the Dynamic Router uriDelimiter resolve property placeholders

The value of a Switch case and the uriDelimiter of the Dynamic Router EIP were used as written, so a property placeholder or a route template parameter in them never matched (Switch) or did not split the slip (Dynamic Router). They are now resolved at startup like the other EIP options; a placeholder of a property that does not exist now fails the route at startup.

camel-kamelet - the route of a Kamelet endpoint is removed with the endpoint

A Kamelet endpoint creates a route from the Kamelet’s template. When the endpoint is created while the CamelContext runs and is shut down later, its route is now stopped and removed as well; before, it stayed behind and kept running. This applies to an endpoint that is used once and discarded (toD, recipientList or routingSlip with cacheSize=-1), which kept one more route for every message. It also applies to a route added at runtime (such as a dev-mode reload or CamelContext.addRoutes) that consumes from a Kamelet (from("kamelet:my-source")): removing that route now also removes the route of the Kamelet. A route id given in the uri (kamelet:my-source/myRoute or routeId=) is shared by the endpoints that use it, so such a route is not removed. Routes created when the CamelContext starts are not changed.

camel-undertow - the producer refuses a request target or header value with control characters

The undertow producer writes the path, query and header values of the outgoing request to the socket one character at a time, without validating them. A carriage return or line feed therefore broke the request into further lines, and a character above 0xFF was written as a single, different byte (for example U+010A became a line feed). The producer now refuses to send a request path, query or header value that contains a control character or a character above 0xFF, and fails the exchange with an IllegalArgumentException instead. A space and a tab, which do not change the framing, are still allowed. Routes that put a percent-encoded control character (such as %0D%0A) into the CamelHttpPath or CamelHttpQuery header, which the producer decodes before building the request, are the ones most likely to be affected.

camel-undertow - a WebSocket connection opened before a consumer with custom handlers started is no longer served

On a WebSocket path whose producer applies a security provider or allowed roles, a connection could be accepted while only the producer served the path, before its consumer was started for the first time. Such a connection ran the producer checks but not the custom handlers the consumer configures, as those handlers run only for consumers. Once the consumer was started, the connection was still treated as having passed the consumer checks, so its messages reached the route and it kept receiving what the producer sent.

Once a consumer that configures handlers is started on a path, a connection opened on that path before the consumer first ran is no longer served: its messages no longer reach the route and it no longer receives what the producer sends, and the connection is closed. Clients that open a connection in this window must reconnect after the consumer has started.

camel-undertow, camel-vertx-http - CamelHttpPath with a query in the endpoint uri

When the endpoint uri of an undertow or vertx-http producer has a query, the CamelHttpPath header now goes before that query, as in camel-http. Before, camel-undertow dropped the header path, and camel-vertx-http put it into the query (or dropped it when CamelHttpQuery was set). The query is chosen as before: a CamelHttpQuery header wins over the query of the endpoint.

This changes the target of routes that bridge an HTTP consumer to such a producer, as the HTTP consumers (undertow, platform-http-vertx, servlet) set CamelHttpPath from the path of the request. For example the route from("undertow:http://0.0.0.0:8080/api?matchOnUriPrefix=true").to("undertow:http://backend/svc?key=1"), or the same with a vertx-http: producer and bridgeEndpoint=true, now sends a request for /api/orders/123 to /svc/orders/123 instead of /svc. To keep the old target, remove the header before the producer, for example with removeHeader(Exchange.HTTP_PATH). The path comes from the client request, and the producer sends it with the query of the endpoint (key=1 in the example): a proxy route should validate or restrict the path, or remove the header.

camel-undertow - the bodies of requests and responses

The undertow consumer and producer change how they send and read a message body:

  • A number, a boolean, a character or an enum is sent as its text: setBody(constant(42)) sends 42 and no longer the 4 bytes of the binary form of the number. A boolean or a character was not sent at all before.

  • A Map, a List or an object that does not convert to bytes is no longer sent as an empty body. The consumer answers 500 instead of an empty 200, and logs what the body is and the marshal that writes it. The producer fails the exchange with an InvalidPayloadException for a POST, PUT or PATCH instead of sending an empty request; for the other methods, such as a GET, it sends no body as before. Marshal the body first, for example with marshal().json().

  • The consumer reads the body of a request of any method that has one (a Content-Length above 0 or a chunked body), such as a DELETE. Before, only the body of a POST, PUT or PATCH was read, and the body was null for the others.

  • The producer gives a null body for a 204 or 304 response and for the response to a HEAD request, instead of an empty byte[], as camel-http does. A 200 response with an empty body is still an empty byte[].

camel-dapr - the saveBulk and executeTransaction state operations are sent

The saveBulk and executeTransaction operations of dapr:state built their request but never sent it to Dapr, so nothing was stored and the exchange completed without an error. The requests are now sent, and the producer waits for the answer like the other state operations: the states are stored, and a request that Dapr rejects (or a sidecar that cannot be reached) fails the exchange.

camel-ibm-secrets-manager - a Map or a POJO body is stored as JSON in an arbitrary secret

The createArbitrarySecret operation, and the updateSecret operation for an arbitrary secret, now store a Map, a List or another object (POJO) in the message body as JSON. Before, the payload was the toString() of the body, such as {username=admin}. Text, bytes, a stream, a file, XML (a DOM node or a Source), a ByteBuffer, a UUID, a number, a boolean, a character or an enum is stored as text, as before. A route that relied on the toString() form must convert the body to text before the call.

camel-pubnub - the producer uses its configured channel

The pubnub producer no longer reads the CamelPubNubChannel header. This header is set by the pubnub consumer to the channel a message was received on, and the producer used it instead of the channel configured on its endpoint, so a route from one pubnub endpoint to another sent each message back to the channel it came from. The producer now uses the channel of its endpoint, for every operation. To choose a different channel for a message, set the new CamelPubNubOverrideChannel header (PubNubConstants.OVERRIDE_CHANNEL). Routes that set CamelPubNubChannel to pick the channel of a pubnub producer must set CamelPubNubOverrideChannel instead.

camel-dhis2 - the delete body, and one message per item of the collection consumer

The delete/resource producer no longer sends the message body as the resource of the request. Previously any body, also one left by an earlier step, was sent as the payload of the DELETE. To send a resource with the delete, set inBody=resource or the CamelDhis2.resource header.

The post/resource and put/resource producers now send a body of bytes, a stream or a file as the JSON it holds. Previously bytes were sent as a base64 JSON string, a file as its path and a stream failed.

The get/collection consumer now sends one message per item of the collection, a Dhis2Resource, as the other API components do for a list. Previously it sent one message per poll with an Iterator of the items as the body: remove a split of the body after the consumer.

camel-docker - the events consumer subscribes again when its event stream ends

The docker:events consumer subscribes again when its event stream fails or ends (for example when the Docker daemon restarts): after 1 second, then with a delay that doubles up to 30 seconds while the daemon cannot be reached. Before, it stayed started without receiving any event. The events of the time without a stream are not received. Stopping a docker:events or docker:stats consumer now closes its stream: before, the stream stayed open, and a route that was stopped and started again received every event once more for each restart.

camel-pinecone - the pinecone:embeddings transformer stores the text

The pinecone:embeddings data type transformer now reads the message body. The text of a LangChain4j TextSegment, or of a String that camel-langchain4j-embeddings converts to one, goes in the text_segment field of the new CamelPineconeMetadata header, which UPSERT stores as the metadata of the vector and whose fields UPDATE sets, so the pinecone:rag transformer now gives the text back. Before, the text was not stored, and pinecone:rag gave an empty String for the match of such a vector.

The pinecone:embeddings transformer now fails with an IllegalArgumentException that names the CamelLangChain4jEmbeddingsVector header when the message does not have it, as pgvector:embeddings does. Before, it failed with a NullPointerException.

camel-docker - the documented default of the socket option is false

The socket option, and the CamelDockerSocketEnabled header, were documented with the default true, but their default is false: the component connects over TCP to host and port unless socket=true is set. The documented default is now false, its real default.

Up to Camel 4.22, Camel Spring Boot applied the documented default true to the component, so a Spring Boot application that did not set camel.component.docker.socket used the unix socket of the Docker daemon, with host as the path of the socket. Such an application now connects over TCP instead (to tcp:///var/run/docker.sock:2375 when it set camel.component.docker.host=/var/run/docker.sock). A Spring Boot application that relied on the socket must set camel.component.docker.socket=true, or set the host as a URL with the unix:// scheme, such as camel.component.docker.host=unix:///var/run/docker.sock. In socket mode, the default host localhost now means /var/run/docker.sock.

camel-keycloak - the consumer reads every event since the previous poll

The keycloak consumer (eventType=events and eventType=admin-events) reads, after its first poll, all the events since the previous poll, page by page, with maxResults as the page size. Before, it read only the latest maxResults events and moved past the older ones, so when more than maxResults events happened between two polls the older ones were never consumed. A poll can now route more than maxResults events. The first poll still reads the latest maxResults events.

camel-influxdb2 - a write is synchronous and a rejected write fails the exchange

The producer wrote with the batching write API of the InfluxDB client: the point was put into a buffer of the client and sent later by a background thread, so the exchange completed before anything was written, and a write that InfluxDB rejected (a line it cannot parse, an unknown bucket, a token that is not authorized) or could not receive was only logged by the client while the route continued as if the data had been stored. The producer now writes with the synchronous write API: the exchange continues when InfluxDB has answered, and a failed write fails the exchange with a CamelInfluxDb2Exception (cause: the InfluxException of the client), which the error handler can redeliver.

Each exchange is now one write request, where the client used to collect the points of up to one second into one request, and the client no longer retries a write in the background. Routes with a high message rate can send a Points, Measurements or Records body (for example from an aggregator) to write many points with one request.

camel-elasticsearch-rest-client - the credentials are only sent to the nodes of the cluster

The user and password are now offered only to the nodes the client talks to: the hosts of hostAddressesList and, when enableSniffer is set, the nodes the sniffer finds, matching the scheme, host and port. Previously they were registered for any host. A request to another host, including the host of a node with another scheme or port, is now sent without them. A client set with the restClient option is not changed.

camel-jcr - the insert operation keeps the headers of the message

The CamelJcrInsert operation of the producer set the identifier of the new node on a new out message, so every header of the incoming message was gone after the producer. The headers are now kept, including CamelJcrOperation, CamelJcrNodeName and CamelJcrNodeType. Remove these headers before a later jcr producer in the same route if it must not use them.

camel-jgroups - a stream or a file body is sent as its content

The jgroups producer sent the body as is, so a body that is not Serializable failed with a RuntimeException caused by a java.io.NotSerializableException. A stream body (an InputStream, a Reader or a stream cache), a file from the file component and a java.nio.file.Path are now sent as a byte[] of their content, so the members of the cluster receive a byte[]. Any other body that is not Serializable now fails with an InvalidPayloadException that says to convert it first, before it reaches JGroups. An onException for NotSerializableException must handle InvalidPayloadException instead.

camel-jgroups - a channel given by the user is not closed when Camel stops

When the CamelContext stops, the jgroups component no longer closes the channel of the component (its channel option) and the channels given to an endpoint (channel=#myChannel). It disconnects them, so the node leaves the cluster, and leaves them open, as a closed JChannel cannot be connected again: the routes can now use them again when the CamelContext is started again. The channels that the endpoints create (with channelProperties or the default configuration) are closed as before. Close a channel you give to Camel yourself when the application no longer needs it, for example as a bean with a destroy method (close).

camel-jira - the producers wait for the answer of Jira

The addComment, addIssueLink, addWorkLog, attach, deleteIssue, transitionIssue and watchers producers (and addIssue for its watchers) sent their request without waiting for the answer, so the exchange continued before Jira had done the work, and a request that Jira rejected (for example no permission, or a transition that is not allowed) was not noticed. They now wait for the answer, as addIssue and updateIssue already did, and a rejected request fails the exchange with the RestClientException of the Jira client. Routes that relied on such failures being ignored must handle the exception, for example with onException(RestClientException.class).handled(true).

A producer that sends several requests can fail after some of them were done: transitionIssue can fail on its comment after the transition was made, watchers and addIssue can fail after some of the watchers were changed (addIssue sets the created issue as the message body before it adds the watchers), and a retry or redelivery repeats the earlier requests.

camel-groovy - a script that returns a GString gives a String

A Groovy expression or script that returns a GString (a double-quoted string with placeholders, such as "Hello ${body}") now gives a String. Before, the body, header or variable was set to the GString (org.codehaus.groovy.runtime.GStringImpl). A GString is not equal to a String, and components that check for a String did not take it as text: for example camel-jms sent it as a jakarta.jms.ObjectMessage, which fails because objectMessageEnabled is false by default. A Groovy predicate that returns a GString now matches as a String does: empty text and the text false do not match. Before, every GString matched.

camel-jms - JmsConsumer starts and stops its listener container through private methods

JmsConsumer.doStart and doStop no longer call the protected methods prepareAndStartListenerContainer(), stopAndDestroyListenerContainer() and testConnectionOnStartup(), so that a consumer stopped and started again (with asyncStopListener=true or asyncStartListener=true) always works on its own listener container. The methods are still there. A subclass of JmsConsumer (created by a custom JmsEndpoint.createConsumer) that overrides them to change how the listener container is started or stopped should override doStart and doStop instead.

camel-jms - preserveMessageQos uses the time to live of the endpoint for a message without expiration

With preserveMessageQos=true, a message without a JMSExpiration (header missing or 0) was sent with no time to live, so it never expired, even when the endpoint has a timeToLive and, for request/reply, although Camel sets the requestTimeout as time to live otherwise. Such a message now gets the time to live of the endpoint, as the option documents. Set disableTimeToLive=true to send it without time to live.

camel-sjms - preserveMessageQos also applies to InOnly sends

With preserveMessageQos=true, an InOnly send ignored the JMSPriority, JMSDeliveryMode and JMSExpiration headers, and used the QoS of the endpoint (or of the JMS client); only request/reply used them. An InOnly send now uses these headers, as camel-jms does. A message whose JMSExpiration is in the past expires at once, and a message consumed from JMS and sent on keeps the priority, delivery mode and expiration of the consumed message. The replies of a consumer endpoint with preserveMessageQos=true use the headers of the reply message too; these are still the headers of the request unless the route changes or removes them. An exception reply (transferException=true) has no headers and uses the QoS of the endpoint. camel-sjms2 behaves the same.

A header that is not set falls back to the endpoint, as the option documents, also with explicitQosEnabled=false: to the priority (a priority of 0 is not applied, as with explicitQosEnabled) and the deliveryPersistent (or deliveryMode) of the endpoint, and, for a message without JMSExpiration (header missing or 0), to the time to live of the endpoint (timeToLive, or the requestTimeout for request/reply). This also changes request/reply: before, a request without JMSPriority was sent with the priority of the new JMS message (the default of the JMS client, 0 with ActiveMQ Classic), a request without JMSDeliveryMode was persistent whatever the endpoint said, and a request without JMSExpiration never expired. To send a request without time to live, set disableTimeToLive=true and no timeToLive. Remove preserveMessageQos=true to keep using only the QoS of the endpoint for InOnly sends.

camel-sjms - an InOut reply that cannot be sent fails the message, and jmsMessageType=Stream

When an InOut consumer could not send its reply (for example, a reply body that cannot be mapped to a JMS message), the error was set on the exchange but ignored: the incoming message was committed or acknowledged, no reply was sent and nothing was logged. The incoming message now fails as when the route fails: it is rolled back when transacted=true (or recovered with CLIENT_ACKNOWLEDGE), so the broker delivers it again, and the error is logged.

With jmsMessageType=Stream, sending failed with a NullPointerException. The body is now written to a StreamMessage. A jmsMessageType that cannot be sent (Message) fails with a JMSException that names the type. camel-sjms2 behaves the same.

camel-jms, camel-sjms - a body that maps to no JMS message type fails the exchange

When the body could not be mapped to a JMS message type (for example a POJO that is not Serializable and has no type converter to a File or an InputStream), the producer logged a WARN and sent an empty jakarta.jms.Message, so the body was lost and the exchange succeeded. The same happened to the reply of an InOut consumer, and the caller got a null body. Sending now fails with a jakarta.jms.MessageFormatException that names the class of the body: the exchange of the producer fails, and for a reply the incoming message fails as when the route fails (rolled back when transacted=true) and the caller gets no reply. A null body is still sent as an empty jakarta.jms.Message.

To send such a body, implement java.io.Serializable (and enable objectMessageEnabled), force a type with the jmsMessageType option, or marshal the body (for example to JSON) before sending it. This applies to the components that build on camel-jms (camel-amqp, camel-activemq, camel-activemq6) and to camel-sjms2.

camel-jms, camel-sjms - text that is not a String is sent as a TextMessage

A body that is text but not a String (a CharSequence such as a StringBuilder or a Groovy GString) is now sent as a jakarta.jms.TextMessage, as a String is, and a consumer gets it as a String. Before, it was sent as a jakarta.jms.ObjectMessage, as it is Serializable. That failed, as objectMessageEnabled is false by default. With objectMessageEnabled=true the consumer got the serialized object, or failed on the deserialization filter when the class is not allowed (a GString is not). To send such a body as an ObjectMessage, set jmsMessageType=Object.

When a body would be sent as an ObjectMessage and objectMessageEnabled is false, the error now names the class of the body. It also says how to send the body as text: convert it to a String first, or marshal it (for example to JSON). This applies to the components that build on camel-jms (camel-amqp, camel-activemq, camel-activemq6) and to camel-sjms2.

camel-pulsar - the producer continues the sequence ids of the broker

The pulsar producer no longer passes the default initialSequenceId=-1 to the Pulsar client, so the client continues after the last sequence id the broker has for the producerName. Before, every start began again at sequence id 0, and with deduplication enabled on the broker the messages sent after a restart were dropped as duplicates (the send succeeded) until the sequence ids passed the ones of the previous run. A configured initialSequenceId of 0 or more is still used as before.

camel-pulsar - the deadLetterTopic option applies in retry mode

With enableRetry=true, the consumer did not pass deadLetterTopic to the Pulsar client, so a message that failed maxRedeliverCount times was sent to the client’s default dead letter topic (<topic>-<subscription>-DLQ, or <subscription>-DLQ when that topic already exists) instead of the configured one. It is now sent to deadLetterTopic. Messages already in the default topic stay there.

With enableRetry=true and no maxRedeliverCount, the consumer failed to start with a NullPointerException. It now starts without a dead letter policy, as the maxRedeliverCount option documents, and the Pulsar client applies its own in retry mode: 16 redeliveries and its default retry letter and dead letter topics. As without retry mode, retryLetterTopic and deadLetterTopic only apply when maxRedeliverCount is set. The consumer logs a WARN when it starts with one of these topics but no maxRedeliverCount (and for retryLetterTopic without enableRetry).

camel-twitter - twitter-search applies its query options again

Since Camel 4.0 (the upgrade to Twitter4J 4.1, whose Query is immutable) the twitter-search endpoint sent only the keywords. The consumer ignored filterOld (default true), lang, count and the geolocation options, so it routed the same tweets again at every poll. The producer ignored sinceId, lang, count and the CamelTwitterSinceId, CamelTwitterMaxId, CamelTwitterSearchLanguage and CamelTwitterCount headers. The options are applied again: a poll returns only the tweets newer than the last one consumed, and a page has count tweets (default 5; Twitter’s own default of 15 applied before). Raise count and numberOfPages if a search can match more tweets than that between two polls. A consumer with type=direct returned nothing when keywords were given, and now runs the search at startup.

camel-kudu - a row error of Kudu fails the exchange

The insert, delete, update and upsert operations of the producer ignored the answer of Kudu. The Kudu client does not throw for a row that Kudu rejected; it returns a row error, for example for an insert of a key that is already present, or for an update or a delete of a row that is not found. Such an exchange completed as if the row had been written. A write that timed out or could not reach a tablet server was reported the same way, and completed the exchange too; for such a write it is not known whether the row was written. The producer now fails the exchange in both cases with a KuduRowErrorException (getRowError() returns the row error of the Kudu client, whose status tells the kind of error). Routes that relied on rejected rows being skipped silently can use the upsert operation, or handle the exception, for example with onException(KuduRowErrorException.class).handled(true).

The producer also closes the Kudu session of each operation. The sessions stayed registered with the Kudu client until the client was closed, one more for every exchange.

camel-ldif - the connection is bound for every message, and the status of a failed change

The producer closes the LdapConnection after each message. A connection bean that is not a prototype then reconnected without a bind for the next message, so its changes failed with modifications require authentication. The producer now binds such a connection again, with the name and credentials of its LdapConnectionConfig. A route that called bind() on the connection before each message can drop that step.

The status of a failed change is now the LDAP result code, the DN of the entry and the message from the server, such as entryAlreadyExists (68) on uid=test1,ou=test,dc=example,dc=org, instead of only the message (which OpenLDAP leaves empty for some failures). A successful change is still success.

An empty or null body now gives an empty String as the result, instead of null. A missing connection bean fails with a NoSuchBeanException instead of a NullPointerException.

camel-mllp - the producer verifies the host name of the server over TLS

When sslContextParameters is set (on the endpoint, or with useGlobalSslContextParameters on the component), the MLLP producer now checks that the certificate of the server matches the host it connects to, with the same rules as an HTTPS client. Before, it accepted any certificate trusted by the trust store, whatever host the certificate was issued for.

A producer that connects with a host name or an IP address that the certificate does not list now fails the TLS handshake; an IP address must be listed as a subject alternative name of the certificate, and a producer URI without a host name (such as mllp://8888) cannot pass the check. Use a certificate that matches the host of the endpoint, or set the new hostnameVerification option to false to keep the previous behaviour.

camel-mybatis - the body is kept with InOut, and an Insert, Update or Delete without a body fails when the statement has parameters

With an InOut exchange, the Insert, InsertList, Update, UpdateList, Delete and DeleteList statement types now keep the message body, as they already did with InOnly. Before, the body was null after the call unless the outputHeader option was set. The result is still in the CamelMyBatisResult header (or the outputHeader).

An Insert, Update or Delete with a null body (or a missing inputHeader) now fails with an IllegalArgumentException when the statement has parameters. Before, MyBatis ran the statement with NULL for every parameter: an Insert stored a row of nulls, and an Update without a WHERE set columns to null in every row. A statement without parameters still runs with a null body.

camel-aws2-s3 - streaming upload with the random or timestamp naming strategy names the first file too

With streamingUploadMode=true and namingStrategy=random (or timestamp), the first file uploaded by a producer was named after keyName alone (for example camel.txt), and only the later files got a UUID (or a timestamp) after keyName. A producer that was started again (a restart of the route or of the application), or another producer with the same keyName, wrote its first file to the same key and replaced the first file written before. Every file now gets the UUID (or the timestamp), the first one too: camel-<uuid>.txt. The progressive naming strategy is unchanged.

camel-aws2-s3 - streaming upload writes concurrent exchanges one at a time

With streamingUploadMode=true (without timestampGroupingEnabled), the producer now writes one exchange at a time, so exchanges sent concurrently (concurrent consumers, parallel splits, threads()) no longer lose objects or parts. The body of an exchange is read while the other exchanges of the same producer wait, and bodies that complete an upload of their own (batchSize or more, or batchMessageNumber=1) are no longer uploaded in parallel. To upload large bodies in parallel, use the producer without streamingUploadMode.

camel-atom - sortEntries sorts by the updated date and routes the oldest entry first

With sortEntries=true the atom consumer sorted the entries of a feed by their published date and routed them from the newest to the oldest one. With the default idempotent strategy (which skips an entry that is not newer than the one routed before it) only some entries of a feed were routed, and the others were skipped.

This is fixed. The entries are now sorted by their updated date (by the published date for an entry without an updated date), which is the date the idempotent strategy compares, and they are routed from the oldest to the newest one, as camel-rss does and as Camel 3.x did. Entries are no longer skipped because of the routing order, also when an older entry was updated after newer ones were published.

Routes using sortEntries=true with idempotent=false get the entries in this order too: the entry that was updated first is routed first, where the entry that was published last was routed first before.

camel-opensearch - the credentials are only sent to the nodes of the cluster

The user and password are now offered only to the nodes the client talks to: the hosts of hostAddresses and, when enableSniffer is set, the nodes the sniffer finds, matching the scheme, host and port. Every node of a multi-host cluster still receives them. Previously they were registered for any host. A request to another host, including the host of a node with another scheme or port, is now sent without them. A client set with the client or openSearchClient option is not changed.

camel-aws-cloudtrail - the consumer looks again for events that CloudTrail shows late

The consumer polls from the newest event time it has seen, so an event that CloudTrail made visible after a newer one was never routed. Now, every lookBackInterval (default 1 minute), the consumer also looks up the events from lookBackWindow (default 10 minutes) before now up to that newest event time, and routes those it has not routed yet. The normal polls send the same LookupEvents requests as before. The late-event lookup adds ceil(N / maxResults) requests per lookBackInterval, at most one per poll, where N is the number of events in that range: with the defaults one request per minute up to 50 events in 10 minutes, 20 per minute for 1000 events, and none once no event was seen for 10 minutes. AWS allows two LookupEvents requests per second per account and Region. Set lookBackWindow=0 to disable the late-event lookup.

camel-aws2-s3 - a listed object that cannot be fetched is skipped

When the consumer lists the bucket, a failure to get one of the listed objects (or to download its content) no longer fails the poll, as long as another object of that listing could be got: the object is skipped, the other listed objects are consumed in that poll, and the skipped object is tried again by a later poll. An object that no longer exists (for example deleted by another consumer after the listing) is skipped with a log line at DEBUG. Any other failure is given to the exception handler of the consumer, with the key in the message: by default that is a WARN with the stack trace, and with bridgeErrorHandler=true the route’s error handler gets it. This happens at every poll that lists an object that can never be read (for example archived, or access denied), until it can be read or is removed from the bucket. Such a poll is a successful poll, so it does not count for backoffErrorThreshold.

The poll still fails, as before, when none of the listed objects could be got and at least one get failed with an error other than that the object no longer exists (for example expired credentials, no permission to get objects, throttling): the failure of the first object is then the failure of the poll, and backoffErrorThreshold applies. In both cases the objects are no longer kept in the inProgressRepository; before, the object (or a new object with the same key) was skipped by all later polls until the endpoint was restarted. With the fileName option, a failed get still fails the poll.

camel-aws2-s3 - the producer stores the whole body when the content-length header is stale

When the length of the body is known without reading it (a byte[], String, file, Path or cached stream), the producer now uploads it with that length, and the CamelAwsS3ContentLength header is only used for a stream whose length is not known. Previously the header took precedence: after a route changed the body of an object read by the aws2-s3 consumer (which sets the header), a longer body was stored cut to the old length and a shorter one failed.

camel-minio - the producer stores the whole body when the content-length header is stale

When the length of the body is known without reading it (a file, byte[], String, Path or cached stream), the producer now uploads it with that length, and the CamelMinioContentLength header is only used for a stream whose length is not known. Previously the header took precedence, also over the length of a java.io.File body: after a route changed the body of an object read by the minio consumer (which sets the header), a longer body was stored cut to the old length and a shorter one failed.

camel-mina - the host name of the server is verified over SSL

When sslContextParameters is set (on the endpoint, or with useGlobalSslContextParameters on the component), the producer, and the consumer with clientMode=true, now check that the certificate of the server matches the host of the endpoint, with the same rules as an HTTPS client. Before, any certificate trusted by the trust store was accepted, whatever host the certificate was issued for.

A connection to a host name or an IP address that the certificate does not list now fails the SSL handshake; an IP address must be listed as a subject alternative name of the certificate. Use a certificate that matches the host of the endpoint, or set the new hostnameVerification option to false to keep the previous behaviour.

MINA does not pass a failed handshake on as an error: the producer fails with a timeout (after writeTimeout, or timeout while waiting for a reply), and the handshake error is logged at ERROR level by org.apache.mina.filter.ssl.SslHandler. A producer that starts timing out after the upgrade should be checked for a certificate that does not list the host.

camel-mina - the producer closes its session when a request times out

A synchronous mina producer (sync=true) now always closes its session when a request times out while waiting for a reply. Because a synchronous producer is pooled and reused for later exchanges, a session kept open after a timeout could receive the late reply to the timed-out exchange and hand it to the next exchange that reused the same producer. The producer now discards that session, so the next exchange opens a fresh one.

The default behaviour is unchanged: disconnectOnNoReply defaults to true, which already closed the session on a timeout. Setting disconnectOnNoReply=false no longer keeps the producer session open after a timeout. The option now has no effect: the consumer never used it, and always closes the session when it has no reply to send.

camel-ftp - the host name of the FTPS server is verified

The ftps endpoint now checks, on the control connection, that the certificate of the server matches the host of the endpoint, with the same rules as an HTTPS client, with sslContextParameters as well as with the ftpClient.trustStore.* options. Before, any certificate accepted by the trust manager was accepted, whatever host the certificate was issued for. Data connections are not checked by host name.

A connection to a host name or an IP address that the certificate does not list now fails the TLS handshake, before login; an IP address must be listed as a subject alternative name of the certificate. Use a certificate that matches the host of the endpoint, or set the new hostnameVerification option to false to keep the previous behaviour. A custom ftpClient set on the endpoint is not changed.

camel-ftp, camel-sftp, camel-ftps, camel-mina-sftp, camel-smb - ${file:name} in moveExisting is the name of the file

With fileExist=Move, the ${file:name} token of moveExisting is now the name of the existing file in its directory, as the producer of ftp, ftps and sftp already used it with the default stepwise=true. So with stepwise=false, with a tempFileName, in mina-sftp and in smb, a relative moveExisting built from ${file:name}, ${file:onlyname} or literal text now gives the same result as with stepwise=true. Before, ${file:name} was the path of the file from the login directory (or the share), and since a relative moveExisting is put below the directory of the file, moveExisting=backup/${file:name} for hello.txt in the directory inbox/orders moved the existing file to inbox/orders/backup/inbox/orders/hello.txt; it is now moved to inbox/orders/backup/hello.txt. The tokens ${file:name.noext} and ${file:name.noext.single} read the same name and change the same way: inbox/orders/hello is now hello. A backup kept in the nested directory is no longer found there, for example by the check for an existing backup when eagerDeleteTargetFile=false, or by a job that reads the backups. ${file:parent}, ${file:onlyname} and ${file:ext} are unchanged, and so is a moveExisting without ${…​} such as moveExisting=backup.

In the same cases, a relative result is now left as it is only when it starts with the directory of the file as whole path segments (inbox/orders/backup/hello.txt); any other relative result is put below that directory. Before, a result that only started with the same characters was left as it was. So for a file in inbox/orders, moveExisting=${file:parent}-old/${file:onlyname} and moveExisting=inbox/orders-old/${file:onlyname} now move it to inbox/orders/inbox/orders-old/hello.txt instead of inbox/orders-old/hello.txt; to keep a directory next to the directory of the file, use a path relative to it, such as moveExisting=../orders-old/${file:onlyname} (camel-smb resolves the .. itself, as SMB servers do not accept it in a path). Such a path can lead out of the directory of the endpoint: jailStartingDirectory does not apply to moveExisting. The file component is not changed: there ${file:name} is still the path of the file, and the check is as before.

camel-aws-secrets-manager - the CloudTrail refresh task reads every event since the last check

With camel.vault.aws.refreshEnabled=true and without SQS notification, each check now reads every page of the PutSecretValue and UpdateSecret events since the previous check, including the events in the same second as the newest event seen before, so an update is no longer missed when more events than one page happened in between. The first check looks for the updates made after the task started: an update made before the start no longer reloads the context. A check sends two LookupEvents requests, one per event name, and one more per further 50 updates (before: always one request, for all Secrets Manager events); reads of secrets no longer add requests. LookupEvents allows two requests per second per account and Region.

camel-aws2-s3 - a timestamp window uploaded more than once is stored in more than one object

With streamingUploadMode=true and timestampGroupingEnabled=true, the upload of a time window is completed when it reaches batchMessageNumber messages (10 by default) or batchSize bytes, or when streamingUploadTimeout runs. A later message of the same window started a new upload with the same key, and S3 replaced the object of the first upload with it, so the messages of the earlier uploads were lost (unless the bucket is versioned). A later upload of a window now gets the number of the uploads of that window before it after the time range: for example grouped-messages_20240101_0800_0800-0805.txt, then grouped-messages_20240101_0800_0800-0805-1.txt, grouped-messages_20240101_0800_0800-0805-2.txt, and so on. The first upload of a window keeps the name it had. The first time a producer uploads a window, it lists the objects of the window in the bucket (one ListObjectsV2 request per window), so after a restart it continues after the highest number stored; this needs the s3:ListBucket permission, and without it the producer logs a WARN and numbers the uploads of the window from the first one. These keys do not sort in upload order (-1 sorts before the first upload’s name, -10 before -2). Producers running at the same time with the same keyName still need distinct key names.

camel-aws2-kinesis - the consumer continues after the last record when a shard iterator expired

When a cached shard iterator has expired (an iterator is valid for 5 minutes, so after a route was suspended for longer, or with a delay or back-off above 5 minutes), the consumer now continues after the last record it routed from that shard. Previously it requested a new iterator of the configured iteratorType: with TRIM_HORIZON (the default) it routed all the records of the shard again, with LATEST it skipped the records stored meanwhile.

A shard created by a split or a merge while the consumer is running is now read from its first record, whatever the iteratorType. Previously, with LATEST, the records stored in the new shard before the consumer listed it were skipped. The shards that exist when the consumer starts are read from the configured iteratorType, as before.

camel-graphql - the credentials are only sent to the authority of httpUri

The accessToken (with its jwtAuthorizationType) is now added to a request only when the request goes to the scheme, host and port of httpUri, and the username and password are registered for that authority only. Previously neither was bound to the authority of httpUri. A request to another authority, including the same host with another scheme or port, is now sent without them. A route that needs to authenticate to another authority must address it in httpUri. When proxyHost is set, the username and password are no longer offered to the proxy when it asks for authentication.

The producer also no longer takes the request URL from the CamelHttpUri, CamelHttpPath, CamelHttpQuery and other camel-http control headers of the message. It did so since Camel 4.19, when the component moved onto camel-http; the request now always goes to httpUri, as it did before. For the same reason the CamelHttpMethod and CamelHttpProtocolVersion headers no longer apply, and the request is always a POST.

camel-elasticsearch - the credentials are only sent to the nodes of the cluster

The user and password are now offered only to the nodes the client talks to: the hosts of hostAddresses and, when enableSniffer is set, the nodes the sniffer finds, matching the scheme, host and port. Previously they were registered for any host. A request to another host, including the host of a node with another scheme or port, is now sent without them. A client set with the client option is not changed.

camel-elasticsearch, camel-opensearch - the client set on the component is not closed by Camel

A RestClient set with the client option of the component, or autowired from the registry, is shared by all the endpoints of the component. A producer that stopped (its route stopped or removed, or Camel stopped), and with disconnect=true every request, closed it, and from then on every endpoint of the component failed its requests until the application was restarted. Camel now closes only the clients its producers create from hostAddresses. Close a client you set on the component yourself when the application no longer needs it (a client that is a Spring bean is closed by Spring).

camel-jetty - a multipart text field that occurs several times

A text field of a multipart/form-data request that occurs several times (for example a group of checkboxes) is now mapped to a header whose value is the list of all its values, as a repeated query or form-urlencoded parameter already is. Before, the header had only the first value and the others were lost. Jetty also returns the query string values of the same name, so a name that is in both the query string and a multipart text field is mapped to a list of all these values too, as it already is for a form-urlencoded request.

camel-servlet and camel-jetty - gzip request body that arrives after the headers

The servlet and Jetty consumers uncompressed a request body with Content-Encoding: gzip only when some of its bytes had already arrived when the consumer started to read it. When the client sent the body later than the headers (for example a client that sends Expect: 100-continue), the route got the compressed bytes. Such a body is now uncompressed like any other gzip request body. A route that uncompressed the body itself in that case must no longer do so. An empty body is passed on as before, and so is the body of an exchange with the CamelSkipGzipEncoding property. A request with Content-Encoding: gzip whose body is not gzip data now fails with a ZipException before the route is called (Jetty answers 500) also when the body arrives after the headers, where the route got the bytes as they were sent; it already failed in this way when the body had arrived. With disableStreamCache=true the consumer now waits for the first bytes (the GZIP header) of a gzip request body before it calls the route; with stream caching, the default, it already read the whole body before that. The same applies to every consumer that reads the request body with DefaultHttpBinding.parseBody of camel-http-common, such as the Spring Boot platform-http consumer with useStreaming=true.

camel-undertow - form fields are decoded as UTF-8

The consumer now decodes the fields of an application/x-www-form-urlencoded or multipart/form-data request that declares no charset as UTF-8, the encoding browsers use. Before, Undertow’s default ISO-8859-1 was used, so café became café in the headers and the body map. A charset that the request or a part declares is still used: a client that sends ISO-8859-1 form data without declaring a charset now gets U+FFFD replacement characters instead of its text, and must declare its charset (for example application/x-www-form-urlencoded; charset=ISO-8859-1).

camel-milvus, camel-neo4j, camel-qdrant, camel-weaviate - the embeddings transformers without the embedding header, and the text of neo4j:rag

The milvus:embeddings, neo4j:embeddings, qdrant:embeddings and weaviate:embeddings data type transformers now fail with an IllegalArgumentException that names the CamelLangChain4jEmbeddingsVector header when the message does not have it, as pgvector:embeddings does. Before, they failed with a NullPointerException.

The neo4j:rag data type transformer now converts a text value that is not a String, such as a number, to a String, and gives an empty String for a null text, as for a row without text. Before, a value of another type failed with a ClassCastException, and a null text gave a null element.

camel-undertow - rest-dsl and plain consumers on the same port are both served

When rest-dsl consumers and plain undertow: consumers use the same port, both kinds are now served. Before, only the kind that registered first on the port was served, the other answered 404, and stopping the last consumer of one kind stopped the server for the other. A request now goes to the rest-dsl consumers first, and to the plain endpoints when no rest-dsl consumer matches it, so a plain catch-all (for example matchOnUriPrefix=true on /, for static files) and a REST API can share a port. When a rest-dsl path and a plain path both match a request, the rest-dsl consumer gets it, even when the plain path is more specific (for example a plain /users/me next to a rest-dsl /users/{id}): give the plain endpoint another path or port. A request under the static part of a plain endpoint path with a template (such as /api/x/y for /api/{name}) that no template matches now gets 404 (it got 405 before).

camel-undertow - the static route of a base path answers it next to a path template

When one route consumes a path template, such as /orders/{id}, and another route the static path of its base path (/orders, exact or with matchOnUriPrefix=true), a request for /orders itself (also /orders/, and / for templates of the root path) now goes to the static route. Before, the template route answered it with an empty id when it had started first, or when the static route used matchOnUriPrefix. When the static route restricts the HTTP methods (httpMethodRestrict), such a request with another method now gets 405 instead of reaching the template route. A matchOnUriPrefix route of the base path now also starts when the template route started first (it failed with Duplicate handlers on a path). An exact static route (without matchOnUriPrefix) now answers only its own path: a deeper path that no template matches, such as /orders/a/b, gets 404, as without the template route (before, the static route answered it, or it got 405 when the static route restricted the methods). To keep the old behaviour, where the template route answers /orders with an empty id, drop the static route of the base path: a template route alone still serves the base path.

camel-huaweicloud-obs - a listed object that cannot be got is skipped

When the consumer lists the bucket, a failure to get one of the listed objects (for example access denied, or an archived object that is not restored) no longer fails the poll, as long as another object of that listing could be got: the object is skipped, the other listed objects are consumed, and the skipped object is tried again by a later poll. The failure is given to the exception handler of the consumer, with the key in the message (by default a WARN with the stack trace, at every poll that lists the object). Before, the poll failed, so the other objects listed with it were not consumed either, and when the bucket fitted in one listing (maxMessagesPerPoll, default 10) the consumer consumed no object at all. The poll still fails when none of the listed objects could be got. With the fileName option, a failed get still fails the poll.

camel-huaweicloud-dms - createInstance fails on a body that is not the request

The createInstance operation of hwcloud-dms now reads the JSON of a CreateInstanceRequestBody from text, bytes, a stream or a file, such as the body of an HTTP request or the file of a file consumer, and from a Map with the same field names, such as the JSON unmarshalled by an earlier step. Before, only a String body was read: JSON given as bytes, a stream, a file or a Map was ignored without a warning, and the instance was created from the endpoint options and the exchange properties.

A body that is not a CreateInstanceRequestBody or a valid JSON representation of it now fails the exchange with an IllegalArgumentException. Before, such a body was ignored (a String with a WARN log) and the instance was created from the options. The instance is now created from the options only when the exchange has no body, or an empty one. A route that creates the instance from the options while it carries another body, such as a text from an earlier step, must clear the body first, for example with setBody and an empty constant.

updateInstance reads the JSON of an UpdateInstanceRequestBody from bytes, a stream, a file or a Map too. A body that is not a valid JSON of it now fails with an IllegalArgumentException, where it failed with the exception of Jackson.

camel-github2 - consumers and items of a poll that were not routed

The github2 consumers (commit, event, pullRequest, pullRequestComment, tag) remembered every item of a poll as consumed before routing it. Items that were not routed because the route was suspended during the poll were never routed. They are now routed by the first poll after the route is resumed.

The commit consumer also lost the new commits of a poll in which the GitHub lookup of an author or a committer failed (for example a 404 for a deleted account, or a server error). Such a commit is now routed, and a WARN is logged for it. Its CamelGitHubCommitAuthor and CamelGitHubCommitCommitter headers then have the name that git recorded in the commit instead of the name of the GitHub profile, and are not set when the commit has no name either.

camel-huaweicloud-functiongraph - the whole result of a function without a body field

The invokeFunction operation of hwcloud-functiongraph now gives the whole result of the function as the message body when the result is not a JSON object with a top-level body field. Before, the body was null when the result was a JSON object without a body field, and the call failed with a Gson JsonSyntaxException when the result was a JSON array, another JSON value or plain text. A result with a body field, such as the result of a function behind an API Gateway trigger, still gives that field, and no body when the field is null. A route that tested for a null body after the call must now test the result instead.

camel-slack - consumer and a suspended route

When a route is suspended (RouteController.suspendRoute, also from a route policy that suspends the route such as DurationRoutePolicy, or CamelContext.suspend()) while the slack consumer routes the messages of a poll, the consumer stops after the message that is being routed. The other messages of that poll were skipped when the route was resumed. They are now kept and routed by the first poll after the resume, together with the messages posted in the meantime. They are kept in memory only: a route that is stopped and started again does not route them.

camel-huaweicloud-iam - the JSON results use the field names of the IAM API

The operations of hwcloud-iam now write their results (the users or the groups of listUsers, listGroups and getGroupUsers, and the user or the group of getUser, updateUser and updateGroup) as JSON with the field names of the IAM API. These are the names that the JSON requests of updateUser and updateGroup already used, and that hwcloud-dms writes too. Before, the results used the Java field names of the Huawei Cloud SDK. For example, domainId is now domain_id, pwdStatus is pwd_status, lastProjectId is last_project_id, passwordExpiresAt is password_expires_at, createTime is create_time and isDomainOwner is is_domain_owner. A route that reads these fields, for example with the JSONPath $.domainId, must use the new names.

The component no longer depends on Gson.

camel-mail - mails of a poll that were not routed, with an idempotentRepository

With an idempotentRepository, the mail consumer now removes the keys of the mails that a poll retrieved but did not route (the consumer was stopped in the middle of the batch, or the poll failed on a later mail), so that a later poll routes them. Before, they were skipped as duplicates by every later poll. As a consequence, a mail that the consumer cannot read (with the default skipFailedMessage=false and handleFailedMessage=false) now fails every poll until it is removed, as it does without an idempotentRepository. Before, it failed only the first poll and was then skipped, together with the mails retrieved before it. Set skipFailedMessage or handleFailedMessage to skip such a mail.

camel-huaweicloud-smn - a Map, a List or a POJO is published as JSON, and the CamelHwCloudSmnMessageId property

The hwcloud-smn producer now publishes a Map, a List or a POJO in the message body as JSON. Before, it published the toString() of the body, such as {orderId=1, status=shipped} for a Map. A Map or a List that cannot be written as JSON now fails with an IllegalArgumentException. Text, bytes, a stream, a file, an XML Source or DOM node, and a single value such as a number are published as text, as before.

The id of the published message is now set in the CamelHwCloudSmnMessageId exchange property (SmnProperties.MESSAGE_ID). The CamelHwCloudSmnMesssageId exchange property (SmnProperties.SERVICE_MESSAGE_ID), whose name has a typo, is still set to the same id, but it is deprecated and will be removed in a future release: read CamelHwCloudSmnMessageId instead.

camel-smpp - the host name of the SMSC is verified over SSL

With smpps (or usingSSL=true), directly or through an HTTP proxy, the connection now checks that the certificate of the SMSC matches the host it connects to, with the same rules as an HTTPS client. Before, any certificate trusted by the JVM was accepted, whatever host it was issued for.

A connection to a host name or an IP address that the certificate does not list now fails the SSL handshake; an IP address must be listed as a subject alternative name of the certificate. Use a certificate that matches the host of the endpoint, or set the new hostnameVerification option to false to keep the previous behaviour.

A mismatch shows as the producer or consumer failing to start, with an IOException whose cause names the host that was checked:

java.io.IOException: No subject alternative names matching IP address 127.0.0.1 found
    Caused by: javax.net.ssl.SSLHandshakeException: No subject alternative names matching IP address 127.0.0.1 found

camel-netty-http - the request body read as an InputStream

The body of a request received by the consumer is a NettyChannelBufferStreamCache. Its read() method now returns a byte as a value from 0 to 255, and -1 at the end of the body, as java.io.InputStream requires. Before, a byte from 0x80 was returned as a negative value (0xFF as -1, which a reader takes for the end of the body), and the method threw an IndexOutOfBoundsException at the end. markSupported() now returns false (mark(int) never did anything): a reader that needs mark support has to wrap the stream, for example in a BufferedInputStream. reset() goes back to the start of the body, as before.

camel-netty-http - securityConfiguration is copied per endpoint

When securityConfiguration is set on the NettyHttpComponent, each endpoint now receives an independent copy of the configuration. Modifying securityConfiguration.* options on an endpoint URI no longer alters the component-level configuration or other endpoints.

camel-netty-http - the endpoint URI includes the endpoint options

The endpoint URI of a netty-http endpoint (Endpoint.getEndpointUri()) is now its full URI, with the component scheme and the endpoint options, as for netty and most other components. For example, the endpoint netty-http:http://localhost:8080/foo?throwExceptionOnFailure=false now has the endpoint URI netty-http://http://localhost:8080/foo?throwExceptionOnFailure=false. Before, it was http://localhost:8080/foo.

Endpoints whose URIs differ only in their options are therefore no longer equal, and each of them gets its own producer when a ProducerTemplate, toD or another EIP with a dynamic URI sends to them. Before, these endpoints used the producer of the endpoint that was used first, with the options of that endpoint.

The request is still sent to the same HTTP address, which NettyHttpEndpoint.getAddressUri() returns. The new endpoint URI shows in:

  • the name of an auto-mocked endpoint, for example mock:netty-http:http://localhost:8080/foo instead of mock:http:localhost:8080/foo. A mockEndpoints pattern such as http* no longer matches a netty-http endpoint, and netty-http* does.

  • the name of the endpoint in JMX, for example netty-http://http://0.0.0.0:8080/foo.

  • the CamelToEndpoint exchange property.

  • tracing: a netty-http endpoint now gets the span decorator of netty-http instead of the one of http or https, and the include and exclude patterns are matched against the new endpoint URI.

  • the endpointName tag of the Micrometer exchange event metrics.

camel-netty - hostnameVerification verifies the host instead of failing

hostnameVerification=true on a netty producer, or on a consumer with clientMode=true, now checks that the certificate of the server matches the host the endpoint connects to. Before, it made the TLS handshake fail for every certificate, matching or not, with CertificateException: Hostname or IP address is undefined - the engine was created without a peer host, so the JDK had no name to compare against.

A route that had the option set was therefore not connecting at all, and needs no change. A route that sets it while connecting to a host the certificate does not list - most commonly an IP address, which has to appear as a subject alternative name of its own - now fails the handshake with the mismatch as the reason. The default is unchanged: hostnameVerification is false, and the host is then not checked.

Enabling the option also makes the JDK send the host as an SNI server name, as camel-netty-http already did. The default path still creates the engine without a peer and sends no SNI.

On a consumer that acts as a server the option is now ignored instead of being passed to the engine. It never verified anything there - the JDK does not support host name verification on the server side of a handshake, and rejected the client certificate with Endpoint Identification Algorithm HTTPS is not supported on the server side - so a consumer that asks for client certificates and had the option set now completes the handshake instead of failing it.

camel-netty-http - hostnameVerification is ignored on a consumer

A netty-http consumer no longer passes hostnameVerification=true to its TLS engine, as netty consumers already do not (see above). The JDK does not support host name verification on the server side of a handshake: a client that presented a certificate failed the handshake with Endpoint Identification Algorithm HTTPS is not supported on the server side, and a client that presented none was not checked at all. A consumer with needClientAuth=true and the option set now accepts trusted client certificates instead of rejecting all of them. Consumers that share a server through nettySharedHttpServer never applied the option, and are unchanged. The option still verifies the host on a netty-http producer.

camel-alibaba-oss - a listed object that cannot be got is skipped

When the consumer lists the bucket, a failure to get one of the listed objects (for example access denied, or an archived object that is not restored) no longer fails the poll, as long as another object of that listing could be got: the object is skipped, the other listed objects are consumed, and the skipped object is tried again by a later poll. The failure is given to the exception handler of the consumer, with the key in the message (by default a WARN with the stack trace, at every poll that lists the object). Before, the poll failed and the next poll listed the same page again, so one such object stopped the consumer from consuming any object. The poll still fails when none of the listed objects could be got, and the next poll then reads the next page of the listing.

camel-alibaba-fc - invokeFunction sends a stream or a file as its bytes

The invokeFunction operation of alibaba-fc now sends a stream or a file body (an InputStream, a java.io.File, or a GenericFile from a file consumer) as its bytes, as they are. Before, it read such a body as text and sent that text in UTF-8, which changed binary data. A String is still sent in UTF-8, a byte[] as it is, and no body as an empty payload.

A body that does not convert to bytes, such as a Map or a POJO, is no longer sent as its toString(): it now fails with an InvalidPayloadException. Marshal such a body first, for example to JSON with marshal().json().

camel-dropbox - search and get read every page of the result

The search operation (producer and consumer) now returns every match that the service gives. Previously it returned only the first page of the matches (the Dropbox SDK asks for 100 matches per page), and the rest was dropped without any sign. A search with many matches now sends one request per page, for the consumer at each poll. When one of these requests fails, the operation fails and no match is returned, as before.

The get operation on a folder (producer and consumer) now downloads every file of the folder. Previously it downloaded only the files of the first page of the folder listing. As before, the content of all the files is in the message body, so a large folder needs more memory than it did when only the first page was read.

camel-alibaba-kms - encrypt encrypts a stream or a file as its bytes

The encrypt operation of alibaba-kms now encrypts a stream or a file body (an InputStream, a java.io.File, or a GenericFile from a file consumer) as its bytes, as they are. Before, it read such a body as text and encrypted that text in UTF-8, which changed binary data, so decrypt did not give the same bytes back. A String is still encrypted in UTF-8, a byte[] as it is, and the plaintext option or the CamelAlibabaKmsPlaintext header still win over the body.

A body that does not convert to bytes, such as a Map or a POJO, is no longer encrypted as its toString(): it now fails with an InvalidPayloadException. Marshal such a body first, for example to JSON with marshal().json().

camel-pqc - a configured KeyGenerator or KeyAgreement is used by one exchange at a time

When Camel creates the instance itself, the KEM operations of the pqc component (including the hybrid KEM operations) and the PQC data format now use a new KeyGenerator, and for the hybrid operations a new KeyAgreement, for every operation. A configured instance is still shared: a registry bean, which is autowired into every pqc endpoint, a KeyGenerator set on the data format, or the default material used when no key material is configured. Each operation now holds its lock from init until the result is returned, so concurrent exchanges that use it run one at a time.

A started PQC data format no longer keeps a KeyGenerator of its own: getKeyGenerator() returns only a configured instance and, unless one is configured, the CamelPQCKemAlgorithm header now selects the KEM algorithm of each operation.

camel-pqc - message bodies are signed and verified over their bytes

The sign, verify, hybridSign and hybridVerify operations now read a java.io.File or java.nio.file.Path body, and a file from a file consumer (a GenericFile body), as bytes from the file, the same way as a byte[] or InputStream body. A String (or any CharSequence) body is encoded as UTF-8, and any other body that has a binary type converter (for example a java.nio.ByteBuffer, a java.io.ByteArrayOutputStream, a Netty ByteBuf or a Vert.x Buffer) is signed over those bytes.

A body with no binary representation (for example a Map or a POJO) is no longer signed over its toString(): it is now rejected with an InvalidPayloadException. Convert such a body to a String, byte[] or other binary type earlier in the route.

Signatures created by earlier versions over a File, Path, GenericFile or other non-byte[]/InputStream body may not verify and need to be created again.

camel-alibaba-mns - binary bodies are kept, the message id and MD5 are headers, and receiveMessage is not an operation

The sendMessage and publishMessage operations of alibaba-mns now send a byte[], a stream or a file body (an InputStream, a java.io.File, or a GenericFile from a file consumer) as its bytes, as they are. Before, they read such a body as text and sent that text in UTF-8, which changed binary data, and a binary file from a file consumer failed to convert. A String is still sent as it is, and any other body, such as a Map, a List or a POJO, is still sent as its toString().

The consumer now gives the body of a message as a byte[] when its bytes are not UTF-8 text, such as the bytes of a binary file. Before, it gave a String in which such bytes were replaced. A message of UTF-8 text is still a String.

sendMessage and publishMessage now set the CamelAlibabaMnsMessageId and CamelAlibabaMnsMessageBodyMd5 headers, as the docs say, so after a send these headers hold the id and the MD5 of the message that was sent, not of a message consumed earlier in the route. They are still set as exchange properties too, as before.

receiveMessage is no longer a value of the operation option: the producer never supported it, and the consumer, which receives the messages of a queue, does not use the option. A producer with operation=receiveMessage still fails, now with a message that says so. Remove operation=receiveMessage from consumer endpoints, which tooling that validates endpoint options now reports.

camel-minio - a listed object that cannot be got is skipped

When the consumer lists the bucket, a failure to stat or get one of the listed objects (for example access denied, or an object encrypted with a customer key the consumer does not have) no longer fails the poll, as long as another listed object could be got: the object is skipped, the other listed objects are consumed, and the skipped object is tried again by a later poll. The failure is given to the exception handler of the consumer, with the object name in the message (by default a WARN with the stack trace, at every poll that lists the object). Before, the objects listed after it in the same poll were not consumed, so one such object could keep the consumer from consuming the rest of the bucket. The poll still fails when none of the listed objects could be got, and when objectName is set. When a poll lists maxMessagesPerPoll objects, the next poll now continues after the last listed object (before, after the last object that was consumed), so an object that cannot be got at the end of a page does not hold the listing back.

camel-minio - Server-side encryption options now apply to MinioProducer

The serverSideEncryption and serverSideEncryptionCustomerKey endpoint options now apply to producer operations: - Put operations apply the configured server-side encryption or customer-supplied key. - Copy operations apply the configured encryption to the destination object (and serverSideEncryptionCustomerKey to the source object when configured). - Get operations (getObject and getObjectRange) pass the customer key when configured. - Only one of serverSideEncryption or serverSideEncryptionCustomerKey can be configured on an endpoint; configuring both throws IllegalArgumentException on producer startup.

camel-box - getEnterpriseEvents returns the events of every chunk

The getEnterpriseEvents operation of the event-logs API now returns all the events between after and before. Previously it returned only the first chunk of the event log (at most 500 events), and the rest was dropped without any sign. A wide after/before window now returns every event in one list: that is one request per 500 events plus one that finds the end of the log, and more memory. Narrow the window when the event log is large.

camel-aws2-eventbridge - the consumer keeps one target for a queue given with queueUrl

A consumer that uses its own queue (queueUrl) added a target with a new random id to the EventBridge rule at every start, and removed it on stop. Two consumers that share the queue, or a restart after a consumer was not stopped cleanly, left two targets for the queue, and EventBridge put every event on the queue twice. The target now has an id derived from the queue ARN, and stays on the rule when the consumer stops, so the queue also collects the events of the rule while no consumer runs. Remove the target from the rule yourself when you no longer use the queue, and remove targets with ids camel-eb- plus 8 characters that an earlier version left for the queue. An auto-created queue is not affected. During a rolling upgrade from an earlier version, events can be put on the queue twice until the last instance on the earlier version stops.

camel-chunk - message data printed with a direct tag is rendered literally

Previously the chunk component bound the message body, headers and variables into the Chunk engine so that Chunk re-parsed those values as template source: a value that itself contained Chunk tags (such as a body of {$headers.name}) had those tags expanded instead of being printed verbatim.

Message data bound to the engine is now neutralised so that a direct {$tag} printing the body, a header, a variable, or an element of a nested map, list or array renders the value literally instead of evaluating the tags inside it. Tag-free data is unchanged and Chunk’s string filters (for example slice, get, join) keep their existing behaviour. Routes that relied on message data being evaluated as Chunk template code must move that markup into the template itself.

As part of this, a non-List Collection value (such as a Set) is now bound as a List, so a direct {$body} prints Chunk’s list placeholder instead of the collection’s toString(), and a filter such as |join operates on its elements.

This does not cover every path: Chunk re-parses any string it produces while rendering, so the output of a filter ({$body|trim}), a string property of a bean body ({$body.name}, {$exception.message}) and string items bound by a {% loop %} are still parsed as template code. Keep untrusted data to direct {$tag} output; do not pass untrusted values through Chunk filters, bean properties or loops. If untrusted data must go through one of those paths, remove the { and } characters from it in the route before the chunk endpoint (Chunk has no escape for tags inside data), for example with a processor that rewrites the body or header.

camel-azure-storage-blob - a listed blob that cannot be got is skipped

When the consumer lists the container, a failure to get one of the listed blobs (for example a blob in the archive tier, or access denied) no longer fails the poll, as long as another listed blob could be got: the blob is skipped, the other listed blobs are consumed, and the skipped blob is tried again by a later poll. The failure is given to the exception handler of the consumer, with the blob name in the message (by default a WARN with the stack trace, at every poll that lists the blob). Before, the poll failed, and as every poll lists the container again, one such blob stopped the consumer from consuming any blob. The poll still fails when none of the listed blobs could be got.

camel-azure-eventhubs - an event that completes when the checkpoint timer of its window has run is checkpointed

With checkpointBatchTimeout, the consumer now starts a new checkpoint window when the timer of the current window has run, and no longer when the wall clock passes the time the timer was scheduled for. Before, an event that completed in the millisecond in which the timer ran (or while the timer was taking the count) was counted in the window whose count had already been taken: it was checkpointed only with the next event, and when the partition stayed quiet it was delivered again after a restart.

The CamelAzureEventHubsCheckpointUpdatedBy header can now be timeout. An event that completes after the scheduled time of its window, but before the (late) timer has run, joins that window and gets timeout; its offset is checkpointed by that timer. Before, such an event always started a new window, so timeout was practically never set. Routes that read this header can now see timeout as well as size.

camel-azure-functions - invokeFunction fails on an HTTP error status, and sends the body as bytes

The invokeFunction operation now fails the exchange with an org.apache.camel.http.base.HttpOperationFailedException when the function answers with an HTTP status of 400 or above, as the HTTP components do. The exception holds the status, the response body and the response headers. Previously the exchange succeeded, with the error response as the body and the status in the CamelAzureFunctionsStatusCode header. Set the new throwExceptionOnFailure option to false to keep the previous behaviour.

The request body is now sent as bytes: a stream or a file is sent as its content, and bytes are sent as they are. Previously a body other than text or bytes was sent as its toString(), and bytes were decoded as UTF-8. Text, a number or a boolean is still sent as its text. Another body that does not convert to bytes, such as a Map or a POJO, now fails the exchange: marshal it to JSON first. A request with a body now always has Content-Type: application/json unless the CamelAzureFunctionsHttpHeaders header sets a Content-Type; previously it had no Content-Type when that header was not set.

camel-azure-servicebus - the dead-letter reason and description are bounded

When the consumer dead-letters a message after a failed route with enableDeadLettering=true, the reason and the error description it writes are now capped at 4096 characters, the limit the Azure SDKs apply to both fields. A value that was cut ends with …​. The description holds the stack trace, so a deep one is now truncated rather than sent in full and rejected by the broker.

A settlement request the broker rejects is now reported through the consumer’s exception handler.

camel-azure-key-vault - the body after deleteSecret, and a Map or a POJO given to createSecret

The deleteSecret operation now sets the DeletedSecret of the Azure SDK as the message body, with the recovery id and the dates of the deletion and of the scheduled purge. Before, the message body after deleteSecret was always null. A route that relied on an empty body after deleteSecret must now clear the body itself.

The createSecret operation now stores a Map, a List or a POJO body as JSON, such as {"username":"admin","password":"secret"}, with dates and times as ISO-8601 text. Before, it stored the text the type converter gives (usually toString()), such as {username=admin, password=secret}. A POJO that has its own type converter to String, or a JAXB object, is now stored as the JSON of its properties as well: to store the text of the converter, convert the body to a String first, for example with convertBodyTo(String.class). A Map or a List that Jackson cannot serialize now fails with an IllegalArgumentException. Text, bytes, a stream or a file is stored as its text, as before.

camel-nats - the JetStream pull consumer subscribes again when its ephemeral consumer was deleted

With jetstreamEnabled=true and a pull subscription (the default), the nats consumer kept fetching from a JetStream consumer that the server had deleted, and consumed nothing more until the route was restarted. It now subscribes again when the server no longer has its ephemeral consumer (no durableName, and no durable name in the consumerConfiguration), which the server deletes after about 5 seconds without a pull request, for example while the client is disconnected, or loses on a restart. The new consumer starts at the first message the route did not acknowledge, so the route can see again the messages after that one (with ackPolicy=None, after the last message fetched). On a stream with WorkQueue retention, and on a stream that was created again since the route subscribed, the new consumer uses the consumer configuration as it is. The consumer asks the server for its consumer only after a fetch that ended empty well before pullFetchTimeout, at most every 5 seconds. A durable consumer is not created again: when it is gone (deleted by an administrator, or after its inactiveThreshold), the consumer logs a WARN and keeps fetching, and the fetches work again when the durable is created again with the same name.

camel-azure-cosmosdb - a client set on the component or autowired is not closed by Camel

A CosmosAsyncClient set with the cosmosAsyncClient option, or autowired from the registry, is shared by all the azure-cosmosdb endpoints. An endpoint that stopped (for example because its route was removed or reloaded) closed it, and from then on the other endpoints, and the application, failed every request until the application was restarted. Camel now closes only the clients that its endpoints create. Close a client you set yourself when the application no longer needs it (a client that is a Spring bean is closed by Spring).

camel-azure-cosmosdb - the JSON of an item is parsed into a Map

The createItem, upsertItem and replaceItem operations now parse a message body that holds the JSON of the item (a String, a byte[], a stream or a file) into a Map before they write it. Before, a JSON String or a file was written as a JSON string and failed, and a stream could not be serialized. A byte[] body was sent as it is, and the message body after the operation was the written item as a byte[]: it is now a Map, as for a Map body. If the next steps need the JSON as bytes, marshal the body, for example with marshal().json().

A List body, or JSON that is not one object (such as a JSON array), now fails with an IllegalArgumentException before Cosmos DB is called; before, Cosmos DB rejected it with an error 400. These operations write one item: send each item in its own exchange, for example with the Split EIP.

camel-telegram - a failed send fails the exchange once

When Telegram answers a message with an error status (for example 400 for an unknown chat), or with a body that cannot be read, the producer now fails the exchange once and the route stops there. Previously the route first continued after the producer as if the message had been sent, and then the exchange failed as well.

The exception of the exchange is the one that process() of the producer threw before: a RuntimeException that wraps an ExecutionException, with the RuntimeCamelException (status and body of the answer) as its cause, or the IOException when the request got no answer. The producer still waits for the answer before it returns, so the order of the messages is the same. The steps after the producer now run on the calling thread, and no longer on a thread of the HTTP client.

camel-nats - the JetStream push consumer subscribes again when its ephemeral consumer was deleted

With jetstreamEnabled=true and pullSubscription=false, the consumer now creates its ephemeral JetStream consumer again after a reconnect when the server deleted it, starting at the first message the route did not acknowledge, so the route can see again the messages after that one. A durable consumer is not created again (a WARN is logged), and a consumer deleted while the connection stays up is not detected. See the NATS component page for the details.

camel-vertx-websocket - the client consumer makes one reconnect attempt at a time

When the WebSocket of a consumer with consumeAsClient=true is closed, the consumer now makes one reconnect attempt at a time: while an attempt waits for the server (up to the connect timeout), the next runs of reconnectInterval are skipped, and maxReconnectAttempts counts these attempts one after the other. Previously every interval started another attempt, and when the server answered late each of them opened a WebSocket, so that every message was routed once per WebSocket. A consumer that is stopped no longer reconnects.

camel-core - a route from a route template has the template’s resource as its source

A route created from a route template or a Kamelet now returns the resource with the route template from Route.getSourceResource() (it returned null). This is the resource its source location (Route.getSourceLocation()) was already in, so the debugger and message history consoles show the source code of these routes too.

A route created from a templatedRoute also remembers the resource that declares the templatedRoute (Route.getTemplatedRouteResource(), the TemplatedRouteSource JMX attribute, and templatedRouteSource in the route dev console), and the route template id is available from Route.getRouteTemplateId(), the RouteTemplateId JMX attribute, and routeTemplateId in the route dev console.

Code that took a null source resource to mean that a route was created from a route template should use Route.isCreatedByRouteTemplate() instead.

camel-aws2-ddb - aws2-ddbstream consumer and a suspended route

When a route is suspended (RouteController.suspendRoute, also when a route policy calls it, or CamelContext.suspend()) while the aws2-ddbstream consumer routes the records of a poll, the consumer stops after the record that is being routed. The other records of that poll (up to maxResultsPerRequest, default 100) were skipped when the route was resumed. They are now routed after the resume. A consumer that is suspended directly (consumer.suspend()) routes all the records of its poll and was not affected.

camel-jackson and camel-jackson3 - the bodies of the data type transformers

When the X-Content-Class exchange property is not set, the application-json data type transformer now writes a Map body as a JSON object and a List (or another Collection) body as a JSON array, and reads text, bytes or a stream (an InputStream, a Reader or the stream cache) as text: a JSON object or array is kept as it is, and any other text becomes a JSON string, so Hello becomes "Hello". This includes text that is a JSON number, boolean, null or quoted string: 42 becomes "42", true becomes "true", and "Hello" with its quotes becomes "\"Hello\"". These bodies failed before. With the X-Content-Class exchange property set, the transformer still writes only a body of that class, or JSON object or array text. A Map entry with a null or empty value is left out, as the transformer already did for the properties of a POJO.

Bytes and a stream are read as text with the charset of the exchange (the CamelCharsetName header or exchange property, else UTF-8), and the JSON is written in UTF-8. A payload that is not text, such as Avro binary or an image, therefore becomes a JSON string with replacement characters. Encode such a payload first, for example in Base64, or give it a content class: a byte[] body with the X-Content-Class exchange property set to byte[] is written as a Base64 JSON string.

The avro-x-struct data type transformer now reads a JSON object or array given as text as JSON, as protobuf-x-struct does. Before, it read the text as Avro binary, which failed or gave a wrong JsonNode.

The avro-x-java-object and protobuf-x-java-object data type transformers of camel-jackson-avro and camel-jackson-protobuf now read a JsonNode body into the class of the X-Content-Class exchange property, as the camel-jackson3 ones do. Before, they failed with an UnsupportedOperationException.

The application-x-java-object, avro-x-java-object and protobuf-x-java-object data type transformers now fail on a null body with a CamelExecutionException, whose cause is an InvalidPayloadException, instead of a NullPointerException, when the X-Content-Class exchange property is set.

The avro-binary, avro-x-struct, protobuf-binary and protobuf-x-struct data type transformers of camel-jackson3-avro and camel-jackson3-protobuf now wrap a JacksonException in a CamelExecutionException, as their camel-jackson-avro and camel-jackson-protobuf counterparts and the camel-jackson3 JSON transformers do. The JacksonException is the cause of the CamelExecutionException.

camel-core - the route structure leaves out onException clauses that never run

A route with noErrorHandler runs none of its onException clauses, and a global onException is copied into every route of its route builder or file. The route structure now leaves these clauses out of such routes: the route-structure dev console, camel cmd route-structure, and the route diagrams drawn from it. Before, they were listed among the steps of the route.

camel-core - simple == compares a number and a numeric String by their values

The == and != operators of the simple language compare a number with a String that holds a number by their values, as <, ⇐, > and >= (and in when the left value is a number) do since Camel 4.23. Before, when the two were written differently, the String was converted to the type of the number and compared with equals (which also compares the scale of a BigDecimal), or the two were compared as text. So ${header.amount} == '10' is now true for a BigDecimal of 10.00 (it was false, while ${header.amount} ⇐ '10' and >= '10' were true), and so are ${header.count} == '3.0' for an Integer of 3 and ${header.text} == 10 for a String of 10.00. Spaces around the number and an exponent (such as ' 10' and '1e1') are allowed, as in the in operator, and < and > compare such a String by its exact value too (it was compared as a double). The same applies to =~, to contains on a collection, and to the Java DSL and mock predicates such as header("amount").isEqualTo("10"). Two String values, and a Double or Float compared with a String, compare as before. Use equals to compare the two values as text.

camel-diagram - a route diagram draws the steps of a route

A route diagram drew the onException clauses of a route among its steps, though they are not steps: a global onException is copied into every route, and runs only while a failure is handled. A route diagram now draws only the steps of the route: camel cmd route-diagram, the route-diagram dev console and its web component, and the route diagrams dumped by DumpRoutesStrategy. Use --errors (?errors=true in the dev console, the errors attribute of the <camel-route-diagram> web component, errors of DumpRoutesStrategy and @EnableRouteDiagramDump, and dumpRouteDiagramErrors of @CamelMainTest) to draw them in a frame of their own below the steps, with where the failures of the route go. A highlighted path that steps into an onException clause draws the frame as well.

The <camel-topology-diagram> web component no longer draws the error paths of the route topology as calls between routes. With its new errors attribute it draws the routes reached only on error in a frame below the other routes.

camel-jbang - the default name of a run of a whole directory

camel run --source-dir, and camel run of a directory, without --name name the integration after the directory. A folder of a project layout (src, main, resources, java, camel or routes) gives way to the first parent with another name, and . is the working directory. Before, the name was the one of the first file, in the order that the file system lists them, which could be any file, such as a Java class that a route uses. --name, and camel.main.name in application.properties, still win.

camel-jbang - route-diagram, route-structure and route-topology read Java sources without compiling them

Given Java source files, camel cmd route-diagram, camel cmd route-structure and camel cmd route-topology (and the camel_render_route_diagram MCP tool) now read the routes with the Java DSL parser, as camel transform route and camel overview do, instead of compiling the sources and loading their routes in a short-lived Camel. A step the parser cannot work out, such as a lambda or a processor, is shown as a placeholder, and the parts it could not read are listed (with --json, as an unread array in the JSON). Use the new --compile option to compile the sources and load their routes as before.

camel-diagram - a route diagram image draws the steps as cards

The PNG image of a route diagram draws each step as a card: an icon for the kind of step, in a circle of the color of its kind, the kind in bold (such as setBody) and its detail below (such as groovy: body.items). Before, a step was a box filled with the color of its kind, with its code (setBody[groovy{body.items}]) in one line. This applies to the dark, light and transparent themes, in camel cmd route-diagram, camel get history --diagram, camel get error --diagram, the camel_render_route_diagram MCP tool, the route-diagram developer console (format=png) and the RouteDiagramDumper. A card is taller than a box, so the image of the same route is taller. The ASCII and Unicode diagrams, the topology diagram and the web component do not change.

A custom color spec sets the colors of the cards with card, cardBorder, cardText and cardDetail. The text color no longer applies to the steps of a route diagram (it still does to the topology diagram).

The RouteDiagramRenderer(nodeWidth, fontSizeScaled, nodeTextPadding, metrics) constructor is deprecated, as the text of a card starts after its icon and nodeTextPadding is not used. Use RouteDiagramRenderer(nodeWidth, fontSizeScaled, metrics) instead.

A program that lays out routes with RouteDiagramLayoutEngine and draws them with RouteDiagramRenderer must call engine.setCardStyle(true) before layoutRoute. Without it, the cards get the height and text of the old boxes.

camel cmd route-diagram of a running application whose message history recorded the body types (such as in the dev profile) shows them on the arrows, with longer arrows to hold them. Use --body-types=false for the diagram without them.

camel cmd route-diagram of source files, and the camel_render_route_diagram MCP tool, check the routes as camel validate does, and mark the steps with a problem: a yellow border and a callout beside the route (a numbered list below a route with branches). An image of routes with problems is wider or taller. Use --problems=false for the diagram without them.

camel cmd route-diagram --output=diagram.png without --theme now saves the PNG image in the light theme. Before, the default theme was unicode also for a file, so the command saved the text diagram in diagram.txt instead. A diagram in the terminal is still text (unicode) by default, and a file that does not end with .png still gets the text diagram. Use --theme=unicode to save the text diagram.

The camel_render_route_diagram MCP tool also draws in the light theme when no theme is given (before, it failed without a theme).

camel-jbang - export to Quarkus keeps the names in quarkus.* keys

camel export --runtime=quarkus wrote every quarkus. key except quarkus.hawtio. in dash case: an upper-case letter became a dash and the lower-case letter, and an underscore became a dash. That also changed the names that a key holds, such as a log category, a datasource name or a map key. Now only option names in camel case are written in dash case, such as quarkus.http.rootPath as quarkus.http.root-path. A segment of the key that is quoted, that starts with an upper-case letter, or that has no upper-case letter is kept as it is. So quarkus.log.category."com.example.MyRoute".level, quarkus.datasource.Orders.db-kind and quarkus.datasource.order_history.db-kind are no longer exported as quarkus.log.category."com.example.-my-route".level, quarkus.datasource.-orders.db-kind and quarkus.datasource.order-history.db-kind. The same applies to camel run --runtime=quarkus, which exports the project in the same way.

A name in camel case that starts with a lower-case letter, such as the datasource myOrders or my_DataSource, still looks like an option name and is written in dash case as a whole, underscores included (my-orders, my-data-source). Quote it to keep it: quarkus.datasource."myOrders".db-kind.

camel-jbang - camel dirty removes only the files of processes that are gone

camel dirty counts, and camel dirty --clean removes, only the files named after the pid of a process that is gone, such as <pid>-status.json. Before, it counted every other file in ~/.camel too, and --clean removed them: the log of a Spring Boot or Quarkus app that camel run runs (<name>.log), also while the app ran, the console output that the AI tools keep, and the history of the AI prompt. Directories are not counted, and --clean counts only the files it removed.

camel-huggingface - bounded chat histories, outputs that contain the word error, and no Content-Type for text-to-speech

The chat task keeps the history of each conversation in memory, one per memory ID (the CamelChatMemoryId header). It used to keep every history for the life of the endpoint. It now keeps at most 1000 of them: once there are more than 1000 memory IDs, the oldest conversations are evicted (the history that was updated least recently first), and a message for an evicted memory ID starts a new conversation without its earlier messages. Set the new maxChatHistories option to change the limit, or to 0 to keep every history as before.

Each history used to keep every message of its conversation. The new maxHistoryMessages option keeps the last 50 messages by default, besides the system prompt: the oldest messages are dropped first, so a long conversation no longer sends its earliest turns to the model. Set it to 0 to keep every message as before. Its value must be 0 or at least 2: other values fail when the route starts.

A task no longer fails when its output has the text "error" in it. Only the error payload of the Python handler, a JSON object with the single field error, fails the task. The message of the RuntimeCamelException is now Python inference failed: followed by the error text, instead of the whole payload (automatic-speech-recognition used Python inference error:).

The text-to-speech task no longer sets the Content-Type header to application/octet-stream, as its body is a DJL Audio and not encoded audio bytes: it now removes the Content-Type header from the message, so the content type of the request does not stay on the result. A route that relied on this header, for example to answer an HTTP request, must set it itself after encoding the audio.

camel-djl - the model and translator of a custom model are looked up once

With a custom model (the model and translator options), the model and translator beans are looked up in the registry at the first message and kept until the route is stopped. Before, they were looked up for every message. A bean bound again under the same name is used after the route is restarted. A missing bean fails the message with a NoSuchBeanException that names the bean, instead of a NullPointerException, and an endpoint without the model or translator option fails to start.

The nlp applications take a byte[] or InputStream body as text, as well as a String.

camel-google-storage - a listed object that cannot be got is skipped

When the consumer lists the bucket, a failure to download one of the listed objects (for example access denied, or an object whose encryption key the consumer cannot use) no longer fails the poll, as long as another listed object could be got: the object is skipped, the other listed objects are consumed, and the skipped object is tried again by a later poll. The failure is given to the exception handler of the consumer, with the object name in the message (by default a WARN with the stack trace, at every poll that lists the object). Before, the poll failed, and as every poll lists the whole bucket, one such object stopped the consumer from consuming any object. The poll still fails when none of the listed objects could be got.

camel-mongodb-gridfs - the consumer routes a file once, offers the file of a failed exchange again and sets CamelFileName, and an unknown operation fails

With the TimeStamp (default) and PersistentTimestamp query strategies, the mongodb-gridfs consumer routed every file uploaded since the consumer started again at each poll (every delay, 500 ms by default). The upload date of the newest routed file is now kept between the polls, so a file is routed once. A route that relied on receiving the same file repeatedly now receives it once. These two strategies now skip a file whose upload date is older than that of a file already routed (as they did before Camel 3.13), for example a file from a second uploader, or from a client with another clock, that becomes visible later: GridFS sets the upload date on the client. Use TimeStampAndFileAttribute or PersistentTimestampAndFileAttribute if such files must be routed: these strategies keep the start date in their query, as before, and mark each file so that it is routed once.

When the exchange of a file fails, the consumer no longer marks the file as processed or moves its timestamp past the file: it reports the failure to its exception handler (which logs it by default), and the next poll offers the file again. Before, the failure was ignored and the file was never offered again. A failure that the route handles, such as with a dead letter channel, still completes the file. With the TimeStamp and PersistentTimestamp query strategies, the files of a poll are now routed in the order of their upload date, and the poll stops at a file whose exchange fails, so a file that always fails holds back the files uploaded after it: handle such failures in the route, or use a strategy with a file attribute, which goes on with the other files. Only the first failure of a file is reported to the exception handler: when the file fails again at the next poll, the consumer logs the failure at DEBUG, so a file that always fails is reported once (it is reported again when it fails after a poll that routed it or did not offer it). The exceptionHandler and bridgeErrorHandler options now apply to the consumer; before, they had no effect.

The consumer now sets the CamelFileName header to the name of the file. A route that sends the message on to an endpoint that uses this header, such as a file endpoint, now writes the file under that name instead of a generated one. Remove the header before that endpoint to keep the generated name.

The consumer now closes the stream of the content of the file when the exchange is done. With stream caching turned off (it is on by default), read the stream before the exchange is done.

The producer now fails with an IllegalArgumentException that lists the supported operations when the operation option or the CamelGridFsOperation header is not create, remove, findOne, listAll or count. Before, a misspelled operation, such as findone, did nothing and the exchange succeeded.

listAll now gives the id of each file as the hex string of its ObjectId, such as 665f1c2e8b3e4a5d6c7b8a90. Before, each line held BsonObjectId{value=665f1c2e8b3e4a5d6c7b8a90}.

camel-paho-mqtt5, camel-paho - consumers that share a client get the messages of their own topic

A client has a single callback, and each consumer set it on its client. The consumers of several endpoints that use the same client (the client option) therefore all routed their messages through the consumer that started last, and only that consumer subscribed again after the client reconnected. The consumers of a client now share one callback: a message goes to the consumers whose topic filter matches it, also when a single consumer of the client is left (only a client that the consumer created for itself still gives it every message), and each consumer subscribes again after a reconnect. When a consumer that uses a client set with the client option stops, it now unsubscribes its topic from the client (unless cleanStart=false, or cleanSession=false for camel-paho, or another running consumer of the client uses the same topic filter), as the client stays connected, and it uses the same client again when it starts again (it created a client of its own before). This depends on the endpoint option, not on how the client was connected: set cleanStart=false on the endpoints to keep the subscriptions of a client connected with cleanStart=false. With cleanStart=false the stopped consumer’s subscription stays on the connected client, so the broker keeps sending its messages, which are dropped (with one WARN per topic, then at DEBUG) until the consumer starts again. With manualAcksEnabled=true such a dropped message is also acknowledged, so it is gone from a session that the broker keeps: left unacknowledged, it would count against receiveMaximum (for camel-paho, the broker’s limit of unacknowledged messages per client), and the broker would then send nothing more to any consumer of the client. Consumers of one client whose topic filters overlap (such as orders/# and orders/eu, or the same filter) each get a message that matches several filters, and a broker that sends one copy per matching subscription (MQTT allows it, Apache ActiveMQ Artemis does it) makes each of them get every copy. With manualAcksEnabled=true each of them acknowledges the message, so a message that failed in one of these routes is still acknowledged when another of them processes it. Each subscribe of a filter makes the broker send the retained message of a matching topic again, so consumers of the same filter get it again when another of them starts and each time they subscribe again after a reconnect. Only the running consumers count when a consumer stops: with consumers of the same filter on one client, one with cleanStart=true and one with cleanStart=false (cleanSession for camel-paho), stopping the durable one and then the other unsubscribes the filter, which also removes the subscription that the durable consumer kept. Give such consumers a client of their own.

Semantic image input and audit output

Semantic evaluations now recognize a selected ${body} with an image Content-Type, and self-describing data:image/…​ strings, as image input. Text-only experts reject these inputs instead of treating encoded images as text. For binary image streams, Camel preserves the bytes using a replayable stream cache; downstream code must not rely on the original stream class. For the exact ${body} selector, audio/ and video/ content types now fail before inference, even for a String body. Select an explicitly textual field to evaluate text accompanying audio or video. Content-Type normalization does not apply to other selectors such as $\{bodyAs(byte[])}; use ${body} for image bytes or select a media envelope or image data URL explicitly. Ordinary strings without a media content type remain text. The $semanticInput key is reserved for explicit media envelopes. TypeSafe AI rejects that key in structured state, including nested objects; rename an application-state key that conflicts with it.

SemanticAdapter has an additive default validateState(Object) hook for validation shared by all questions for one expert. Existing per-evaluation validateInput hooks still run; both finish for every expert before any provider performs inference. A shared validation failure marks that expert’s group as invalid input in the audit.

The additive SemanticAdapter.getMaxImageBytes() method exposes an expert’s configured per-image byte limit, with zero meaning no advertised limit. Image-capable adapters should advertise a positive limit and enforce it before inference. CLI and TUI local image loading now requires that metadata; the configured value appears as maxImageBytes in each image operation’s contract.

Semantic audit backends can opt into the additive inputType extension. Readers that do not support it keep their existing projection. The camel semantic audit --json display omits image base64 and reports metadata instead. Use --output=<new-file> to export the full returned JSON, with --include-input for case/bulk requests that need captured input. Existing files are not overwritten.

camel-azure-storage-datalake - flush position is the final file length

The flushToFile operation now uses the position option or CamelAzureStorageDataLakePosition header as the absolute final file length, as documented. Previously, it added this value to the current file length. Routes that supplied only the number of appended bytes must now supply the total final length. For example, after appending 20 bytes to a 100-byte file, use 120 instead of 20. When no position is supplied, the operation still uses the current committed file length.

camel-azure-storage-datalake - a listed file that cannot be got is skipped

When the consumer lists the file system, a failure to get one of the listed files (for example access denied by the ACL of the file) no longer fails the poll, as long as another listed file could be got: the file is skipped, the other listed files are consumed, and the skipped file is tried again by a later poll. The failure is given to the exception handler of the consumer, with the file name in the message (by default a WARN with the stack trace, at every poll that lists the file). A file deleted after the listing is skipped as well; before, it made the whole poll return no file. Before, any other failure failed the poll, and as every poll lists the file system again, one such file stopped the consumer from consuming any file. The poll still fails when none of the listed files could be got.

camel-azure-storage-datalake - streaming downloads honor ranges and request conditions

The getFile operation now honors the CamelAzureStorageDataLakeFileRange header, or the endpoint fileOffset and dataCount options, when it returns an InputStream. Previously, these settings only applied when the message body supplied an OutputStream; streaming reads returned the entire file. This also affects consumers without fileDir: they now deliver the configured byte range instead of the whole file. Remove the range header and options if your route needs the entire file.

The streaming path also honors the CamelAzureStorageDataLakeRequestCondition header, whether or not a range is set. An ETag mismatch or an invalid lease can now fail a streaming read that previously ignored the condition.

camel-chatscript - the first message of a conversation is sent, and the headers are kept

The first message of a ChatScript endpoint, and every message with resetChat=true, used to start the conversation with the text null and return the greeting of the bot as the reply, so the message itself was never sent. The conversation now starts with an empty message, as the ChatScript protocol requires, and the message is then sent, so the reply is the answer of the bot to it. A first message with an empty text only starts the conversation and still returns the greeting.

With resetChat=true the :reset command is now sent for the user of the message (or for the chatUserName option when the message has no user), not for the chatUserName option alone.

The producer now keeps the headers of the message, which it used to drop.

camel-milo - a Bad or Uncertain status is delivered by default

A milo-client consumer now delivers a data change with a Bad or Uncertain status by default, even when its value is null; previously the default omitNullValues=true dropped it. omitNullValues now only omits a null value with a Good status. A route may therefore receive a Bad status with a null value, also as the first notification of a monitored item at startup and after a reconnect, for example Bad_WaitingForInitialData or the status of a milo-server item that was not written yet. A route that uses the value must check getStatusCode() of the DataValue body first: Camel’s type converter returns 0 or false for a null value that is converted to a primitive type such as double or boolean. To skip the startup notification, filter on its status code; a filter on isGood() would also drop the Bad status of a failed sensor.

camel-milo - creating and deleting the monitored items of a consumer

Since 4.16, a milo-client consumer whose monitored item could not be created, for example because the node does not exist, received nothing. Now it receives one DataValue with the bad status code. The monitored item is then created again every reconnectTimeout (10 seconds by default) on the same connection, without delivering anything for a failed attempt, until the server accepts it; its first value is the current value of the node. A route restart or a connection that Camel rebuilds creates it again too.

A node ID with a namespace URI (nsu=) that is not in the namespace table of the server no longer takes the whole client connection down, including the consumers of other nodes. Its consumer receives one DataValue with Bad_NodeIdUnknown, like a node that does not exist.

The consumer now sets the CamelMiloNodeId header on every exchange, including a consumer with a single node.

Stopping a consumer now deletes its monitored items. Previously they stayed on the server while another endpoint kept the shared connection open, so a restarted consumer received each value twice. While the server is unreachable, the stop waits until the client’s next attempt to reconnect has failed.

When the server removes the subscription that the consumers of a connection share, or the subscription times out on the server, it is now created again in the existing session; previously the consumers stopped receiving values until the connection was rebuilt for another reason. The monitored items deliver their current values again after the subscription is recreated.

camel-milo - a producer request for a node with an unknown namespace URI

A milo-client write, method call or read, or a milo-browse browse, for a node ID with a namespace URI (nsu=) that is not in the namespace table of the server no longer makes the client reconnect, which also recreated the subscriptions of the consumers that share the connection. A write, read or browse answers the node like a node that the server does not have, with the status code Bad_NodeIdUnknown, and the other nodes of a read or browse are not affected. Previously an awaited write or read completed without an error and with a null body, and a browse failed with a NullPointerException.

camel-milo - the consumer reports a lost connection and refreshes its values after it

The OPC UA client consumer delivers Bad_NoCommunication with a null value for every monitored node when communication with the server is lost, and the current value of every node once communication is restored. Before, the consumer delivered nothing during an outage, and values that did not change were not delivered again after it.

A consumer that starts while the server cannot be reached, or whose client cannot connect at all, for example because of wrong credentials or an untrusted server certificate, delivers the status for its nodes too; the cause is in the log. A connection that Camel rebuilds delivers it for every node of the connection. Each loss and each recovery delivers one exchange per node.

A route that uses only the value of the DataValue body receives a null for these exchanges, which the type converters turn into 0 or false; a route that stores values checks the status code of the body first, for example with ${body.statusCode.bad}. A value can now arrive again after an outage, so a route that counts values, computes differences or detects changes must tolerate repeats. The consumer’s route also runs during an outage: an OPC UA write that it makes while the client reconnects waits for the next reconnect attempt and can take effect after communication is restored.

To keep the previous behaviour, filter the exchanges with a bad status out right after the consumer, for example with filter(simple("${body.statusCode.bad} == false")). This also drops the other bad statuses, such as the refusal of a node, and the current values delivered after an outage still arrive.

camel-tensorflow-serving - a body that is not the request of the api fails

The tensorflow-serving producer converted the body to the protobuf request of the api, and dropped a body that did not convert, such as text, JSON, bytes or a Map, without an error. The request was then sent without it, so predict, classify and regress failed on the server with an error about the missing inputs, and model-status and model-metadata ignored the body. Such a body, when it is not empty, now fails the exchange with an InvalidPayloadException that names the request type of the api, before anything is sent. Build the protobuf request before the endpoint, or remove a body that an earlier step left before calling model-status or model-metadata.

No body, a blank String or an empty byte[] still sends a request built from the options and headers, and a body that is the request or its Builder, or an Input for classify and regress, is sent as before.

model-metadata asked for signature_def in addition to the metadata fields of a GetModelMetadataRequest body, so a request that named signature_def asked for it twice. It now asks for signature_def only when the request names no metadata field.

camel-milo - an awaited write, call or read that fails now fails the exchange

An awaited milo-client write, method call or read that fails at the service or transport level, for example a request that times out (Bad_Timeout) or a lost connection, now fails the exchange and lets the route’s error handler run. Before, the exchange completed without an error and with a null body, silently discarding the failure. A bad status for the node itself, such as Bad_TypeMismatch on a write, is not a failure of the operation and is still returned as the body.

An awaited call that succeeds now returns the CallMethodResult as the exchange body. Before, the body was always null even on success. A route that reads the result of an awaited call must be updated to read the CallMethodResult from the body instead of expecting null.

camel-milo - a failed producer request keeps the connection

A milo-client write, method call or read, or a milo-browse browse, that fails (for example with Bad_Timeout, or because the session was lost) no longer disconnects the client connection and rebuilds the subscriptions of the consumers that share it. The failure is still set on the exchange of an awaited request. A lost session is recovered by the client itself, and a session that the server no longer has rebuilds the connection, as before. The client recovers a lost session with the endpoint and the server certificate it found when it connected, so a server that changes either while it cannot be reached is reached again only when Camel rebuilds the connection, for example when the Camel context restarts.

Producer requests and MiloClientConnection.getOpcUaClient() no longer wait for the subscription work on the same connection to finish, such as a consumer that starts or stops, or the periodic attempt to create the monitored items that the server refused. Before, they waited until the server answered that work, and while the network was down silently, until the client noticed the lost session. While Camel rebuilds the connection, a producer request now fails at once with an IllegalStateException ("No connected", caused by the failure that ended the connection) instead of waiting for the rebuild to finish. The attempts to create the refused monitored items again now run on the MiloClientReconnect thread instead of the MiloClient pool, so that a slow attempt no longer holds up the outcome of the requests, which that pool completes; on a machine with one processor the pool has a single thread.

camel-milo - the reconnectTimeout of the components sets the delay between connection attempts

setReconnectTimeout on the milo-client and milo-browse components set the request timeout of every service call, and the delay between two connection attempts stayed 10 seconds. It now sets that delay, which the new reconnectTimeout option of both endpoints and components sets too; its default of 10000 milliseconds is the previous delay, and a value of 0 or less is rejected. The option also sets how often the monitored items that the server refused are created again: a lower value repeats the CreateMonitoredItems request for them, for example for a node ID with a typo, more often while the connection lasts. To set the request timeout, use the requestTimeout option instead (for example camel.component.milo-client.requestTimeout, or getConfiguration().setRequestTimeout(…​) in Java).

camel-milo - the sessionTimeout of the components reaches their endpoints

A sessionTimeout configured on the milo-client or milo-browse component (for example camel.component.milo-client.sessionTimeout) did not reach its endpoints, so their sessions used the default of Milo, 120000 milliseconds. The endpoints now use it, unless they set sessionTimeout themselves.

camel-kserve - a body that is not the request of the api fails

The kserve producer converted the body of infer, model/ready and model/metadata to the protobuf request of the api, and dropped a body that did not convert, such as text, JSON, bytes or a Map, without an error. The request was then sent without it, so infer sent a request with no inputs, and model/ready and model/metadata ignored the body. Such a body, when it is not empty, now fails the exchange with an InvalidPayloadException that names the request type of the api, before anything is sent. Build the protobuf request before the endpoint, or remove a body that an earlier step left before calling model/ready or model/metadata.

No body, a blank String or an empty byte[] still sends a request built from the options and headers, a body that is the request or its Builder is sent as before, and the server/ready, server/live and server/metadata apis still do not use the body.

camel-milo - a write of a null body fails the exchange

A milo-client producer no longer writes a null body; the exchange now fails with an InvalidPayloadException, whether or not the write is awaited. Before, it wrote a null value, which a server typically refused with Bad_TypeMismatch, and an awaited write completed without an error and with that status as the body. To write a null value, set the body to Variant.NULL_VALUE or to a DataValue that carries it. A method call with a null body still calls the method without input arguments.

camel-infinispan - the key option of the producer takes precedence over the key header

The infinispan and infinispan-embedded producers now use the key option of the endpoint when it is set, and use the CamelInfinispanKey header only when the endpoint does not set key. Previously the header took precedence over the option, so a route that sent the events of an Infinispan consumer, which set CamelInfinispanKey to the key of the event, to a producer with a fixed key worked on the key of the event.

Routes that set key on the endpoint and override it per message with the CamelInfinispanKey header must remove key from the endpoint.

The CamelInfinispanCacheName header still takes precedence over the cache of the endpoint, so a route that sends the events of an Infinispan consumer to a producer must still remove the CamelInfinispan* headers first, see Key and cache name headers in the component docs.

camel-aws2-athena - the SQL is taken from the message body only with useMessageBodyForSql

The startQueryExecution operation of the aws2-athena producer, which is its default operation, now takes the SQL to run from the CamelAwsAthenaQueryString header, and when the header is not set, from the queryString option. It uses the message body as the SQL only when the new useMessageBodyForSql option is set to true, like the option of the same name in camel-sql. A non-empty body then takes precedence over the header and the option, as before. Previously the message body always came first, so an endpoint configured with queryString used the message body as the SQL whenever the message had one.

Routes that put the SQL in the message body, as the examples in the component documentation did, must set useMessageBodyForSql=true, or set the SQL in the CamelAwsAthenaQueryString header or the queryString option instead. Without SQL, the exchange fails with an IllegalArgumentException that names these options.

With includeTrace=true, the URI of the from endpoint in the SQL comment is now sanitized, as Camel does when it logs an endpoint URI, and it is left out when the exchange has no from endpoint, which failed with a NullPointerException before.

camel-aws2-athena - result headers are not taken as the input of a later operation

  • The CamelAwsAthenaOutputLocation header set by getQueryExecution, by getQueryResults with S3Pointer, and by startQueryExecution with a waitTimeout is the file with the results of that query. A later startQueryExecution on the same message no longer uses it, and puts its results under its outputLocation option. Previously it put them under the result file of the earlier query.

  • The CamelAwsAthenaNextToken header set by listQueryExecutions is now used only by a later listQueryExecutions, and the one set by getQueryResults only by a later getQueryResults for the same query. Previously the token was sent to the other operation, or for another query, which Athena rejects.

  • A header that the route sets itself is used as before.

  • A query execution without a result configuration, as with managed query results, no longer fails with a NullPointerException: the CamelAwsAthenaOutputLocation header is null.

camel-couchdb - the producer saves for the UPDATE method, and fails for an unknown method or a change of the consumer

The couchdb producer now saves the document of the body when the CamelCouchDbMethod header is UPDATE, the method that the couchdb consumer sets for an insert or an update; before, it did nothing for UPDATE. A method other than GET, DELETE and UPDATE now fails with a CouchDbException naming the method, where it did nothing before.

The body of the couchdb consumer is the change (its seq, id and changes), not the document, and the consumer sets the CamelCouchDbMethod header. When the consumer’s header and its change reach the producer, the producer now fails with a CouchDbException that says the body is a change and not a document, instead of saving or deleting it. A route from a couchdb consumer straight to a couchdb producer therefore fails, where it did nothing before. To copy the document, get it first, with the CamelCouchDbMethod header set to GET and the CamelCouchDbId header that the consumer sets, or set the body to the document; once the body is a document, remove the CamelCouchDbMethod header, or set it to the method the producer must use. To store the change itself, remove the CamelCouchDbMethod header: the producer then saves the change as a new document with a generated id, as before. In a route back to the database that the consumer reads, each such save is a new change in turn, so that route writes without end.

With the GET method the producer no longer reads the body, so an empty body or a body that is not JSON no longer fails it. A body that the producer cannot save or delete now fails with a message that says why: JSON text that is not valid fails with an InvalidPayloadException whose cause is the parse error, a JSON array fails with a CouchDbException that says the body must be one JSON object (before, Could not save document [unknown reason]), and a DELETE of a body without _id and _rev fails with a CouchDbException instead of a NullPointerException.

camel-protobuf - a bytes field in a Map

When a protobuf message is converted to a Map (for example getBody(Map.class) after unmarshalling), the value of a bytes field is now a byte[] with the content of the field. Previously it was the String that ByteString.toString() returns, a description such as <ByteString@1b6d3586 size=4 contents="…​">, which does not contain the whole value. When a Map is marshalled, a byte[] value is accepted for a bytes field.

camel-jbang - where the CLI writes its log files

  • The log4j2.properties of camel-jbang-core logs to the console only. It is the configuration that log4j finds on the classpath when nothing configures logging. It also wrote a log file into ~/.camel, named after the pid system property, which only the CLI sets: a JVM that uses camel-jbang-core without the CLI (such as a test) wrote ~/.camel/${sys:pid}.log, and camel test run left a <pid>.log there. The configuration that camel run uses with color is now log4j2-color.properties, next to log4j2-no-color.properties.

  • The log files of the CLI (<pid>.log, camel-export.log and camel-script.log) are written into the Camel directory of the CLI, where camel log reads them, instead of ~/.camel under user.home. The two differ when user.home is ? (a user without a home, such as in a container), as the CLI then uses $HOME, and when a test gives the CLI a home of its own. The CLI sets the directory as the system property camel.jbang.logDir. A Quarkus app that camel run exports and runs also logs there.

camel-jbang-mcp - runtime tools that changed behaviour

Some runtime tools of the Camel CLI MCP server now do what their description says, which changes what a client sees:

  • camel_runtime_receive receives a message in one call: it starts a consumer on the endpoint, waits up to timeoutSeconds (default 10, at most 60) for a message, returns it and stops the consumer. It returns one message by default; limit returns up to that many, and received counts every message the consumer took, returned or not. Before, it started a consumer that kept running, took the messages from the routes, and returned none of them. It is now marked destructive, so the read-write access level (camel.mcp.security.access-level) no longer offers it, only admin does. It runs one call at a time per process, and refuses to run while camel cmd receive is receiving from the same process.

  • camel_runtime_memory_leak with command=start and no mode makes one recording (60 seconds by default) and returns at once; get the results with query once it is done. Before, it made two recordings and blocked for about 90 seconds, longer than many MCP clients wait. Pass mode=dual for the previous behaviour, or make the two recordings yourself and use the new compare command. A duration that is not a whole number of seconds is refused.

  • camel_runtime_route_dump, camel_runtime_route_structure and camel_runtime_route_analysis return the routes that routeId names; they returned every route whatever routeId was.

camel-jbang - the AI write and edit tools repair YAML shapes that have one reading

The camel_write_file and camel_edit_file tools used to refuse content with a YAML shape that a model often gets wrong but that has only one reading, such as steps: written at the column of its EIP, or an option at the column of the EIP it belongs to. Such content is now repaired and written, and repaired in the answer lists the repairs. Content with more than one reading is refused as before.

camel_edit_file now matches lines to find that are not in the file as given, as when a model writes them from memory, to the closest lines of the file when exactly one place matches well. It changes there the lines the model changed and keeps the file’s version of the other lines, and the lines of the file the model did not have. The edit stands only when its result is valid; otherwise the answer is the one of a miss, with the lines the file has there. matchedLines in the answer names the lines it matched.

A write of the content the file already has, and an edit whose replacement gives the text the file has, answer with the status unchanged instead of overwritten or edited. Nothing is written, so a running integration does not reload. When one of several edits misses, the answer shows the file as it is in fileWindow and says that none of the edits was written, also not the ones before it that matched.

camel_get_log marks the records from before the last reload of the routes with beforeLastReload, with a reloadNote first in the answer. The note of camel_get_errors on errors from before the last reload is now the first key of its answer, and says how many exchanges failed since.

camel-jbang - the console output of an integration that the AI tools start

camel_run and camel_control restart send the console output of the integration they start to ~/.camel/<pid>-run.log, as camel run --background does, and remove it when the integration ends. It went to ~/.camel/camel-launch-<number>.log or camel-restart-<number>.log, which stayed until the MCP server exited. An integration that still runs when the server exits keeps the file while it runs: camel stop removes it, and camel dirty --clean once the integration has ended.

camel-main - a properties file that changed while reloading applies its beans and components

With live reloading (such as camel run --dev), a change to application.properties now applies its configuration:

  • The file is matched to the properties file the application loaded, also when its location is written another way: camel run --source-dir=. loads file:./application.properties, and the file watcher reports file:application.properties. Before, the two did not match, so the file was added once more for its property placeholders, but its camel.component., camel.dataformat. and camel.language.* options were not applied.

  • A new bean (camel.beans.*) is created and bound, before the components are configured again, so a component can refer to it (#bean:name), and a component that is created later can autowire it. With camel-jbang, the class of the bean is downloaded when camel-jbang knows it, such as the Artemis client. A dependency that is added to camel.jbang.dependencies while the application runs is not downloaded until a restart (CAMEL-25833).

  • A bean that exists is left as it is, as components may hold it. When its properties change, a WARN says that a restart is needed.

  • A properties file that is created while the application runs is applied as well.

PropertiesReload has a new default method, onChanged, which a properties file that changed calls with the properties that changed. A context reload (such as for secret rotation) still calls onReload.

camel-jbang - camel run keeps the log of the previous run

When camel run ends, its log is no longer deleted. It is kept as ~/.camel/<name>-previous.log, by the name of the integration, with ~/.camel/<name>-previous.json, which says when and how the run ended. The next run of that name that ends replaces both, and at most the last 10 MB of a log are kept. camel log <name> --previous prints the log, like kubectl logs --previous in Kubernetes. The log of an integration that was killed is kept the same way, the next time a previous run is looked up. camel dirty does not count these files.

camel-jbang - the AI tools wait for Camel to start, and read the log of the previous run

  • camel_run and camel_control restart answer that the integration started once its CamelContext has started. They answered as soon as the status file of the integration appeared, which happens while Camel starts and before the routes start. An integration whose route then failed to start was answered as started, and stopped right after. Now the answer is the failure, with its cause.

  • camel_get_log of an integration that is not running answers with the log of its previous run (see camel log --previous), and its message says how that run ended. previous=true asks for that log also when the integration runs again. camel_get_errors of an integration that is not running says how its previous run ended.

camel-toon - the lengthMarker option is removed (Breaking change)

camel-toon now uses JToon 2.0.7, which follows a newer version of the TOON specification.

The lengthMarker option of the TOON data format is removed, because the TOON specification no longer has the [N] array length marker. Remove the option from the data format in Java, XML and YAML routes. TOON text with a [#N] array header, such as the output of a data format with lengthMarker=true, now fails to unmarshal in both strict and non-strict mode; in Camel 4.23 it already failed in strict mode (the default). To read such data, remove the from its array headers (tags[#2]: becomes tags[2]:).

With strict=false, unmarshalling no longer returns null or a partial result for malformed TOON. Only the non-strict recoveries of the TOON specification apply, such as accepting an array whose length differs from the declared length; other malformed TOON fails with IllegalArgumentException, as in strict mode.

camel-as2 - consumers sharing a port use their own asynchronous MDN settings

as2://server/listen consumers that share a serverPortNumber now deliver asynchronous MDNs with their own mdnUserName, mdnPassword, mdnAccessToken and asyncMdnAllowedHosts, selected by the matched requestUriPattern in the same way as the signing material. Previously the values of the first consumer configured on the port were used for every consumer on it, and the values set on the other consumers were ignored. A consumer that relied on inheriting them now has to set them itself.

This applies to asyncMdnAllowedHosts as well. A consumer without its own list used to inherit the first consumer’s list, so asynchronous MDNs to other hosts were refused; it now delivers to the address the sender gives, without credentials. A deployment that counted on the inherited list as a restriction has to set it on each consumer.

A consumer whose requestUriPattern is / now also uses its own keys, certificates and asynchronous MDN settings when another consumer created the shared server connection; before, it used the first consumer’s.

camel-arangodb - the operation option is required, and a Collection body is saved as several documents

A route that sends to an arangodb endpoint without the operation option, set on the endpoint or on the component configuration, now fails to start with a message that names the option. Before, the route started and each message failed with Operation not supported. Value: null.

The SAVE_DOCUMENT operation now inserts a Collection body, such as a List of documents, as several documents, as it does with the CamelArangoDbMultiInsert header, and the body after it is a MultiDocumentEntity. Before, without the header, ArangoDB inserted the documents and the exchange then failed, so a redelivery inserted them again.

camel-aws2-sqs - the CloudEvent source is the queue name

The aws2-sqs:application-cloudevents data type transformer now sets the CamelCloudEventSource header to aws.sqs.queue. followed by the name of the queue the message was received from. Before, it used the receipt handle of the message, which changes each time a message is received, so every event had a source of its own.

The queue name comes from the new CamelAwsSqsQueueName header, which the aws2-sqs consumer now sets on each message. When the endpoint names the queue by its ARN, the header holds the queue name part of the ARN, and when the queueUrl option is set, the last part of its path, as that is the queue the consumer reads. A message without this header, such as one that does not come from the aws2-sqs consumer, gets no CloudEvent source from the transformer, also when it has a CamelAwsSqsReceiptHandle header. A route that relies on the old source can set the CamelCloudEventSource header itself after the transformer.

camel-aws2-ddb - the x-struct transformer reads bytes as JSON text

The aws2-ddb:application-x-struct data type transformer now reads a byte[] body as JSON text, with the charset of the exchange, as it reads a stream or a file and takes a String as it is, so the same JSON gives the same String whatever the type of the body. Before, Gson wrote the bytes as a JSON array of numbers. A stream or a file body, which failed before, is now read as text as well.

camel-ftp, camel-sftp, camel-ftps, camel-mina-sftp - sendNoop also applies to the consumer

The sendNoop option now also applies to the check a consumer makes before each poll, not only to the pre-write check of a producer, and its label is now common,advanced instead of producer,advanced. The default is still true, so a consumer sends a noop before each poll as before.

A consumer endpoint that already sets sendNoop=false, which was ignored before (for example a URI shared by from and to), now skips that noop and only checks whether the client still regards itself as connected. Remove sendNoop=false from the consumer endpoint to keep the noop before each poll.

For ftp and ftps the client does not notice a connection that the server has closed, so such a consumer finds it only when the next operation fails, during the poll or while processing a file. After a failure caused by an I/O error, the consumer sends a noop before the next poll, and connects again if the noop fails. If the FTP server closes idle connections and the delay between polls is longer than its idle timeout, every other poll fails and is logged before the consumer connects again. Keep sendNoop=true for such a consumer. Whatever sendNoop is, the ftp and ftps consumer still sends a noop before it renames or deletes a file on the server, as it does for each processed file unless noop=true.

For sftp and mina-sftp the client notices a closed connection, but not one that dropped silently. For sftp the noop is a keep-alive message that does not wait for a reply, so it does not find such a connection either. For mina-sftp the noop is a request that waits for the reply, so with sendNoop=false such a connection is found by the next poll, which fails and is logged before the consumer connects again. For sftp and mina-sftp, set serverAliveInterval to let the client close such a connection itself, so that the consumer connects again before the poll.

camel-cometd - the CORS filter allows credentials only for listed origins

With crossOriginFilterOn=true, the CORS responses now include Access-Control-Allow-Credentials: true only when allowedOrigins lists the origins explicitly, without any or null. When allowedOrigins is not set, is , or has an entry with a wildcard such as https://* or an entry null, the origin is still allowed, but without credentials. Previously credentials were allowed for every origin. A cross-origin client that sends cookies or other credentials must have its origin listed in allowedOrigins.

camel-neo4j - the similarity search gives the score, and the CamelNeo4jLabel header and query option are deprecated

VECTOR_SIMILARITY_SEARCH now adds the similarity score of each node to its Map in the body, as a Double under the score key. It replaces a property of the node named score; the records of the Neo4j driver in the CamelNeo4jQueryResultListNeo4jNodes header keep both. The body of the search can now also be a List or an array of any numbers, such as a List<Double>. A body that is not a vector, such as text, now fails with an InvalidPayloadException instead of a NullPointerException or a type conversion error.

RETRIEVE_NODES_AND_UPDATE_WITH_CYPHER_QUERY with a query whose first column is not a node, such as a property or a count, now gives each record as a Map of its columns. Before, it failed with Cannot coerce …​ to Node.

CREATE_NODE without a body now fails with an InvalidPayloadException instead of a NullPointerException.

The CamelNeo4jLabel header and the query option are deprecated: the component never used them. CREATE_VECTOR takes the label of the node from the label option (Embedding when it is not set), and the Cypher operations take the query from the message body.

The catalog now declares the CamelNeo4jQueryResultRetrieveSize header and the counters of the write operations (such as CamelNeo4jQueryResultNodesCreated) as Integer, the type the producer always set, and CamelNeo4jQueryResultListNeo4jNodes as a List of the Record of the Neo4j driver.

camel-tahu - a Device producer uses the client of its started Edge Node producer

A Device producer (tahu-edge:group/edgeNode/device) no longer creates the client of its Edge Node: only the Edge Node producer creates it, from the options of the Edge Node endpoint (deviceIds, primaryHostId, servers). A Device route that starts before the Edge Node route, or while it is stopped, therefore no longer fails with a NullPointerException when the Device URI has no deviceIds. Sending to a Device endpoint while no producer of its Edge Node endpoint is started now fails the exchange with an IllegalStateException; before, the data was dropped.

camel-tahu - one exchange for each Sparkplug message, and the payload body of the Edge Node producer

The tahu-host consumer gave two exchanges for each Sparkplug message (three for an NDATA). It now gives one. The exchange of a message with metrics now keeps the CamelTahuMessageType, CamelTahuEdgeNodeDescriptor, CamelTahuMessageTimestamp, CamelTahuMessageSequenceNumber and CamelTahuMessageUUID headers next to the CamelTahuMetric.<name> headers; before, the metric headers replaced them. The exchange of each metric is unchanged: it has no CamelTahuMessageType header.

The tahu-edge producer publishes a message body that is not a SparkplugBPayload, such as a text or a byte array, as the body of the payload, whose metrics are made from the CamelTahuMetric.<name> headers. Before, such a body failed with an InvalidPayloadException. A body that does not convert to a byte array still fails.

camel-aws - producers without an operation, and headers of the wrong type

The producers of aws2-ec2, aws2-ecs, aws2-eks, aws2-mq, aws2-msk, aws2-rekognition, aws2-step-functions, aws2-transcribe, aws-bedrock-agent and aws-config now fail with an IllegalArgumentException that says to set the operation option or the operation header, when neither is set. Before, they failed with a NullPointerException.

A header that holds a value of the wrong type is no longer dropped: the request is not sent, and the exchange fails with an IllegalArgumentException that names the header. This applies to CamelAwsEKSVPCConfig, CamelAwsMSKBrokerNodesGroupInfo, CamelAwsMQBrokerUsers and CamelAwsMQConfigurationID. CamelAwsMQConfigurationID also accepts the id of the configuration as a String. In aws2-ec2, CamelAwsEC2InstanceType also accepts the AWS value of the instance type (such as t2.micro), and CamelAwsEC2InstancesIds a String of ids separated by commas. In aws2-rekognition, CamelAwsRekognitionFacialAttributes and CamelAwsRekognitionDetectionAttributes also accept the names of the attributes as Strings.

In aws2-transcribe, a List in the CamelAwsTranscribeVocabularyPhrases header is now sent as one phrase per element by createVocabulary, and updateVocabulary now sends the phrases too. deleteVocabulary and deleteTranscriptionJob now set the response as the message body, as the other delete operations do.

The catalog and the endpoint DSL of aws-bedrock-agent listed the headers of aws-bedrock-agent-runtime. They now list the headers of aws-bedrock-agent, so the CamelAwsBedrockAgentRuntime* header names are no longer in the header builder of the aws-bedrock-agent endpoint DSL.

The CamelAwsEKSDescription header of aws2-eks is removed, together with the EKS2Constants.DESCRIPTION constant and its method in the header builder of the endpoint DSL. No operation of the component read it.

The aws2-mq component, which manages Amazon MQ brokers, is now in the management category instead of messaging.

In aws2-step-functions, the CamelAwsStepFunctionsExecutionInput header of startExecution and startSyncExecution must be JSON text (a String, a byte array or a stream). A value of another type, such as a Map, was sent as its toString(), which is not JSON; now the exchange fails. startSyncExecution now sets the CamelAwsStepFunctionsExecutionStatus header to the status of the execution (SUCCEEDED, FAILED or TIMED_OUT), and the CamelAwsStepFunctionsExecutionError and CamelAwsStepFunctionsExecutionCause headers when it failed. The exchange still does not fail when the execution did not succeed.

In aws-bedrock-agent, the CamelAwsBedrockAgentKnowledgeBaseId, CamelAwsBedrockAgentDataSourceId and CamelAwsBedrockAgentIngestionJobId headers now override the knowledgeBaseId, dataSourceId and ingestionJobId options, as the headers of the other AWS components do. Before, a header was used only when the option was not set.

Data formats - bodies they cannot write fail, and stream bodies

Several data formats wrote a body they cannot handle as empty or wrong output, or returned a stream that was closed by the time the next step read it. They now behave as follows:

  • bindyCsv, bindyFixed and bindyKvp: marshal of a body that is not an object of the model class, a List of them, or a Map of the model class names to the objects fails with an InvalidPayloadException, instead of writing an empty line. bindyFixed now takes a Map as a record of model class names, as bindyCsv and bindyKvp do.

  • flatpack: marshal writes a single Map as one row, and fails with an InvalidPayloadException for a body that is neither a Map nor a List of Maps. The name of the data format, as shown in JMX and in error messages, is now flatpack instead of flatback.

  • protobufJackson: a body that is already JSON text (a String with a JSON object or array, bytes, a stream or a file) is no longer written as it is and labelled application/protobuf. It is marshalled as any other body.

  • toon: marshal reads a byte[], a stream or a file as JSON text, as it does a String.

  • base64: unmarshal decodes the data right away and returns a stream cache, instead of a stream that decoded while it was read.

  • mimeMultipart: unmarshal of a message that is not a multipart leaves the body as it was, instead of setting it to a stream.

  • tarFile: with usingIterator, each entry is copied into a stream cache, as zipFile does, so a split that is not streaming gets the content of each entry. Marshal takes the size of the entry from the body, not from the CamelFileLength header. The messages of the tarFile and zipFile iterators have the size of their entry in CamelFileLength, instead of the size of the archive.

  • zipFile and tarFile: unmarshal without usingIterator skips the folder entries, so an archive of a folder with one file gives that file. An archive with only folder entries now fails.

  • iso8583: the configFile is loaded with the Camel resource loader, so file: and http: work, and a configFile that is set but not found fails at startup. Without the isoType option and the CamelIso8583IsoType header, unmarshal fails with a message that says so.

  • xmlSecurity: the catalog now gives the defaults the data format uses: secureTagContents is true, and keyCipherAlgorithm has no default. The behaviour is unchanged.

camel-influxdb2 - a point without fields fails the exchange, and InfluxDB is called at startup only to create the organization or bucket

A Point without a field that has a value (no field at all, or only fields whose value is null, NaN or infinite) now fails the exchange with a CamelInfluxDb2Exception. This also applies to a Point converted from a Map body and to each point of a Points body. The InfluxDB client skipped such a point, so the exchange completed while nothing was written.

The endpoint now looks the organization up when it starts only when autoCreateOrg is true (the default), and the bucket only when autoCreateBucket is true (the default). With both options set to false it no longer calls InfluxDB when it starts, so the route starts while InfluxDB is down, and an organization or a bucket that does not exist fails the first write instead of being looked up. With autoCreateOrg=false and autoCreateBucket=true the organization is looked up only when the bucket must be created, and the endpoint fails to start with a message that names them when that organization does not exist.

camel-mail - the text of multipart mails, the charset of sent text, and producers on imap or pop3

A multipart mail without a text part, such as one with only a PDF attachment, now converts to a String or an InputStream as null. Previously it converted to jakarta.mail.internet.MimeMultipart@…​, and the type converter then remembered that conversion, so every later multipart mail in the CamelContext converted to such a text too. A Message whose content is null also converts to null.

A text part that Jakarta Mail has no content handler for, such as text/csv or text/calendar, now converts to its text in the charset of the part. Previously it converted to the name of a stream.

When the contentType of the mail producer is text/* and gives no charset (not in the option, the header or the CamelCharsetName exchange property), the Content-Type of the sent mail now declares the charset the text is encoded in: mail.mime.charset, else the default charset of the JVM (UTF-8). The bytes sent are unchanged. Previously the Content-Type had no charset, so a reader decoded the text as US-ASCII. This also applies when ignoreUnsupportedCharset=true drops an unsupported charset.

An endpoint on imap, imaps, pop3 or pop3s used as a producer now fails when the producer is created. Previously every send failed with NoSuchProviderException: invalid provider. Use smtp or smtps to send mails.

A mail read with mapMailMessage=false can now be sent with smtp: a MimeMessage body that comes from a mail store is sent as a copy, as the messages of a store are read-only. A MimeMessage built by the route is sent as it is.

Setting the body of the message of a mail consumer, also to null, now sticks. Previously a null body gave the content of the mail again when it was read next.

camel-smpp - the body of the producer, InOut, and unknown commands

After an InOut send, the message now keeps its body and the headers set before the send, and the result headers, such as CamelSmppId, are added. Previously the message had only the result headers and no body.

A CamelSmppCommand value that is not a command now fails with an IllegalArgumentException. Previously the body was sent as an SMS (SubmitSm). The names are case-sensitive, and a message without the header is still sent with SubmitSm.

SubmitSm, SubmitMulti and ReplaceSm read the body once, so a stream body is no longer sent as an empty short message. A body that is not a String or a byte[], such as a stream or a file, is replaced on the message by the value it is converted to. A message without a body fails with an SmppException caused by an InvalidPayloadException, instead of a NullPointerException.

With the dataCoding or alphabet option set to an 8-bit value, and neither the CamelSmppDataCoding nor the CamelSmppAlphabet header set, a byte[] body is now sent as it is, as with the headers. Previously its bytes were decoded as text, so bytes that are not text became ?. A text body is still encoded with the encoding option. A message whose data coding is 8-bit is now split by its length in bytes.

SmppSmCommand.getShortMessage(Message) now declares throws SmppException.

camel-influxdb - a query error fails the exchange, and the operation is checked at startup

InfluxDB answers a query it cannot run, such as a query on a database that does not exist, with the error inside the result. The producer used to set that QueryResult as the body, and the route continued as if the query had succeeded. An error in the result of the query, or of one of its statements, now fails the exchange with a CamelInfluxDbException whose message holds the error of the server.

The operation option now lists its values (insert, query and ping) and is not case-sensitive, so operation=QUERY runs a query. An endpoint with another operation now fails to start, where it used to start and fail every message with an IllegalArgumentException.

camel-paho, camel-paho-mqtt5 - a body that does not convert to bytes

The paho and paho-mqtt5 producers now fail with an InvalidPayloadException that names the type of the body for a body that does not convert to a byte[], such as a Map, a POJO or no body. Previously they failed with a NullPointerException with no message.

camel-spring-ai-chat - File, Path and stream bodies, media without a user message, and outputFormat

A java.io.File or java.nio.file.Path body of an image, audio, video or document is now sent as media, like a WrappedFile. An InputStream body is sent as media when the CamelSpringAiChatMediaType, CamelFileContentType or Content-Type header, or the extension in the CamelFileName header, names an image, audio, video or document (PDF, Word, Excel) media type. Previously both were sent as their text. A text file, a file with an unknown extension, and a stream with another or no media type are still sent as text.

A media body (byte[], File, Path, InputStream, WrappedFile or List<WrappedFile>) without the userMessage option or the CamelSpringAiChatUserMessage header now fails with an IllegalArgumentException that names them, before the media is read. Previously it failed inside Spring AI with "text cannot be null or empty".

With outputFormat set to BEAN, MAP or LIST, the format instructions are now appended to the user message, or to the last user message of CHAT_MULTIPLE_MESSAGES and CHAT_SINGLE_MESSAGE_WITH_PROMPT. Previously they replaced the text of the user message, so the model did not get the question, and they were an extra user message after a conversation or a prompt template.

CHAT_MULTIPLE_MESSAGES now takes String elements in the list and sends each one as a UserMessage. Another element fails with an IllegalArgumentException. Previously both failed with a ClassCastException.

camel-spring-rabbitmq - the content type and the correlation id of a consumer reply

The reply of a spring-rabbitmq consumer used to take the content type of the request whenever the route left the CamelSpringRabbitmqContentType header as the request set it. Now, when the route leaves the header unchanged:

  • A String reply keeps the content type of the request, as before.

  • A byte[] or stream reply keeps it too, unless it is a text content type such as text/plain. It is then sent as application/octet-stream, so the requester receives a byte[] and does not decode the bytes as text.

  • Any other reply body gets the content type that the message converter derives from it.

A content type that the route sets is used as it is. A reply whose body is a Spring AMQP Message now gets the correlation id of the request when it has none, so the requester no longer times out.

The producer is unchanged: a route that changes the type of the body of a consumed message and sends it on should remove the CamelSpringRabbitmqContentType header.

camel-elasticsearch - built requests, search bodies, DeleteIndex and file bodies

A request of the Elasticsearch client in the message body (an IndexRequest, UpdateRequest, GetRequest, DeleteRequest, SearchRequest, MsearchRequest, MgetRequest, BulkRequest or DeleteIndexRequest) is now sent as it is, like its builder. Before, a built IndexRequest was written as the document itself, with a generated id, a built UpdateRequest failed without the id header, and the other built requests failed with "Wrong body type".

The Search operation now uses the whole search request of a JSON text or Map body: its size, from, sort, aggregations, _source and the other members are no longer dropped, so the hits returned can change. A member that is not part of a search request now fails the exchange with an error that names it; before, it was dropped like every member other than query. A JSON object with a single member other than query, such as {"match": {…​}}, is still taken as the query alone. The size and from headers or options, when set, still override those of the body.

The DeleteIndex operation now deletes the index named by a String body when neither the CamelElasticsearchIndexName header nor the indexName option is set. Before, the call failed.

The Index and Update operations now fail with an IllegalArgumentException when the body is null, instead of failing with a Jackson error or an error of the server. A java.io.File, a java.nio.file.Path or the file of the file component is now read as the JSON of the document, instead of being written as a JSON string of its path.

camel-nats - the replies of an InOut consumer

A nats consumer with exchangePattern=InOut now answers with an empty reply when the route ends with no body. Previously it sent no reply, and the requester timed out. A body that does not convert to a byte[], such as a Map or a POJO, is still not sent, but the error now goes to the exception handler of the consumer, which logs it at WARN level.

A consumer with jetstreamEnabled=true and exchangePattern=InOut no longer publishes the reply to the ack subject of the JetStream message. InOut replies are for core NATS only, and the consumer logs a WARN when it starts.

camel-solr - the converter to Map, and bodies that are not sent

With camel-solr on the classpath, converting a value that is not a Solr response to a java.util.Map no longer gives an empty Map. It now fails, or gives null with tryConvertTo, as without camel-solr.

Without a body:

  • SEARCH and DELETE now fail with an InvalidPayloadException. Previously they searched for, or deleted, the text java.lang.Object@…​.

  • SEARCH still works with the CamelSolrQueryString header, or a CamelSolrParam.q header.

  • INSERT builds the document from the CamelSolrField.* headers.

  • INSERT and DELETE with request parameters, such as the commit of autoCommit=true, send only the parameters.

An INSERT of a body that is not a document, such as plain text, a byte[], a stream, a number or a bean without @Field, now fails with an InvalidPayloadException also when the request has parameters. Previously it reported success and stored nothing.

The catalog now says that deleteByQuery defaults to false, as it always did at runtime. A null value in a Map body or in a CamelSolrField.* header is left out of the document, instead of failing with a NullPointerException.

camel-opensearch - built requests, search bodies, DeleteIndex and file bodies

A request of the OpenSearch client in the message body (an IndexRequest, UpdateRequest, GetRequest, DeleteRequest, SearchRequest, MsearchRequest, MgetRequest, BulkRequest or DeleteIndexRequest) is now sent as it is, like its builder. Before, a built IndexRequest was written as the document itself, with a generated id, a built UpdateRequest failed without the id header, and the other built requests failed with "Wrong body type".

The Search operation now uses the whole search request of a JSON text or Map body: its size, from, sort, aggregations, _source and the other members are no longer dropped, so the hits returned can change. A JSON object with a single member other than query, such as {"match": {…​}}, is still taken as the query alone. The size and from headers or options, when set, still override those of the body.

The DeleteIndex operation now deletes the index named by a String body when neither the CamelOpensearchIndexName header nor the indexName option is set. Before, the call failed.

The Index and Update operations now fail with an IllegalArgumentException when the body is null, instead of failing with a Jackson error. A java.io.File, a java.nio.file.Path or the file of the file component is now read as the JSON of the document, instead of being written as a JSON string of its path.

camel-ssh - the shell channel and sleepForShellPrompt

An ssh endpoint with channelType=shell and no shellPrompt now fails when it starts. Previously each command failed with a NullPointerException.

sleepForShellPrompt now defaults to 100 milliseconds, as the documentation said. Previously it was 0, so the shell channel waited for the prompt in a busy loop.

The deprecated SshResult.STDERR is CamelSshStderr again. Previously it was CamelSshExitValue.

camel-slack - the JSON of the webhook mode

In webhook mode the slack producer now writes the JSON keys in snake_case, as Slack reads them, such as icon_emoji, icon_url, thread_ts, unfurl_links, image_url, alt_text, author_name and title_link, and short for the fields of an attachment. Previously they were written in camelCase, and Slack ignored them.

Other changes:

  • The send no longer changes the message body.

  • The channel, username, iconUrl and iconEmoji of the endpoint are set only when the endpoint sets them, so a SlackMessage body keeps its own values when the endpoint has none.

  • A com.slack.api.model.Message body also gets the icons of the endpoint.

  • A RuntimeException from the send now fails the exchange, instead of being thrown.

In token mode a SlackMessage body now sends its attachments too, not only its text.

The Slack CloudEvents data type transformer now sets CamelCloudEventSource to slack.<appId> and CamelCloudEventSubject to the channel. Previously they were slack.true and true.

camel-elasticsearch-rest-client - SEARCH body and the id of GET_BY_ID and DELETE

The SEARCH operation now sends a body that is not a Map (JSON text, bytes, a stream or a file) as the JSON query, when the CamelElasticsearchSearchQuery header is not set. Before, such a body was ignored and every document matched. A route whose SEARCH step gets a body left by a previous step, such as the id given by INDEX_OR_UPDATE, now sends that body as the query and fails: remove the body, or set the query in the header, to match every document as before.

The GET_BY_ID and DELETE operations now take the id of the CamelElasticsearchId header first, and the body only when the header is not set, as INDEX_OR_UPDATE does. Before, the body won over the header, so a body left by a previous step was used as the id.

camel-telegram - media bodies, messages without text, and answers that are not ok

The telegram producer changes:

  • A byte[] body with CamelTelegramMediaType=TEXT is now sent as a text message, decoded in the charset of the exchange. Previously it was sent as a document named file.

  • A stream, a file or a stream cache body with a media type other than TEXT is now sent as that media. Previously it failed with "Unsupported conversion from String".

  • An IncomingMessage body without text, such as a received photo, is no longer sent, as a null body is not. Previously its toString() was sent as the text.

  • An answer of the Bot API with "ok": false now fails the exchange with the error code and the description of the answer. Previously the body was a MessageResult with ok=false.

The bufferSize option no longer limits the size of a request, so media larger than 1 MB can be sent. It is now the initial size of the buffer, and its default changed from 1048576 to 8192. The parts of a multipart request that are numbers or booleans, such as duration or disable_notification, are now sent; previously they were sent empty. The text parts are written in UTF-8, as they declare.

camel-spring-ai-image - an ImagePrompt body uses the endpoint options, and a URL-only Image fails to convert

With an ImagePrompt body, the endpoint options and headers (n, width, height, model, responseFormat and style) now fill the generic options that the prompt leaves unset; the options the prompt sets still win. Before, an ImagePrompt body was sent as is, and the endpoint options and headers did not apply. The options of a provider, such as OpenAiImageOptions, are still sent as is.

The type converter from an Image that has only a URL (no base64 data) to byte[] or InputStream now fails with a message that suggests responseFormat=b64_json. Before, it gave the bytes of the URL text, so a route such as .to("spring-ai-image:gen").to("file:out?fileName=image.png") wrote the URL into the .png file. The conversion to String still gives the URL.

camel-hazelcast - getAll copies, map expired events, queue removeIf and clear errors

The getAll operation of hazelcast-list and hazelcast-set now sets a copy of the items as the message body (a java.util.ArrayList and a java.util.LinkedHashSet) instead of the distributed list or set itself. A later change of the body no longer changes the distributed collection, and the body no longer shows the changes made to it afterwards.

The hazelcast-map consumer now also routes an event when the time to live of an entry ends: the CamelHazelcastListenerAction header is expired, the key is in the CamelHazelcastObjectId header, and there is no body, as for evicted. Before, such an entry gave no event. A route that sends an unknown event to an error path must handle expired.

The removeIf operation of hazelcast-queue now works: it tests the items of the queue with the java.util.function.Predicate of the body, removes the matching ones, and sets the body to true when it removed an item. Before, it always failed with an UnsupportedOperationException. The operation is not atomic: the items are read first, and the matching ones removed in one call afterwards.

The following now fail with an IllegalArgumentException that names what is missing, where they failed with a NullPointerException before: a producer of hazelcast-map, hazelcast-multimap, hazelcast-replicatedmap, hazelcast-list, hazelcast-set, hazelcast-ringbuffer or hazelcast-pncounter with neither the CamelHazelcastOperationType header nor the defaultOperation option, get on hazelcast-list without the CamelHazelcastObjectIndex header, drainTo on hazelcast-queue without a Collection in the CamelHazelcastDrainToCollection header (a value that is not a Collection failed with a ClassCastException), removeIf without a Predicate body, getAndAdd on hazelcast-pncounter without a numeric body, and a hazelcast-seda producer with no body or a body that is neither Serializable nor convertible to bytes.

camel-spring-redis - commands the producer does not run fail, and a read that finds nothing gives no body

The spring-redis producer now fails the exchange for a CamelRedis.Command it does not run. Previously these did nothing, and the body stayed as it was:

  • An unknown command, such as a misspelled one, fails with an IllegalArgumentException.

  • AUTH, BGREWRITEAOF, BGSAVE, CONFIG, DBSIZE, DEBUG, FLUSHALL, FLUSHDB, INFO, LASTSAVE, MONITOR, PUNSUBSCRIBE, RENAMEX, SAVE, SELECT, SHUTDOWN, SLAVEOF, SUBSTR, SYNC and UNSUBSCRIBE fail with an UnsupportedOperationException.

  • SUBSCRIBE and PSUBSCRIBE fail too, as they are for the consumer.

A command in mixed case, such as Get, now runs the command. ZSCORE is now implemented and returns a Double.

The commands that read one value set the body to null when they find none, so a route can tell a miss from a value. Previously the body stayed as it was. These are GET, GETSET, HGET, LINDEX, LPOP, RPOP, BLPOP, BRPOP, RPOPLPUSH, BRPOPLPUSH, SPOP, SRANDMEMBER, RANDOMKEY, ZSCORE, ZRANK, ZREVRANK and GEODIST. For BLPOP and BRPOP, this includes when the timeout passes.

Other changes:

  • MSETNX sets its Boolean result as the body.

  • ECHO without the CamelRedis.Value header fails with a NoSuchHeaderException, instead of a NullPointerException.

  • SETBIT and GETBIT serialize the key with the key serializer of the template, as the other commands do. With the default JDK serializer they now work on the same Redis key as SET and GET. Bits stored by older versions under the raw key are not found.

  • The CamelRedis.Keys header may be a String: one key, or keys separated by commas.

The consumer now gives a message that its serializer cannot deserialize, such as one published by redis-cli, to the route as a byte[]. Previously the message was dropped with a WARN. A failed exchange of the consumer is now handed to its exception handler.

camel-spring-ws - the reply of a consumer, and File and stream bodies of the producer

A spring-ws consumer route that sets the body to null in a processor, such as with exchange.getMessage().setBody(null), now answers with an empty SOAP body. Previously the message created its body again from the request, so the request payload was sent back. Likewise, an OUT message that the route creates with exchange.getOut() no longer starts with the request payload as its body.

A reply body that does not convert to a javax.xml.transform.Source, such as a Map, now gives a SOAP fault with the conversion error. Previously it gave an empty SOAP body. A reply body that is an InputStream is now sent as XML.

The spring-ws producer now sends a File body, the file of a file or ftp consumer, and an InputStream body (also when stream caching is off) as XML. Previously these failed with an InvalidPayloadException.

camel-ignite - cache PUT of a Map, the existing query results, RUN bodies and missing operations

With fireExistingQueryResults=true, the ignite-cache consumer now sends each entry that the initial query finds with the value as the body and the key in the CamelIgniteCacheKey header, like the events that follow. Before, the body was the javax.cache.Cache.Entry and the key header was not set.

A PUT of a Map body on ignite-cache with the CamelIgniteCacheKey header now stores the Map as the value of that key. Before, the header was ignored and all the entries of the Map were put. The same holds with treatCollectionsAsCacheObjects=true, where a PUT of a Map without the header now fails, as there is no key to store it under. A PUT of a Map without the header still puts all its entries, and now keeps the body when propagateIncomingBodyIfNoReturnValue is true (the default). Before, the body was null after such a PUT.

The RUN and AFFINITY_RUN execution types of ignite-compute have no result, and now keep the body (the job) when propagateIncomingBodyIfNoReturnValue is true (the default). Before, the body was null. Set propagateIncomingBodyIfNoReturnValue=false to keep the previous behaviour.

The ignite-cache, ignite-idgen, ignite-queue and ignite-set producers now fail with an IllegalArgumentException that names the operation option and the operation header when neither sets the operation. A missing body now fails with an InvalidPayloadException that names what the operation needs: the job for ignite-compute (except EXECUTE with the taskName option, which now runs the task with the CamelIgniteComputeParameters header), the number to add for ADD_AND_GET and GET_AND_ADD of ignite-idgen, and the element for ADD, CONTAINS, REMOVE and RETAIN_ALL of ignite-queue and ignite-set and for OFFER and PUT of ignite-queue. Before, these failed with a NullPointerException. The GET, GET_AND_INCREMENT and INCREMENT_AND_GET operations of ignite-idgen no longer fail on a body that is not a number, as they do not use it.

camel-couchbase - the document id, the operation option and the bodies of CCB_PUT

The couchbase producer now uses the key option as the document id when the message has no CamelCouchbaseId header. When neither gives an id, and autoStartIdForInserts is not enabled, the call fails. Previously the document was written under the id of the Camel endpoint, such as endpoint6, and the key option was not read. The producer no longer removes the CamelCouchbaseId header after the call.

The operation option is now an enum of CCB_PUT, CCB_GET and CCB_DEL. Any other value, including one in lower case, fails when the producer starts. Previously it did nothing.

CCB_PUT now stores the body as follows:

  • Text or a byte[] that holds a JSON object or array is stored as a JSON document. Previously text was stored as a JSON string, and a byte[] failed.

  • Any other byte[] is stored as a binary document. Read it back with contentAsBytes() of the GetResult.

  • A stream is read as bytes, and stored as a JSON document or as a binary document by the same rule. Previously it failed.

  • Other text, a Map, a List, a JsonObject or a POJO is stored as before.

The SQL++ query that the consumer generates when neither statement nor collection is set now reads the default collection of the bucket, FROM bucket._default._default AS bucket . Previously it failed on every poll with "Keyspace not found", as the query runs on the scope. For the same reason, an explicit statement that names the bucket in FROM fails: use the collection, such as FROM _default , or the full path.

In view mode, the rows of a reduce view and the rows with a compound key no longer fail the poll. The body of a reduce row is its value, and it has no CamelCouchbaseId header. A compound key is set in CamelCouchbaseKey as its JSON text.

camel-jolt, camel-jsonata - JSON text, bytes, streams and files are parsed with the default inputType

With the default inputType (Hydrated for jolt, Jackson for jsonata), a body that is bytes, an InputStream (including a stream cache) or a file is now parsed as JSON, and so is text whose first non-whitespace character is { or [. Previously such a body reached the specification or expression unparsed: jolt gave null or the text back, and jsonata read the text as one JSON string, refused bytes and failed on a stream. The bytes are decoded in the charset of the exchange when one is set; otherwise the JSON parser detects the encoding. Such text that is not valid JSON now fails with the parse error. Other text, a Map, a List and other objects are used as before.

camel-jsonata - the endpoint options of an http resource

For a jsonata:http: or jsonata:https: resource, the options of the endpoint, such as inputType, outputType and prettyPrint, are now used by the endpoint. Previously they were appended to the URL of the resource and sent to the server, and the endpoint used its defaults. Only the other query parameters are sent to the server.

camel-state-store - a stream body is stored as its bytes, and a Reader as its text

The put and putIfAbsent operations of the state-store component now read a stream body fully into memory and store its content: an InputStream or the stream cache of Camel as its bytes, so a later get gives a byte[], and a Reader as its text, a String, like a text body. Before, the stream object itself was stored: the first get could read it, later ones got an empty stream, and every exchange that got the key shared the same stream object. A backend that serializes its values could not store such a stream at all. Other bodies are still stored as they are.

The charset of the stored bytes is not stored with them. A convertBodyTo(String.class) after a get decodes them in the charset of that exchange, UTF-8 by default, so use convertBodyTo(String.class, "<charset>") when the stored bytes are not UTF-8.

camel-saxon - the xquery endpoint is producer-only

The xquery endpoint can only be used as a producer. A route that starts from("xquery:…​") now fails when it starts, with a message that says to use the endpoint in to. Previously it polled, and every poll failed because the query had no message to run on. The consumer and scheduler options (such as delay, initialDelay, sendEmptyMessageWhenIdle, bridgeErrorHandler and the scheduler and backoff options) are removed from the endpoint, and bridgeErrorHandler from the component.

camel-duckdb - the CamelDuckDbDatabasePath header needs the allowDatabasePathFromHeader option

The CamelDuckDbDatabasePath header made the duckdb producer open a short-lived connection to the DuckDB file at the path that the header named, instead of using the databasePath, jdbcUrl or DataSource of the endpoint, and DuckDB created that file when it did not exist. The header is now ignored unless the endpoint sets the new allowDatabasePathFromHeader=true option (disabled by default), so a message no longer moves the endpoint to another database file. A route that relies on the header must set this option on the endpoint.

The CamelDuckDbOperation, CamelDuckDbQuery and CamelDuckDbTable headers still override the operation, the SQL and the table of the endpoint, as before.

camel-schematron - no body fails, and bytes are read in the encoding of the XML declaration

A message without a body now fails with an InvalidPayloadException instead of a NullPointerException.

A body that is bytes, a stream or a file is given to the XML parser as bytes, so it is read in the encoding of the XML declaration (UTF-8 when there is none), unless the exchange has a charset set. A String body is read as text. Previously the body was converted to a String and encoded again in the platform charset, so a document whose declared encoding was not the platform charset could be read wrong and its validation result may now differ.

camel-mustache - contentCache=false reads the template for each exchange

With contentCache=false, the template is now read and compiled for each exchange, so a changed template is used. Previously it was compiled once, and only the JMX operation clearContentCache read it again. The default, contentCache=true, is unchanged.

camel-clickhouse - the database and table headers need the allowDatabaseAndTableFromHeaders option

The CamelClickHouseDatabase and CamelClickHouseTable headers took the place of the database and the table of the clickhouse endpoint, so a message could insert into, or query, another database or table than the one the endpoint is configured with. These headers are now ignored unless the endpoint sets the new allowDatabaseAndTableFromHeaders=true option (disabled by default). A route that relies on them must set this option on the endpoint.

An insert endpoint without a table, which took the table from the CamelClickHouseTable header, now fails with an error that asks for the table, unless it sets allowDatabaseAndTableFromHeaders=true.

The CamelClickHouseOperation and CamelClickHouseFormat headers still override the operation and the format of the endpoint, as before.

camel-stub - a stub endpoint without a consumer keeps its latest messages

A stub endpoint that no route consumes from, such as the endpoints camel run --stub stubs, keeps the latest size messages (1000 by default) and drops the oldest one when the queue is full. Previously the producer failed with IllegalStateException: Queue full after size messages, or with blockWhenFull=true waited for a consumer that never came. The on-completions of a dropped message run as done. With a consumer, the stub endpoint queues as seda does.

Browsing a stub endpoint with a limit, such as camel cmd stub --browse --limit, now shows the latest messages. Previously the newest message was left out.

camel-stream - stream:header without the stream header fails

The stream:header producer now fails with a NoSuchHeaderException when the stream header is missing or is not an OutputStream. Previously it failed with a NullPointerException, or did nothing when the body was null.

camel-knative - a structured CloudEvent whose Content-Type has parameters

The consumer now finds the content mode of a CloudEvent by the media type of the Content-Type header, ignoring its parameters and its case. A request with Content-Type: application/cloudevents+json; charset=UTF-8 is now read as a structured CloudEvent: the body is the data field and the attributes are set as the CloudEvent headers. Previously it was read as binary mode, and the body was the whole JSON envelope. A batch content type with parameters is now refused as unsupported, as the plain one is.

Producers that pick their operation by a header - an unknown value fails

A producer that picks its method by a header or an option (a HeaderSelectorProducer, used by camel-caffeine, camel-consul, camel-dynamic-router, camel-ehcache, camel-infinispan, camel-servicenow, camel-spring-redis and camel-web3j) now fails the exchange with an IllegalArgumentException, such as Unsupported value NOSUCH of header CamelCaffeineAction, when no method handles the value. Previously the exchange completed with no error and the body unchanged.

The SPI InvokeOnHeaderStrategy has a new constant NO_METHOD, which the strategies generated by the Camel tooling now return for a key without a method. A strategy generated by older tooling returns null and keeps the old behaviour. The strategy of a parent class is now tried only for NO_METHOD, so a void method no longer runs the parent’s strategy too; a third-party producer with a parent class that has its own strategy must have its strategies regenerated with the 4.24 tooling.

API components - the inBody body is converted to the type of its parameter

The API components (camel-as2, camel-box, camel-braintree, camel-dhis2, camel-fhir, camel-google-calendar, camel-google-drive, camel-google-mail, camel-google-sheets, camel-olingo2, camel-olingo4, camel-twilio and camel-zendesk) now convert the message body to the type of the method parameter that inBody names, for example a byte[], a stream or a file to a String. Previously the body was passed as it was, and a body of another type failed with argument type mismatch; a body that cannot be converted now fails before the call, with an error that names the property.

A header named exactly as the prefix of an API component, such as CamelFhir or CamelFhir., is now ignored. Previously it failed every call with a StringIndexOutOfBoundsException.

camel-google-sheets - splitResult is true by default

The splitResult option of the google-sheets consumer now defaults to true, as documented and as the other API components do: an array or collection result gives one exchange per element. Set splitResult=false for one exchange with the whole result.

camel-aws2-sns - string array message attributes

String values in list-valued message headers are now JSON-escaped when mapped to SNS String.Array message attributes. Quotes, backslashes and control characters are preserved as string data. Routes should supply the original strings rather than pre-escaping them. Other member types, such as enums, characters, UUIDs and dates, are serialized as JSON strings. Non-finite floating-point values (NaN and infinities) are also serialized as strings; finite numbers, booleans and null remain JSON primitives. The JSON utility also escapes forward slashes and some Unicode characters, so raw String.Array attribute text may differ even when its decoded string values are unchanged.

camel-aws2-sqs - sendBatchMessage of more than 10 entries

With more than 10 entries, which are sent in requests of 10, the message body is now one SendMessageBatchResponse with the successful and failed entries of all the requests. Previously the body had the entries of the last request only. An element of the body that is not a String is now converted to a String, instead of failing with a ClassCastException.

camel-aws2-kinesis - a Path body is one record

A java.nio.file.Path body is now sent as one record with the content of the file, as a java.io.File is. Previously it was sent as a batch of its name elements, which failed. A body that does not convert to a byte[] now fails with an InvalidPayloadException instead of a NullPointerException.

camel-aws2-s3 - headBucket and headObject

The headBucket and headObject operations now take the bucket from the endpoint (or the CamelAwsS3OverrideBucketName header) and the key from the keyName option (or the CamelAwsS3Key header), as the other operations do. Previously they failed without the headers.

camel-aws2-cw - the metric data of putMetricData

  • A Collection or an array of MetricDatum in the body is now sent. Previously only a List was, and a Set or an array was ignored, so the metric was taken from the headers and options. A collection or an array with an element that is not a MetricDatum now fails with an IllegalArgumentException.

  • The unit option and the CamelAwsCwMetricUnit header now match the unit in any case (BYTES is Bytes). An unknown unit now fails, where it was sent as null before.

  • A java.util.Date in the CamelAwsCwMetricTimestamp header is now used. Previously it was ignored. The catalog lists the header as an Instant.

camel-pinecone - default topK, generated UPSERT id and the pinecone:rag transformer

QUERY and QUERY_BY_ID without the CamelPineconeQueryTopK header now return 10 matches, as camel-weaviate does. Before, they failed with a NullPointerException.

UPSERT without the CamelPineconeIndexId header now stores the vector under a random UUID, as the pinecone:embeddings data type transformer does, and sets the CamelPineconeIndexId header to that id. Before, it failed with a PineconeValidationException.

The pinecone:rag data type transformer now reads the QueryResponseWithUnsignedIndices body that a QUERY or QUERY_BY_ID leaves, and returns the texts of its matches. Before, it returned an empty List for that body. A route that passes the matchesList of the response to the transformer gets the same result as before.

camel-aws2-lambda - the code of createFunction and updateFunction, and the payload of invokeFunction

  • createFunction and updateFunction: the CamelAwsLambdaZipFile header, or the S3 location of the CamelAwsLambdaS3Bucket and CamelAwsLambdaS3Key headers, now wins over the message body. The body is the zip of the code only when neither is set. Previously the body won over the zip file header, and with the S3 headers both a zip and an S3 location were sent. Setting the zip file header and the S3 headers together, or only one of the S3 bucket and key headers, now fails. A body that is not the zip as bytes, a stream or a file now fails with an IllegalArgumentException instead of a NullPointerException.

  • invokeFunction without a body now invokes the function without a payload, instead of failing with a NullPointerException. A byte[] or an InputStream body is sent as its bytes, and another body as text in UTF-8 (previously in the default charset of the JVM).

camel-google-calendar - the CloudEvent transformer on a body that is not an Event

The google-calendar-stream:application-cloudevents data type transformer now fails with a CamelExecutionException that says the body must be a Google Calendar Event when the body is not one, and it sets no CloudEvent header then. Before, it failed with a NullPointerException. The body of the google-calendar-stream consumer is always an Event, so routes that use the transformer after that consumer are not affected.

camel-aws2-eventbridge - putEvent fails when the event is rejected

When EventBridge rejects the event, for example with MalformedDetail for a detail that is not a JSON object, the putEvent operation now fails the exchange with a CamelExecutionException that has the error code and message. Previously only the CamelAwsEventbridgeFailedEntryCount header showed it; the header is still set. Without a body, putEvent now fails with the InvalidPayloadException itself instead of a RuntimeException. The ARNs of the CamelAwsEventbridgeResourcesArn header are now trimmed.

camel-aws2-ses - raw mails

  • A RawMessage body is now sent as a raw mail (sendRawEmail), so the recipient gets the mail itself. Previously it was sent as a plain mail whose text was the MIME source of the raw mail, with the subject of the endpoint.

  • A SendRawEmailRequest body is now sent as it is. Previously it was sent as its toString().

  • For a raw mail (a jakarta.mail.Message or a RawMessage body), the cc and bcc options and headers are now added to the destinations together with the to ones. When none is set, SES sends the mail to the recipients of its own headers.

  • A plain mail without a body now fails with an InvalidPayloadException before it is sent to SES.

camel-aws-secrets-manager - binary secrets

  • With binaryPayload=true, createSecret, updateSecret and putSecretValue now store the bytes of the message body as the binary of the secret. Previously they stored the Base64 text of the body, which the AWS SDK Base64-encodes once more, so other clients read the Base64 text instead of the value.

  • getSecret now gives the value the secret has, whatever binaryPayload is: a byte[] for a binary secret, a String for a string secret. batchGetSecret gives a List with such a value per secret, and the CamelAwsSecretsManagerSecretVersionIds header lines up with it. batchGetSecret now fails when AWS reports errors for some of the secrets, which were dropped before.

  • The aws: properties function reads a binary secret as the UTF-8 text of its bytes. Previously it decoded it from Base64 and failed for a binary secret that another client stored.

  • Without an operation, the producer now fails with an IllegalArgumentException instead of a NullPointerException.

A binary secret that an earlier Camel version stored with binaryPayload=true holds the Base64 text of the value, and now reads back as that text, in getSecret and in the properties function. Store such secrets again, or decode the value from Base64 in the route.

camel-aws2-iam - the access key status in any case

The CamelAwsIAMAccessKeyStatus header now matches Active and Inactive in any case. An unknown status now fails in Camel, before the request is sent.

camel-aws2-ddb - the operation of the JSON data type transformer

The aws2-ddb:application-json data type transformer now takes the operation only from the route: the CamelAwsDdbOperation header, else the operation exchange property. When the route names neither, the transformer no longer sets the CamelAwsDdbOperation header (and the CamelAwsDdbReturnValues header), so the operation option of the endpoint applies. It then sets the item, key and update values headers that PutItem, UpdateItem and DeleteItem need. Previously the transformer took the operation from a top-level operation field of the JSON body, else PutItem, and always set the header, which won over the operation of the endpoint: aws2-ddb:orders?operation=DeleteItem put the item.

A JSON body with a top-level operation field now fails with an IllegalArgumentException, unless the route names that same operation. A route that chose the operation in the body must set the header or the exchange property instead. The aws-ddb-sink Kamelet sets the exchange property, and is not affected.

camel-aws2-ddb - result headers

  • DescribeTable and DeleteTable no longer set the CamelAwsDdbTableName header. It is also the header that names the table of an aws2-ddb step, so the next step went to that table instead of the one of its endpoint.

  • UpdateTable now sets the headers of the table description of the response (CamelAwsDdbProvisionedThroughput, CamelAwsDdbCreationDate, CamelAwsDdbTableItemCount, CamelAwsDdbKeySchema, CamelAwsDdbTableSize and CamelAwsDdbTableStatus), and DescribeTable also sets CamelAwsDdbProvisionedThroughput.

  • Query and Scan now ask for the consumed capacity, so the CamelAwsDdbConsumedCapacity header has a value. It was always null before.

  • The catalog now gives the types the headers have, such as TableStatus for CamelAwsDdbTableStatus and Instant for CamelAwsDdbCreationDate, and lists the CamelAwsDdbNextToken header and the headers of aws2-ddbstream.

camel-azure-storage-blob - getBlob writes only into an OutputStream body

The getBlob operation now writes the blob into the message body only when the body is an OutputStream. A body that only converts to one, such as a java.io.File, a java.nio.file.Path or the file of a file consumer, is replaced by an InputStream of the blob, as any other body is. Previously such a file was overwritten with the blob, also with noop=true on the file consumer. To save a blob into a file, use the downloadBlobToFile operation, or a file endpoint after getBlob.

uploadBlockBlob, uploadBlockBlobChunked and commitAppendBlob without a body now fail with an InvalidPayloadException instead of a NullPointerException, and commitAppendBlob and uploadPageBlob without a body no longer create an empty blob first.

camel-azure-storage-queue - sendMessage without a body

sendMessage without a body now fails with an InvalidPayloadException, before the queue is created, instead of a NullPointerException of the Azure SDK.

camel-azure-servicebus - the body of the consumer converts to bytes and streams

The body that the consumer gives is a com.azure.core.util.BinaryData, which now converts to byte[] and InputStream as well as to String, so an unmarshal, a file producer or convertBodyTo byte[] can follow the consumer directly.

camel-google-pubsub - the producer sends the content of streams and files

A body that is not a String or a byte[] is now converted to a byte[] first, and only a body that does not convert goes to the serializer (Java serialization by default). So a stream, a stream cache, a java.nio.file.Path and the file of a file consumer are now sent as their content, where they failed with a NotSerializableException, and a java.io.File is sent as the content of the file instead of as a serialized File object. A Map or a POJO is serialized as before, and a custom serializer is now called only for such bodies. A message without a body now fails with an InvalidPayloadException, where it published a serialized null.

camel-ibm-cos - the content length of putObject

putObject now takes the content length from the body when the length of the body is known (a byte[], a String, a stream cache, a file), and uses the CamelIBMCOSContentLength header only for a stream of unknown length. Previously the header won, so a route that read an object, changed the body and wrote it failed, as the header still had the length of the object it read.

camel-minio - the getObjectRange operation

The getObjectRange operation, as the catalog and the documentation name it, now works for the operation option and the CamelMinioOperation header; it failed at startup. The former name getPartialObject still works, but is deprecated.

camel-docling - a stream body

An InputStream body, such as a stream cache, is now written to a temporary file as a byte[] body is, within maxFileSize, instead of failing with an InvalidPayloadException.

camel-kafka - only a JSON array is sent as one record per element

With useIterator=true (the default), the producer now splits a Jackson JSON node only when it is an array, for Jackson 2 and Jackson 3. An object node is now sent as one record; previously it was sent as one record per field value, without the field names, as the 4.22 upgrade guide said. A Jackson 3 scalar node is now sent as one record, where nothing was sent before.

camel-dns - dig without a type, and the wikipedia type

dns:dig without the CamelDnsType header now asks for type A, and without the CamelDnsName header fails with an IllegalArgumentException that names the header; both failed with a NullPointerException before. The wikipedia type and the CamelDnsTerm header are deprecated, as the service behind them is gone.

camel-as2 - client/send

  • A partner answer with an HTTP status outside 2xx, such as a 500 when the route of the partner fails, now fails the exchange. Previously the exchange succeeded with a null body. The CamelAs2.interchange exchange property is still set.

  • The EDI message can now be a byte[], a java.io.File, the file of a file consumer, or anything that converts to bytes, as well as text and a stream.

  • The requestUri option now defaults to / for client/send, as the catalog says.

  • The ediMessageType option is deprecated: use ediMessageContentType. When only ediMessageType is set, client/send uses it as the content type.

  • ediMessageTransferEncoding=quoted-printable now encodes the message as quoted-printable. Previously it was encoded as Base64 but labelled quoted-printable, which the receiver rejected.

camel-as2 - server/listen - signature and MIC over the message as received

The signature of a multipart/signed message is now verified over the signed part exactly as it was received, its MIME headers and its body in the transfer encoding the sender used (RFC 1847, section 2.1), and the Received-Content-MIC of the MDN is computed over the same bytes (RFC 4130, section 7.1). Previously both were computed over a re-serialisation of the parsed part, which matches the sender’s bytes only when the part is binary, 7bit or 8bit and its headers are formatted the way camel-as2 writes them: a base64 or quoted-printable part from another AS2 implementation, encoded with a different line length or escaping, or a part with folded headers or no space after the colon, was refused with authentication-failed although correctly signed, and the MDN carried a MIC the sender could not match. Messages from camel-as2’s own client are not affected. MultipartSignedEntity.getSignedDataRaw() returns the part as received, getSignedDataContent() the bytes a signature is verified against.

The text of a body part now ends exactly where the boundary delimiter begins: the line break before the delimiter line is removed as the sender wrote it, CRLF or a bare LF. Before, two characters were always removed, so a part framed with bare line feeds (as openssl cms -sign writes them without -crlfeol) lost its last byte.

camel-graphql - the HTTP headers of the request

The Content-Type, Accept and Accept-Encoding headers of the request are now always the ones of the producer (JSON), whatever headers the message has, and a Content-Encoding header of the message is no longer sent. The headers named by the queryHeader and variablesHeader options are no longer sent as HTTP headers. After the call the CamelHttpMethod, Accept and Accept-Encoding headers that the producer set no longer stay on the message, so a later http producer no longer sends a POST because of them. When the body is the query, a body that is not text, bytes, a stream or a file, such as a Map, now fails instead of being sent as its toString(), and a Map body (or a Map in the variables header) is used as the variables.

camel-vertx-http - Path and Reader bodies, and urlencoded forms

A java.nio.file.Path body is now sent as a file and a Reader body as its text; both failed before. The values of an application/x-www-form-urlencoded String body are now decoded before Vert.x encodes the form, so an escape such as %26 is no longer sent encoded twice, and a name that is repeated sends all its values. RAW(…​) values are no longer recognized in such a form body.

camel-github2 - getCommitFile

getCommitFile now takes the commit from the CamelGitHubCommitSha header, which must be a commit id of 40 hexadecimal characters, and the path of the file from the new CamelGitHubFilePath header, or else the body, and gives the content of the file at that commit. Previously the body was taken as the commit when the header was not set, and the operation could not give the content of a file.

camel-pdf - page sizes, extractText and line ends

  • The pageSize values of the catalog (LETTER, LEGAL, A0 to A6) now work: pageSize=A4 gave a Letter page before. The former names such as PAGE_SIZE_A4 still work. An unknown value now fails when the endpoint is created.

  • extractText now reads a byte[], a ByteArrayOutputStream (the output of create and merge), a stream, a file or a Path, so create followed by extractText works. Another body, or none, fails with an InvalidPayloadException.

  • create without a body fails with an InvalidPayloadException. With the default lineTermination, a CR LF or a CR now ends a line, a tab is written as spaces and other control characters are left out; they failed before.

  • The CamelPdfFilesToMerge header of merge can also hold java.nio.file.Path elements.

camel-tika - the body and the charset of the output

detect and parse fail with an InvalidPayloadException for a body that does not convert to a stream, or no body, instead of a NullPointerException. parse now sets the charset of the exchange to tikaParseOutputEncoding, so the output converts to text in that charset.

camel-infinispan-embedded - the configurationUri

The configurationUri of the component now reads a file: URI, an absolute path and property placeholders, as well as a name on the class path. The configurationUri, cacheContainer, cacheContainerConfiguration and flags options are read on the component only: set on an endpoint they are not used.

camel-snmp - the producer and the poll consumer

  • A producer without type now sends a GET with the oids of the endpoint, where it sent a GET without them, and without a port in the URI a producer that is not of type TRAP now uses port 161 instead of 162.

  • The trap producer fails with an InvalidPayloadException for a body that is not an org.snmp4j.PDU.

  • The SnmpMessage body of the producer converts to the XML text of its PDU, as the body of the consumer is.

  • The POLL consumer now waits for the answer of each poll, and a poll without an answer counts as an empty poll, so sendEmptyMessageWhenIdle and the backoff options work. SnmpOIDPoller no longer implements ResponseListener.

camel-jdbc - generated keys, outputClass and the body

The CamelGeneratedKeysRows header is now always a List of Maps of the key columns: the outputClass and readSize options no longer apply to it. An outputClass that does not resolve fails with an IllegalArgumentException that names the class, and a message without a body fails with an InvalidPayloadException. The transacted option is deprecated, as it is not used.

camel-jsch - scp without a known hosts file

With useUserKnownHostsFile=false and no knownHostsFile, scp no longer fails with a NullPointerException: no host is known, and strictHostKeyChecking decides whether to connect.

camel-aws2-polly - textType, outputFormat and the next token

  • The textType and outputFormat options no longer have a default value, so the CamelAwsPollyTextType and CamelAwsPollyOutputFormat headers are now read when the option is not set. Previously the defaults TEXT and MP3 were always set, and the headers were ignored: SSML sent with CamelAwsPollyTextType=ssml was spoken as plain text. When neither the option nor the header is set, TEXT and MP3 are still sent.

  • describeVoices, listLexicons and listSpeechSynthesisTasks now read and set the new CamelAwsPollyNextToken header: it is sent as the token of the next page, and set to the token of the response, or removed on the last page. A route that calls a different list operation on the same message must remove the header first.

  • The lexicon names are trimmed, and empty names are dropped.

camel-aws2-translate - the languages and the code of Croatian

  • Each language now comes from its option, or else from its header. Previously, when one of the sourceLanguage and targetLanguage options was set, both were taken from the options, and the request had no language for the other.

  • A missing target language, or a missing source language without autodetectSourceLanguage, now fails with an IllegalArgumentException that names the option and the header.

  • Translate2LanguageEnum.CROATIAN is now hr, the code Amazon Translate uses. It was cr, which is Cree.

camel-aws2-textract - feature types and the job id

  • The CamelAwsTextractFeatureTypes header now also takes Strings (in any case), as a list, an array or one String with comma separated values. An unknown value, or an element of another type, fails with an IllegalArgumentException instead of a ClassCastException in the AWS SDK. A single String, which was ignored before, is now used.

  • startDocumentTextDetection, startDocumentAnalysis and startExpenseAnalysis now set the CamelAwsTextractJobId header to the job id of the response, so a get step that follows uses it. The body is still the response.

camel-aws2-timestream - describeScheduledQuery and the operation

  • describeScheduledQuery now sends the ARN of the CamelAwsTimestreamScheduledQueryArn header. Previously it worked with pojoRequest=true only.

  • Without an operation option and an operation header, the producer fails with an IllegalArgumentException instead of a NullPointerException. An operation of the other client type fails with a message that names the client type to use; when it is the operation option, the route already fails to start.

  • An unknown client type in the URI fails with Unknown clientType …​: use write or query.

  • The catalog now describes CamelAwsTimestreamRecord as what writeRecords sends it as: the common attributes of the records of CamelAwsTimestreamRecordList. CamelAwsTimestreamRecordVersion is listed as a Long, the type it is read as.

camel-aws2-s3-vectors - headers, returned data and metadata filter

  • The consumer and the producer no longer set the CamelAwsS3VectorsVectorBucketName and CamelAwsS3VectorsVectorIndexName headers. They only echoed the endpoint, and the next aws2-s3-vectors step of the route then used that bucket and index instead of its own: a route from the consumer to putVectors on another index overwrote the consumed vectors. The headers still override the endpoint when the route sets them.

  • getVectors and deleteVectors take their keys from the message body when it holds keys (a String, or a collection or an array of Strings), else from the CamelAwsS3VectorsVectorId header. Previously the header won, so a getVectors after a putVectors got the key of the putVectors. Keys that are not Strings now fail with an IllegalArgumentException.

  • getVectors now returns the data and the metadata of the vectors, and queryVectors and the consumer return the metadata and the distance. Previously they returned the keys only. The consumer sets the new CamelAwsS3VectorsDistance header.

  • The CamelAwsS3VectorsMetadataFilter header and the consumerMetadataFilter option are now applied. They are JSON objects in the filter syntax of S3 Vectors. Invalid JSON fails the exchange, or the consumer does not start. Previously both were ignored.

  • The similarityThreshold option and the CamelAwsS3VectorsSimilarityThreshold and CamelAwsS3VectorsSimilarityScore headers are deprecated. They never did anything: S3 Vectors returns a distance and has no threshold.

  • The values of CamelAwsS3VectorsVectorMetadata may be numbers, booleans and lists, which failed with a ClassCastException before. A null in a vector fails with an IllegalArgumentException that names its position.

  • The dataType option no longer offers float16, and the distanceMetric option no longer offers dot-product: S3 Vectors has neither.

camel-aws-security-hub - headers of another type and failed imports

  • A CamelAwsSecurityHubFilters, CamelAwsSecurityHubNote, CamelAwsSecurityHubSeverity or CamelAwsSecurityHubWorkflow header that is not of the AWS SDK type (AwsSecurityFindingFilters, NoteUpdate, SeverityUpdate, WorkflowUpdate) now fails with an IllegalArgumentException. Previously it was ignored: getFindings with the filters as JSON text returned every finding.

  • batchImportFindings now fails the exchange with an IllegalStateException when Security Hub does not import some of the findings. The message names their ids and errors. The CamelAwsSecurityHubFailedCount and CamelAwsSecurityHubSuccessCount headers and the body are still set.

  • batchImportFindings also takes an array or any collection of AwsSecurityFinding in the body. An element of another type fails with an IllegalArgumentException instead of a ClassCastException.

camel-aws-cloudtrail - the content type of the CloudEvent data type

The aws-cloudtrail:application-cloudevents data type now sets the content type application/json, as the body is the JSON of the event. It was text/plain.

camel-python - a script gives the Java value of its result

The python language (Jython) now gives the Java value of what a script returns, instead of its Python text from str(). None is null, a string, a number or a boolean is the Java String, number or Boolean, a dict, list, tuple or set is a Map, List or Set of Java values, and a Java object bound to the script, such as the body, is returned as it is. For example 1 + 2 gives the Integer 3 instead of the text 3, and body on a byte array gives the byte array instead of the text array('b', […​]). Set resultType to java.lang.String to get text.

This also changes predicates: None was the text None, which counted as true, and is now null, which is false. An empty list was the text [], which counted as true, and is now an empty collection, which is false.

An error while a script runs, such as a NameError, now fails with ExpressionEvaluationException instead of ExpressionIllegalSyntaxException, which is kept for a script that does not compile.

camel-openfeature - an expression gives the value of a boolean flag

Used as an expression, for example in setBody, setHeader or setProperty, the openfeature language now gives the Boolean value of a boolean flag. Before, it evaluated every flag as a string flag, so a boolean flag gave the defaultValue of the endpoint as text (false unless set) and logged a TYPE_MISMATCH warning. String flags give their text as before, and number and object flags still give the defaultValue. Predicates are unchanged.

camel-datasonnet - a null result and a stream body

An expression whose result is null now gives null instead of a Document without content, unless resultType is com.datasonnet.document.Document. A predicate whose result is null is false; before, it failed with a NullPointerException.

A stream body without a body media type is now read as text. Before, it failed while the stream was mapped as a Java object.

camel-hl7 - the hl7terser language reads a stream or a file

The hl7terser language now reads a stream or a file body as an HL7 message, in the charset of MSH-18 as the HL7 data format does. A body that is not an HL7 message, such as a Map, or no body, now fails with an ExpressionEvaluationException that says what the body is, instead of a NullPointerException; text that is not a valid HL7 message fails with an ExpressionEvaluationException that has the parse error as its cause.

camel-javascript, camel-python3 - bytes and functions in the result of a script

A Python bytes, bytearray or memoryview returned by a python3 script, and an ArrayBuffer returned by a js script, now give a byte[] instead of a List of numbers. A script that returns a function now fails with an ExpressionEvaluationException; before, it gave a value that failed when it was used later.

camel-quickjs - a Set, a Map or a typed array in the result of a script

A JavaScript Set returned by a quickjs script now gives a List, a JavaScript Map a Map with its keys as text, and a typed array or an ArrayBuffer a List of numbers, also when nested. Before, a Set and a Map gave an empty Map, and a typed array a Map of index to value.

camel-support - the error of a failed API component call names its arguments only

When the call of an API component fails, such as one of fhir, box, google-drive or twilio, the error message now names the arguments of the call, as in Error invoking resourceById with [password, resourceClass, serverUrl, stringId, username]: …​, and no longer shows their values. The arguments hold the options of the endpoint, such as a password or a token, and the message data. The debug log of the call masks the values of the sensitive options.

camel-solr - the operation of the header or the endpoint wins over the type of the body

The solr producer now runs the operation of the CamelSolrOperation header, or else of the operation option, whatever the body is. Before, the type of the body came first: a Map, a file or a collection that is not all Strings ran an insert, and a collection of Strings ran a delete by id, even when the header or the option named another operation. The body decides only when neither is set, and a body that is a request of SolrJ, such as a SolrQuery or an UpdateRequest, still runs its own operation. The CamelSolrQueryString header makes a search only when no operation is set, and a CamelSolrOperation header with a value that is not an operation now fails.

camel-vertx-http - the httpMethod option is not overridden by the CamelHttpMethod header

The httpMethod option of the endpoint now wins over the CamelHttpMethod header, as its description says and as in camel-http. Before, the header overrode the option, so a route that received a request through an HTTP consumer, which sets the header to the method of that request, sent it on with that method instead of the method of the endpoint. To let the header choose the method, do not set httpMethod on the endpoint.

camel-as2 - the response to a failed route does not carry the message of the exception

The server/listen and receipt/receive consumers have the new muteException option, true by default: when the route fails, the HTTP 500 that the partner gets says only that the message (or the receipt) could not be processed. Before, its body was the message of the exception of the route. Set muteException=false to send the message of the exception as before.

camel-as2 - quoted-printable parts keep their line breaks

Commons Codec 1.23.0 changed how a quoted-printable part is decoded. The line breaks (CRLF) that a partner writes into the part are now kept in the decoded payload, as RFC 2045 says. Before, every CR and LF was dropped. A soft line break written as =CR without the LF is now rejected with a DecoderException. Messages that Camel encodes are not affected.

camel-file-watch - CamelFileLastModified is the last modified time of the file

The CamelFileLastModified header is now the last modified time of the file, as its documentation says. Before, it was the time of the event. The header is not set when the file no longer exists, for example for a DELETE event. The time of the event is in the CamelMessageTimestamp header.

camel-whatsapp - the component is no longer marked producer only

The whatsapp component receives events in webhook mode (webhook:whatsapp:…​), so its metadata no longer marks it as producer only, and the catalog now describes the body that the webhook route gets. Tools that read the catalog, such as the YAML DSL validator and the Endpoint DSL, now accept a whatsapp endpoint as a consumer, and the Endpoint DSL builder of whatsapp gets the consumer options bridgeErrorHandler, exceptionHandler and exchangePattern. A whatsapp endpoint used directly in from still fails at startup: wrap it with the webhook component, as before.

camel-cometd - the JSONP transport is disabled by default

The CometD JSONP transport (callback-polling) is now disabled by default. Only long-polling (and websocket, when the CometD WebSocket transport is on the classpath) is enabled by default. Applications that require JSONP clients can configure allowedTransports on the endpoint to include callback-polling (for example, allowedTransports=websocket,long-polling,callback-polling).

camel-avro-rpc - the http consumer listens on the host of the endpoint

The avro:http consumer now uses the host of the endpoint URI (avro:http:[host]:[port]) for its HTTP server, as the avro:netty consumer does.

Use 0.0.0.0 as the host to accept connections on all network interfaces.

AvroRpcHttpServerFactory has a new method create(SpecificResponder, String, int) that receives the host. A custom factory should implement it.

camel-core - the simple resume cache follows the ResumeCache.forEach contract

The action passed to ResumeCache.forEach(BiFunction<K, V, Boolean>) returns true to invalidate (remove) the entry and false to keep it, as documented on ResumeAction.evalEntry and as the Caffeine resume cache has always done. The cache created by TransientResumeStrategy.createSimpleCache() previously did it the other way round: it removed the entries for which the action returned false. It now removes the entries for which the action returns true.

  • A custom ResumeAction, or any other action lambda passed to ResumeCache.forEach, that was written for the old behaviour of the simple cache must swap its return values: false keeps the entry, true removes it.

  • A custom ResumeCache implementation must remove an entry when the action returns true, and must visit every entry (do not stop iterating when the action returns false).

Impact on KafkaResumeAdapter: KafkaResumeAdapter.resume() now returns false (keep the cache entry), so that the topic-partition offset remains cached after the first seek and is re-applied on subsequent resumes. Additionally, KafkaResumeAdapter now guards against seeking partitions that are no longer in the current consumer assignment, avoiding IllegalStateException on revoked partitions.

camel-plc4x - PLC4X 1.0.0 and the triggered consumer

camel-plc4x now uses PLC4X 1.0.0, which requires Java 21, as Camel 4.24 does.

PLC4X 1.0.0 changes the connection strings of its drivers. Several parameters are renamed, for example request-timeout to request-timeout-ms, and the S7 driver reads the rack and slot as cotp.remote-rack and cotp.remote-slot instead of remote-rack and remote-slot. A parameter that a driver does not know, such as an old name, is logged as a warning and does not apply. The OPC UA driver now uses the security policy Basic256Sha256 by default instead of NONE, insecure-certificate-verification=true becomes tls.verify=false, and a user name and password are refused over a channel that is neither signed nor encrypted unless allow-insecure-credentials is set. See the incompatible changes in the PLC4X 1.0.0 release notes for the full list.

PLC4X 1.0.0 writes the array selection of a tag address before the data type, and as a range instead of a count. An address written as holding-register:1:INT[4] or %DB42:28.0:BYTE[4] for four elements is now written holding-register:1[0..3]:INT or %DB42:28.0[0..3]:BYTE; [4] before the data type selects the single element at index 4. Addresses in the old form no longer parse. This applies to the tag.* options, to trigger and to the addresses in the body of the producer.

The S7 driver of PLC4X 1.0.0 no longer includes the s7-light driver, so plc4x:s7-light:// endpoints no longer find a driver. Use plc4x:s7:// instead.

A consumer with trigger now reads the trigger tag and the tags in one request on the connection of the endpoint, every period milliseconds from the start of the route. Before, it sampled the trigger first after about one second, and read the tags in a later, separate request on another connection. A failed read is now reported to the exception handler of the consumer (or to the error handler of the route with bridgeErrorHandler=true), and polling continues; before, the consumer stopped triggering after the first failed read of the trigger. After a failed poll the remembered trigger value is unknown, so a trigger that is still true when the PLC returns emits once. Since the consumer now uses the connection of the endpoint, autoReconnect governs its reconnects: without it, a lost connection stops the endpoint and the polling until the route is restarted. A connection that cannot be set up when the route starts fails the start of the route, unless autoReconnect=true, in which case the polls retry it. A route that starts with the CamelContext then fails the start of the CamelContext too, unless the route is supervised by the supervising route controller or has autoStartup=false. As before, period must be positive when trigger is set. A trigger that reads as a value that is not a boolean, such as a number or an array, fails the poll. See the PLC4X component for how triggered reading works.

With autoReconnect=true, a lost connection is now closed and replaced with a new one from the driver instead of being reconnected, because the PLC4X 1.0.0 S7 and EtherNet/IP drivers cannot connect a connection again once it is lost. This applies to the producer and to the consumers alike.

The component now removes its own options (trigger, period, tag.*, autoReconnect and the generic consumer options such as exceptionHandler) from the connection string it hands to the driver. Before, the driver received them as connection string parameters too.

The constant Constants.PLC_NAME, which was only used with the PLC4X scraper, was removed.

camel-plc4x - the producer checks the tags it writes

The value of each tag in the body of the producer must now be a Map of exactly one address to the value to write. A tag with several addresses now fails the exchange with a PlcInvalidTagException, and nothing is written; before, only one of its addresses was written and the others were ignored. Use one tag for each address. A tag whose value is not a Map, or an empty Map, now also fails with a PlcInvalidTagException instead of a ClassCastException or a NoSuchElementException.

camel-stripe - endpoint request options isolate credentials across endpoints

The camel-stripe producer now applies apiKey and apiBase using endpoint-scoped RequestOptions on each Stripe API request, rather than mutating JVM-global Stripe SDK static configuration at startup. Multiple Stripe endpoints configured in the same application now operate with complete configuration isolation.

Additionally, configuring an apiKey on either the endpoint or the component is now validated at startup, throwing an IllegalArgumentException if missing rather than falling back to unauthenticated or globally-mutated SDK state.

Applications whose custom code (such as beans calling Customer.retrieve(…​) directly) previously relied on camel-stripe setting Stripe.apiKey globally in the JVM at startup must now configure Stripe.apiKey directly or pass RequestOptions explicitly.

camel-lucene - inserted documents are indexed, one document per exchange

The lucene:…​:insert endpoint now indexes the fields it writes, so a lucene:…​:query finds the inserted messages and the files of srcDir. Before, the fields were stored but not indexed, and a query never found them.

Breaking change: an index directory written by an earlier Camel version cannot be appended to. Lucene does not let a field change from not indexed to indexed, so the insert fails with IllegalArgumentException: cannot change field "exchangeId" from index options=NONE to inconsistent index options=DOCS_AND_FREQS_AND_POSITIONS (or the same for another field). Delete the index directory (indexDir) and insert the messages again. A query on such an index does not fail, but it finds none of its documents, as before.

Each exchange is now indexed as one document, with the body in contents, the exchange id in exchangeId and each header in a field of the header name. Each file of srcDir is one document with the contents and path fields. Before, each of these fields was a document of its own. The files of srcDir are indexed each time the insert endpoint is created, such as when the application starts; the document of a file now replaces the one with the same path instead of adding a copy. A query on a header, such as author:carlin, now gives a hit whose data is the body of the exchange, and with CamelLuceneReturnLuceneDocs the document of a hit carries all the fields of its exchange or file.

camel-wordpress - requests and responses are no longer logged

The WordPress client no longer logs every HTTP request and response it exchanges with the WordPress server at INFO level (the org.apache.cxf.services loggers). The component no longer depends on cxf-rt-features-logging, and the camel-wordpress JAR no longer contains a log4j.properties file.

camel-jbang - camel plugin add selects the plugin release built for the CLI

camel plugin add <name> no longer registers a community plugin (such as forage or kit) with the Maven LATEST version when no version is given. It selects the newest release of the plugin built for the version of the CLI, from the camel.jbang.plugin.camel-versions property in the POM of the plugin, or else from the version of its camel-jbang-core dependency. The exact version is stored in ~/.camel-jbang-plugins.json, so the plugin no longer moves to newer releases on its own: run camel plugin add <name> again to select the newest matching release. When the CLI is between the Camel versions of the releases, it selects the newest release for the nearest older Camel version, with a warning. When the CLI is newer than all the releases, or the releases cannot be read, it warns and uses LATEST as before.

--version and --gav are used as given, with a warning when the release is built for another Camel version. Plugins registered before are not changed. See Plugin versions.

camel-spring - a CamelEvent only goes to the consumers of the same name

A spring-event producer publishes a body that is not an ApplicationEvent as a CamelEvent. Before, every spring-event consumer received it, whatever its name. Now only the consumers of the endpoint with the same name receive it: a message sent to spring-event:foo goes to from("spring-event:foo"), and no longer to from("spring-event:bar").

Other application events of the ApplicationContext, such as ContextRefreshedEvent or an ApplicationEvent body published by a producer, still go to every consumer.

camel-paho-mqtt5 - the producer sends a copy of the properties header

The producer used to hand the MqttProperties object of the CamelPahoMqtt5MsgProperties header to the client as it was. The client writes the outgoing topic alias into that object, and the parts of a split, multicast, recipient list, loop or wire tap of a received message share it, so once the broker’s Topic Alias Maximum was used up, later messages were delivered on the wrong topic. The producer now publishes a new MqttProperties with the payload format indicator, message expiry interval, content type, response topic, correlation data and user properties of the header object, which a publish no longer modifies. A topic alias or subscription identifiers set in the header object are not sent: the client assigns topic aliases for its connection, and subscription identifiers are sent by the server only.

camel-wal - the unconfirmed offsets of the previous run are replayed after a restart

WriteAheadResumeStrategy now does what it is documented to do after a restart: when the consumer starts and loads the resume cache (loadCache()), the strategy replays the offsets of the previous run that the wrapped resume strategy did not confirm, by calling updateLastOffset on the wrapped strategy. Before, start() truncated the log file before loadCache() read it, so nothing was replayed. Only the last offset of each key in the log counts: it is replayed when its entry is in state NEW or FAILED, and an earlier offset of the same key is not replayed. These entries stay in the log until the wrapped strategy confirms them through its callback; a wrapped strategy that never calls back gets the last offset of each such key again at every start. The Kafka consumer does not call loadCache(), so with it the entries are kept in the log but not replayed. A replay that fails, or an entry that the resume adapter cannot deserialize, is logged with a WARN and does not stop the start of the route. If the last record of the log is incomplete (the JVM stopped while writing it) or invalid, the entries before it are replayed and a WARN is logged; the route still starts. A file that is not a log of this format is replaced by a new log, as before, with a WARN.

The log keeps 524,288 entries; after that it rolls over and holds the newest entries before the older ones, and the file does not say which are newer. So a log that holds that many entries is not replayed, as before: a WARN names the number of unconfirmed entries, which are discarded with the log. Replaying the wrong offset of a key would make the consumer resume from an older offset. A log that rolled over and was cut short because the system stopped while the log rewrote itself after the roll-over holds fewer entries and is replayed: the last entry of a key in it can then be an older offset, so the consumer can process again the messages after that offset.

camel-core - a hook before each step of a route

A new SPI org.apache.camel.spi.StepHook, set with ExtendedCamelContext.setStepHook, can fail or delay each step of a route. ErrorHandlerRedeliveryCustomizer has a new default method useStepHook(String), so existing implementations need no change. A custom error handler that wants a failure from the hook handled like a failure of the step’s processor (with onException and redelivery) can override it.

camel-quartz - a route that keeps its trigger fires again when it is added again or after a restart

With deleteJob=false (as with pauseJob=true), a route pauses its trigger when it stops, and only the same endpoint resumed it. A route that was removed and added again (a reload, a route template removed and added), and a route that started after a restart with a persistent job store that is not clustered, therefore never fired again. The route now marks the trigger it pauses, with the key CamelQuartzTriggerPausedByCamel in the JobDataMap of its job, and a route resumes a paused trigger with this mark when it starts. A trigger that was paused outside Camel (with the Quartz API or JMX) has no mark and stays paused; this now also holds when the same route is stopped and started again, which resumed such a trigger before. A trigger that Camel paused before this version has no mark either: resume it once with the Quartz API. A clustered job store is not changed, as Camel does not pause triggers there.

camel-langchain4j-ingest - a media document whose type cannot be told is filtered

With modality=media and no contentType set, a document whose media type cannot be told from the extension of its id, such as a README or an editor backup beside the media, is now answered with a filtered result, as an id rejected by includeId or excludeId is. Before, it failed the exchange with an IllegalArgumentException, so such a file in a polled folder failed again on every poll. The exchange now completes, so a file consumer without noop moves such a file into .camel instead of leaving it in place. A hint is logged at INFO. A document whose type is known but is not a medium, or is one the embedding model does not support, still fails the exchange.

camel-langchain4j-web-search - webSearchRequest and additionalParams are not autowired, CONTENT falls back to the snippet

The webSearchRequest and additionalParams options are no longer autowired. Before, the only WebSearchRequest bean of the registry was used by every langchain4j-web-search endpoint, so the search terms in the message body were ignored, and the only Map bean of the registry was sent as the additional parameters of every search. Refer to such a bean on the endpoints that should use it, for example webSearchRequest=#myRequest.

With resultType=CONTENT (the default), a result without content now gives its snippet instead of null. Google Custom Search never gives content, and Tavily only with includeRawContent(true), so with these engines the body was null for a found result (or a list of nulls with maxResults above 1).

camel-jbang - camel export boots the integration in the same JVM, no longer through camel run

To find the dependencies, camel export boots the integration silently, as before, but no longer goes through camel run for it. Some settings of camel run therefore no longer apply to an export:

  • camel.jbang.background and camel.jbang.jvmDebug in application.properties are ignored by the export. Before, they made the export start a separate camel run process (in the background, or with a debugger), which kept running after the export, or which the export waited for.

  • --camel-version is accepted by camel export from camel-launcher too: it sets the Camel version of the exported project. Before, it failed with a usage error.

  • camel export clipboard.yaml no longer reads the route from the clipboard when there is no such file: it fails with exit code 66 (file not found), as for any other missing file. Save the content of the clipboard to a file and export that file.

  • camel export --profile=<profile> boots the integration with that profile to find the dependencies. Before, it always booted with the dev profile, so a component that only application-<profile>.properties uses was missing from the exported project: such an export can now have more (or other) dependencies. The security policy of the profile (such as prod, which fails on an insecure option) does not fail the export, nor does camel.security.policy=fail: the export only warns. Without --profile the dependencies are the same as before.

camel-launcher - the distribution has the JARs in lib/

The zip and tar of the Camel CLI (camel-launcher-<version>-bin, which Homebrew, SDKMAN, WinGet, Scoop, Chocolatey, the website installer and camel self-update install) no longer have the fat JAR in bin/. They have the JARs in lib/, with lib/camel-launcher-<version>.jar, whose manifest names the other JARs of lib/ as its class path. The scripts in bin/ run java -jar lib/camel-launcher-<version>.jar, so each command starts about 100 ms faster, and camel --help about 250 ms. A script or an image that runs the fat JAR from the extracted distribution runs lib/camel-launcher-<version>.jar instead, or the fat JAR from Maven Central, which has not changed.

The JARs of lib/ are in the order of the dependencies of camel-launcher, which is the class path its tests run with; the fat JAR had them in alphabetical order. camel generate no longer has swagger-models 2.2.52 next to swagger-models-jakarta 2.2.36, two JARs with the same classes.

bin/camel.sh and bin/camel.bat turn on class data sharing: the JVM keeps the classes that a command loads in an archive, one per command and Camel version, in ${XDG_CACHE_HOME:-~/.cache}/camel-cli/cds (%LOCALAPPDATA%\camel-cli\cds on Windows). The first run of a command creates its archive and takes longer (up to about a second), and the JVM creates it again when the JDK changes. CAMEL_CDS=false turns it off, and so do CDS or AOT options in JAVA_OPTS (such as -Xshare:off). The archives of a Camel version are not needed after an upgrade, and the directory can be removed at any time.

camel-once - the consumer fires on a Camel thread pool

The once consumer now fires its route on a single thread scheduled executor that it creates through the ExecutorServiceManager when the route starts and shuts down when the route stops, so the thread is named Camel (<context>) thread #N - <name> instead of <name>. Before, each once consumer created a non-daemon java.util.Timer thread when it was created, and that thread stayed alive after its route was stopped and after the CamelContext was stopped (after its route was removed, until the consumer was garbage collected). A once route that is stopped and started again now fires again, and so does a once route after the CamelContext is stopped and started again; before, the start failed with IllegalStateException: Task already scheduled or cancelled.

The protected method OnceConsumer.scheduleTask(TimerTask, Timer) is replaced by scheduleTask().

camel-bonita - startCase fails with InvalidPayloadException for a body that is not a Map

The startCase operation now fails with org.apache.camel.InvalidPayloadException when the body is missing or cannot be converted to a Map, such as JSON text. Before, it failed with IllegalArgumentException: The contract input is null, also when a body was sent. Unmarshal JSON text to a Map before the bonita endpoint.

The id of the started case is now set in the CamelBonitaCaseId header.

camel-camunda - a failed operation fails the exchange, and the JSON uses camelCase property names

The producer caught the error of the Camunda client, and gave a response with success set to false and the error message, while the exchange completed as if the operation had worked. A failed operation now fails the exchange with a CamelExchangeException that has the error message of Camunda; the response stays in the message body. Routes that checked success in the response can handle the exception instead, for example with onException(CamelExchangeException.class).handled(true).

The JSON of the producer (the request bodies and, with formatJSON=true, the responses) now uses the camelCase property names of the request and response objects, as Camunda itself and the JSON of the worker consumer do. Before, it used snake_case names. The snake_case names are no longer accepted: a request with them gets the default values, for example job key 0. Rename the properties in your JSON:

Previous name New name Used in

process_id

processId

ProcessRequest, ProcessResponse

process_version

processVersion

ProcessRequest, ProcessResponse

process_key

processKey

ProcessRequest, ProcessResponse

process_instance_key

processInstanceKey

ProcessRequest, ProcessResponse

correlation_key

correlationKey

MessageRequest, MessageResponse

time_to_live

timeToLive

MessageRequest

message_id

messageId

MessageRequest

message_key

messageKey

MessageResponse

job_key

jobKey

JobRequest

fail_message

failMessage

JobRequest

error_message

errorMessage

JobRequest and all responses

error_code

errorCode

JobRequest and all responses

file_content

fileContent

DeploymentRequest

bpmn_process_id

bpmnProcessId

ProcessDeploymentResponse

process_definition_key

processDefinitionKey

ProcessDeploymentResponse

resource_name

resourceName

ProcessDeploymentResponse

The job operations also read the job key from the key property of the JSON of a worker, so that JSON can be sent to completeJob, failJob, updateJobRetries and throwError as is.

camel-support - DefaultThreadPoolFactory: virtual thread pools validate the pool size

With virtual threads enabled, a thread pool now rejects an invalid pool size at startup, as a platform thread pool always did: threads(5, 2) (maxPoolSize below poolSize) and threads(-1, 2) (negative poolSize) fail with IllegalArgumentException, where they started before.

A scheduled pool with virtual threads now keeps poolSize threads (before: none), and removes a cancelled task from its queue, the same as a scheduled pool with platform threads. After shutdown, it handles a rejected task with the rejected policy of its thread pool profile (by default it discards the task) instead of throwing RejectedExecutionException.

camel-jpa - a TransactionStrategy in the registry is used by default

The transactionStrategy option of the JPA component is now autowired. When no TransactionStrategy is configured, the JPA component and endpoint now use the single TransactionStrategy bean in the registry, if there is one, before creating the Spring based DefaultTransactionStrategy. Without such a bean, or with autowiredEnabled=false, nothing changes. If there is more than one, none of them is used and a WARN is logged.

The bean applies to every JPA component and endpoint without a configured strategy, including ones using another EntityManagerFactory. If the bean is bound to one EntityManagerFactory (for example a DefaultTransactionStrategy created for it), set the transactionStrategy option explicitly.

The return type of the protected method JpaEndpoint.createTransactionStrategy() is now TransactionStrategy instead of DefaultTransactionStrategy. A subclass that overrides it must be recompiled: compiled against the old return type, its method is no longer called.

camel-jbang - the export to Quarkus keeps camel-quarkus-microprofile-health

camel export --runtime=quarkus, and camel run --runtime=quarkus, which exports the same way, now write org.apache.camel.quarkus:camel-quarkus-microprofile-health to the pom.xml when the project asks for it: --dep=camel:microprofile-health, --dep=org.apache.camel:camel-microprofile-health, --dep=org.apache.camel.quarkus:camel-quarkus-microprofile-health, or the same in camel.jbang.dependencies. Before, the export dropped it in every spelling, as if the Quarkus POM template contained it. Since Camel 4.14 the template contains neither the extension nor camel-quarkus-observability-services, which brings it along.

A project that lists the dependency but does not want the health extension in the Quarkus export must remove it. Without the dependency nothing changes: the health extension then only comes with --observe (which camel kubernetes export always sets), as part of camel-quarkus-observability-services.