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.22 to 4.23
Components removal
camel-archetype-spring
The Maven archetype camel-archetype-spring was deprecated in 4.17. Use spring boot instead.
camel-catalog-lucene
The maven plugin was deprecated in 4.12. camel-catalog-suggest is replacing it.
camel-digitalocean
The component camel-digitalocean was deprecated in 4.21. The java library used has been unmaintained for several years and there is no replacement.
camel-headersmap
The component camel-headersmap was deprecated in 4.21. The default CaseInsensitiveMap in camel-core uses a custom O(1) hash table with zero-allocation lookups and header key deduplication, making the external cedarsoftware java-util dependency unnecessary. Simply remove the camel-headersmap dependency from your project — the core implementation now provides equivalent or better performance.
camel-iec60870
camel-iec60870 was deprecated in 4.21. The library used to implement it NeoScada is no more maintained since 2021. There are no alternatives in Java with compatible license.
camel-irc
The component camel-irc was deprecated in 4.21. The library used had no stable release since 2007. There is no Java library very active for this protocol.
camel-ironmq
The component camel-ironmq was deprecated in 4.21. The official library used has been unmaintained since 2017 All the other client libraries (in other languages) are unmaintained since the same amount of time. The whole iron-io GitHub organization has almost no activity.
camel-json-patch
The camel-json-patch was deprecated in 4.19. The library it uses is not actively maintained and this module does not work with Jackson 3.
camel-langchain4j-tools
The camel-langchain4j-tools component was deprecated in 4.19. Use camel-ai-tool to define tools and camel-langchain4j-agent for tool-calling with LangChain4j models.
Migrate your tool definition routes from langchain4j-tools: to ai-tool::
// Before
from("langchain4j-tools:weather?tags=weather&description=Get weather¶meter.city=string")
.setBody(constant("{\"city\": \"Paris\", \"temp\": \"22C\"}"));
// After
from("ai-tool:weather?tags=weather&description=Get weather¶meter.city=string")
.setBody(constant("{\"city\": \"Paris\", \"temp\": \"22C\"}")); Use langchain4j-agent with matching tags to invoke tools:
from("direct:chat")
.to("langchain4j-agent:assistant?agent=#myAgent&tags=weather"); Add the camel-ai-tool dependency to your project:
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-ai-tool</artifactId>
</dependency> camel-leveldb
camel-leveldb was deprecated in 4.18. leveldb library is no more maintained and it exists several alternatives for file-based database nowadays.
camel-reactive-executor-tomcat
The camel-reactive-executor-tomcat component has been deprecated in 4.22. It is now removed.
Its cross-thread ThreadLocal cleanup relied on reflective access to the private Thread.threadLocals field, which is denied by the JDK module system since JDK 17 and is incompatible with virtual threads. Without that cleanup, this executor is functionally identical to the built-in DefaultReactiveExecutor.
To migrate, remove the camel-reactive-executor-tomcat dependency from your project. Camel will automatically use the default reactive executor.
camel-splunk
The camel-splunk component was deprecated in 4.19. The Splunk Java SDK it depends on is no longer actively maintained.
Users who only need to send events to Splunk can migrate to camel-splunk-hec, which uses the Splunk HTTP Event Collector (HEC) over standard HTTPS with no dependency on the Splunk Java SDK.
However, camel-splunk-hec is a producer-only component. The following camel-splunk capabilities have no equivalent in camel-splunk-hec:
-
Consumer (search): normal searches, real-time searches, and saved-search execution are not supported.
-
TCP streaming: the
tcpproducer publish type (raw socket streaming to a Splunk TCP input) is not available. -
SUBMIT and STREAM publish types: only HEC-based ingestion is supported.
If your routes only produce events to Splunk (using the submit or stream publish types), switching to camel-splunk-hec is straightforward — configure the HEC token, index, sourceType, and source on the endpoint. If your routes consume (search) data from Splunk, there is currently no direct replacement within Apache Camel, and you will need to use the Splunk REST API directly or keep using camel-splunk until it is removed.
camel-splunk-hec is NOT deprecated and remains actively maintained. |
camel-a2a - webhook URL address classification
Push notification webhook URLs are now classified by the address the host resolves to, using the same rules whether the host is written as an IP literal or as a name. Previously a few ranges were recognised only in literal form, and host names were partly classified by how they were spelled.
Webhook URLs are now rejected when the host resolves into any of the following, in addition to the loopback, wildcard, link-local and site-local ranges that were already rejected:
-
IPv6 unique local addresses,
fc00::/7 -
IPv4-compatible IPv6 addresses,
::a.b.c.d, when the embedded IPv4 address is itself non-global -
NAT64 addresses under the well-known prefix
64:ff9b::/96, when the embedded IPv4 address is itself non-global -
6to4 addresses under
2002::/16, when the embedded IPv4 address is itself non-global -
The shared address space used for carrier-grade NAT,
100.64.0.0/10
NAT64 and 6to4 addresses carrying a globally routable IPv4 address remain allowed, so an IPv6-only deployment can still reach public webhook endpoints through a translation prefix.
In the other direction, host names are no longer rejected on the basis of their spelling. Names beginning with fc or fd, such as fcm.googleapis.com, were previously refused because those are the leading hex digits of the IPv6 unique local prefixes; they are now resolved and classified like any other name.
Set allowLocalWebhookUrls=true to permit loopback targets during local development. That option is unchanged and still does not permit any of the ranges above.
camel-ai-observability (GenAI observability)
LangChain4j and OpenAI producers now emit GenAI observability data (OpenTelemetry span attributes and Micrometer metrics) when camel-opentelemetry2 and/or camel-micrometer is on the classpath. Disable globally with camel.ai.observability.enabled=false (default is enabled).
OpenAI streaming chat sets stream_options.include_usage=true only when GenAI observability is enabled, adding a final chunk with token usage for span/metric recording.
LangChain4j components also expose request model names on new exchange headers (CamelLangChain4j*RequestModel). The response model header (CamelLangChain4j*ResponseModel) is set when the underlying client exposes it (for example langchain4j-chat); the agent and embeddings producers omit it when unavailable. See AI Observability for metric names and span attributes.
camel-archetypes
The Camel Maven archetypes now generate a README.md instead of the previous ReadMe.txt, with the content rewritten in Markdown and the documentation links updated. Each generated project also gets an AGENTS.md file with guidance for AI coding assistants, pointing at the Apache Camel LLM index (/llms.txt), the Camel CLI and the Camel MCP server.
The camel-archetype-api-component archetype also generates its readme again: the file was declared in the wrong file set and was therefore silently skipped.
camel-docling
A String message body is no longer interpreted as a location by default. Previously the producer inspected the body and, when it started with http:// or https://, handed it to Docling as a remote URL to fetch; when it started with / or contained \, it read it from the local filesystem; otherwise it converted it as document content.
The two location readings must now be enabled explicitly:
-
allowUrlSource(defaultfalse) - interpret a body starting withhttp://orhttps://as a URL. -
allowFilePathSource(defaultfalse) - interpret a body starting with/, or containing\, as a local file path. This also covers the single directory-or-fileStringbody accepted by the batch operations.
A route that passes the document itself in the body is unaffected. A route that passes a URL or a path in the body must set the matching option, otherwise the exchange fails with an IllegalArgumentException naming the option to enable.
The CamelDoclingInputFilePath header is unchanged and still accepts a path without any opt-in, as are File, byte[] and InputStream bodies and the explicit path collections (List<String>, String[], List<File>, File[]) used by the batch operations.
A new inputBaseDirectory option is also available. When set, every local input path - from the header, from a file path body, and from the batch operations - must resolve inside that directory once normalized. It is unset by default, which keeps the previous behaviour of accepting any path.
Additionally, a local input path that does not exist is now reported as a File not found IOException before Docling is invoked. Previously the size check silently skipped a path that resolved to nothing and the failure surfaced later, from the Docling process or API call. === camel-azure-eventgrid
The CamelAzureEventGridDataVersion header (EventGridConstants.DATA_VERSION) has been removed. The component publishes events in the CloudEvents schema, which has no dataVersion attribute (that field belongs to the legacy Event Grid event schema), so the header was read but never applied to the published event. Remove any use of that header; there is no CloudEvents equivalent. === camel-core - property placeholders in pollEnrich
Camel 4.22 stopped resolving property placeholders ({{…}}) on the per-message evaluated recipient for toD and enrich, and said that aligning pollEnrich was deferred to a follow-up. This is that follow-up: a {{…}} token that appears only in the value produced at runtime by the pollEnrich expression is now treated as a literal part of the endpoint URI instead of being expanded.
Like toD and enrich, pollEnrich resolves its static endpoint URI at build time, so a placeholder belongs there:
.pollEnrich("file:{{inbox}}", 5000) recipientList, routingSlip and dynamicRouter are unchanged. Their recipient is supplied entirely at runtime and may legitimately carry a placeholder that comes from configuration, so they continue to resolve {{…}} in the computed recipient.
camel-hazelcast
ReplicatedHazelcastAggregationRepository now applies the same default JavaSerializationFilterConfig that the other repositories and the component endpoints have applied since 4.14.8/4.18.3/4.21.0, when it bootstraps its own HazelcastInstance (that is, when no hazelcastInstance is supplied). It overrides doStart() without calling super.doStart() and was therefore left out of that change.
The default whitelists the class name prefixes java., javax., org.apache.camel. and blacklists java.net., and a user-supplied JavaSerializationFilterConfig is still respected and never overwritten.
Applications that aggregate classes outside the default whitelist through the replicated repository without supplying their own hazelcastInstance must now provide a Config with a JavaSerializationFilterConfig covering their class names.
The same default is now also applied to the ClientConfig that Camel builds for hazelcastMode=client endpoints, when neither a referenced ClientConfig nor hazelcastConfigUri is supplied. Client mode previously behaved differently from node mode for an otherwise identical endpoint configuration.
camel-jbang (TUI)
camel tui --record is now rejected when combined with --web. The recording configuration applies to the whole process, so a browser session served by --web would be recorded into the same .cast file as the local session. Previously the combination was accepted, but recording never produced any output, so run the two modes in separate processes instead.
camel-mail
MimeMultipartDataFormat now uses MailHeaderFilterStrategy instead of a plain DefaultHeaderFilterStrategy when headersInline unmarshal copies the remaining MIME headers onto the Camel message. That strategy filters the mail.smtp. and mail.smtps. prefixes on the inbound path in addition to Camel*/camel*, so the data format now filters the same namespace the mail consumer has filtered since 4.14.9/4.18.4/4.22.0.
Routes that relied on mail.smtp. or mail.smtps. headers arriving on the exchange from an unmarshalled MIME message must set those values explicitly on the route instead. Ordinary application headers are unaffected.
camel-netty - object codecs apply a deserialization filter by default
The ObjectDecoder and DatagramPacketObjectDecoder codecs (used when a route configures Netty object serialization through the encoders / decoders options) now always install a JEP-290 java.io.ObjectInputFilter while decoding, resolved through DeserializationFilterHelper. Previously a decoder built without an explicit filter pattern applied no filter at all and only logged a warning.
When no explicit pattern is passed, the JVM-wide jdk.serialFilter is honoured if set, otherwise the shared Camel default allow-list is applied (it permits standard Java and Apache Camel types, denies java.net.**, and enforces JEP-290 graph-shape limits). Routes that deserialize classes outside that allow-list must pass an explicit filter pattern to the two-argument ObjectDecoder(ClassResolver, String) / DatagramPacketObjectDecoder(ClassResolver, String) constructor (or configure jdk.serialFilter) to permit them.
camel-oauth
The OAuth processors now stop the route on the paths where they do not authenticate the caller, so that no subsequent step of the route runs for such a request. Previously they set a response code and returned, which left the rest of the route to execute and overwrite the response the processor had just prepared.
What changed:
-
OAuthBearerTokenProcessor— a request with noAuthorizationheader, or with one that does not parse asBearer <token>, is now answered with401and aWWW-Authenticate: Bearerchallenge (RFC 6750) instead of400, and the route is stopped. A present-but-invalid token continues to fail by propagating the exception fromOAuth.authenticate(), as before. -
OAuthCodeFlowProcessor— when the caller has no authenticated session and is redirected to the identity provider, the route is now stopped; the302is the whole response. -
OAuthCodeFlowCallback— a callback request without thecodeparameter still answers400, and now also stops the route.
Routes that relied on steps after these processors running for unauthenticated requests must be restructured. The authenticated paths are unchanged: a successfully authenticated request continues through the rest of the route exactly as before, and OAuthLogoutProcessor is unchanged.
camel-core - XmlConverter SAX parser factory
XmlConverter.createSAXParserFactory() now also disables external parameter entities and external DTD loading:
It previously set only FEATURE_SECURE_PROCESSING and external-general-entities=false, while createDocumentBuilderFactory() in the same class already blocked external resource resolution more thoroughly. Both factories are reachable from a converted message body — toSAXSource is a registered converter, and the SAXSource route is tried first for bodies reaching camel-xslt — so the two should not disagree.
Documents carrying an internal DTD subset still parse: disallow-doctype-decl is deliberately not set here, because that would reject input that parses today. Routes that genuinely need to resolve an external DTD or parameter entity through this converter must supply their own SAXParserFactory.
camel-netty-http
The security-constraint lookup now strips the endpoint context-path from the request target case-insensitively, matching how consumer dispatch already matches it (RestConsumerContextPathMatcher compares with equalsIgnoreCase and a lower-cased prefix).
Previously the strip was guarded by a case-sensitive startsWith, so a request whose context-path differed only by case was evaluated against the unstripped target. With matchOnUriPrefix=true and a securityConstraint whose inclusions are specific sub-paths rather than a catch-all, such a request could match no inclusion — and an unmatched target counts as unrestricted — while still being dispatched to the route.
Requests that differ from the configured context-path only by case are therefore now subject to the same constraint as the exact-case form. Deployments that relied on the previous behaviour to reach a route without a challenge will now receive 401.
camel-spring-redis - the default serializer applies a deserialization filter
The default serializer, JdkSerializationRedisSerializer, now installs a JEP-290 java.io.ObjectInputFilter while reading Redis payloads, resolved through DeserializationFilterHelper. Previously no filter was applied at all. This affects both the consumer, which deserializes the payload of every message published to the subscribed channels, and the producer read commands, which deserialize the values stored in Redis.
When no explicit pattern is configured, the JVM-wide jdk.serialFilter is honoured if set, otherwise the shared Camel default allow-list is applied (it permits standard Java and Apache Camel types, denies java.net.**, and enforces JEP-290 graph-shape limits). Routes that exchange classes outside that allow-list must widen it through the new deserializationFilter endpoint option, for example:
from("spring-redis://localhost:6379?command=SUBSCRIBE&channels=myChannel"
+ "&deserializationFilter=com.example.model.**;java.**;!*") Setting the serializer option to a custom RedisSerializer bypasses the filter entirely, since Camel then no longer controls how the payload is read. === camel-langchain4j
The legacy sse transportType has been removed. It follows the support removal in langchain4j-core 1.19.0.
camel-ftp, camel-sftp, camel-ftps, camel-mina-sftp, camel-azure-files, camel-smb
The remote-file consumers now ensure the path resolved for a polled file stays within the directory being polled. The file name that path is built from is reported by the remote server in its directory listing and is not guaranteed to be a single path segment, so a listing entry containing ../ sequences could previously resolve to a path outside the configured directory and be used as the operand for retrieving, deleting or renaming a file.
The containment check honours the existing jailStartingDirectory option (default true), consistent with the file producer and with the localWorkDirectory download path; set jailStartingDirectory=false to disable it. A file that resolves outside the configured directory is now skipped, and a warning is logged.
Ordinary listings are unaffected, as a listed name is normally a single path segment, and a ../ that still resolves back inside the polled directory remains accepted. Two configurations can newly see files skipped: a server that reports names navigating above the polled directory, and a fileName expression (used when useList=false) that navigates above it. Set jailStartingDirectory=false if such a path is intended. === camel-as2
The AS2 server no longer attaches the configured mdnUserName / mdnPassword / mdnAccessToken credentials to an asynchronous MDN unless the delivery address names a host the operator has authorised.
The delivery address comes from the Receipt-Delivery-Option header of the received AS2 message, so it is chosen by the sender. A new option lists the hosts an asynchronous MDN may be delivered to:
as2://server/listen?asyncMdnAllowedHosts=partner.example,partner2.example -
When
asyncMdnAllowedHostsis set, an asynchronous MDN whose delivery address names a host outside the list is refused, and the credentials are attached only for a host on the list. -
When it is not set, the MDN is still delivered to the sender-supplied address, as before, but no credentials are attached and a warning naming the option is logged.
Deployments that rely on authenticating to a partner’s asynchronous MDN endpoint must add that partner’s host to asyncMdnAllowedHosts.
Two further checks are applied to the delivery address regardless of the option: the scheme must be http, and an address with no explicit port now uses 80 rather than being passed to the socket as -1.
https is refused. AS2AsynchronousMDNManager delivers over a plain socket and has no TLS support, so an https address was never actually delivered over TLS — the request was written in cleartext to the TLS port and the peer reset the connection. Such an address is now refused outright rather than attempted, and TLS delivery of asynchronous MDNs remains unsupported.
camel-ibm-cos
The CACHE_CONTROL header constant’s value has been corrected from the misspelled CamelIBMCOSContentControl to CamelIBMCOSCacheControl, so the header name matches the Cache-Control metadata it carries. This is a breaking change for routes that reference the header by its literal string name: they must switch to CamelIBMCOSCacheControl, although the change is trivial to adapt. Routes using the IBMCOSConstants.CACHE_CONTROL constant are unaffected. === camel-knative
The Knative HTTP consumer no longer returns the stack trace of a failed exchange to the caller.
When a route consuming from knative:endpoint/… or knative:event/… failed, the response body was the exception’s full stack trace, sent as text/plain. A new muteException consumer option controls this, and it defaults to true — the same default the HTTP consumers carry (camel-http-common and therefore camel-servlet and camel-jetty, plus camel-http, camel-platform-http, and camel-netty-http and camel-undertow since 4.21.0).
The response status is unchanged: a failed exchange still returns 500 (or whatever CamelHttpResponseCode the route set), only the body is now empty.
A route that relies on the stack trace reaching the caller must opt back in explicitly:
knative:endpoint/myEndpoint?muteException=false org.apache.camel.component.knative.spi.KnativeTransportConfiguration gains a fourth constructor argument for the flag. The three-argument constructor is retained and mutes the exception, so existing code compiles unchanged and picks up the new default.
camel-paho-mqtt5
When automaticReconnect=true and the MQTT broker reconnects, the consumer now restarts the route if the post-reconnect subscribe() call fails. Previously a failed resubscription (for example, when the broker does not send a SUBACK and the Paho keepAlive timer triggers MqttException 32000) was only logged at ERROR level with no recovery action, leaving the route in Started state while silently consuming no messages (zombie state).
If the resubscribe fails and the consumer owns the MQTT client (the default), it automatically stops and restarts the route to force a clean reconnect. If the restart also fails (for example, the broker is still unavailable), the route is left in Stopped state. Routes using a user-provided client are not affected by this change. Configuring Camel’s SupervisingRouteController allows the framework to keep retrying with exponential backoff until the broker recovers:
camel.routeController.enabled = true
camel.routeController.backOffDelay = 2000
camel.routeController.backOffMaxDelay = 60000 camel-pqc
FileBasedKeyLifecycleManager stores private keys unencrypted, as Base64 PKCS#8 inside a JSON file, and used to create both the key directory and those files with whatever the process umask allowed — commonly rw-r—r-- and rwxr-xr-x under the usual 022, leaving private keys readable by every account on the host.
The key directory is now created as rwx------ and each <keyId>.private.json as rw-------, on file systems that support POSIX permissions; elsewhere the equivalent owner-only flags are applied. A private key file left behind by an earlier version is tightened the next time that key is stored, because the file is truncated rather than recreated and would otherwise keep its original permissions.
Deployments where another account legitimately reads these files — a sidecar or a backup agent running as a different user — need to run as the owner, or use a group-aware key store instead.