Every integration estate we review has the same quiet problem somewhere: a broker password in a application-prod.properties file that three people can read, a partner API key pasted into a route's URI during a 2 a.m. incident, and no record of when either was last changed. Nobody planned it. Credentials arrived one endpoint at a time, and configuration management was never anyone's ticket.
Camel has decent answers for this. They are not exotic, but the ordering rules and the rotation behavior surprise people, so here is the working picture.
Endpoint URIs are templates, not literals
The first rule: never put a secret — or a hostname, or a queue name — literally in a route. Use a property placeholder.
from("jms:queue:{{orders.queue}}")
.to("https://{{partner.host}}/v2/orders"
+ "?authenticationPreemptive=true");
Camel resolves {{...}} through its own PropertiesComponent, which is distinct from Spring's ${...} resolution even when you run on Spring Boot. Both work; mixing them carelessly is how you get a route that resolves in dev and fails at startup in prod. Our house rule is simple: {{ }} for anything a route URI consumes, ${ } for framework and bean wiring, and never two layers of indirection for the same value.
Two placeholder features earn their keep:
- Defaults:
{{partner.timeout:30000}}keeps a non-secret knob tunable without forcing every environment to declare it. - Optional properties: prefixing with
?({{?partner.proxyHost}}) lets a parameter vanish entirely when unset, rather than resolving to an empty string that the component then tries to dial.
And one anti-feature to avoid: do not build URIs by string-concatenating values you pulled from a header or an exchange body. If a destination is genuinely dynamic, that is what toD is for, and it needs a sanitization story of its own — an attacker-controlled path fragment in a toD URI is an SSRF with extra steps.
Where secrets should actually live
Property placeholders only move the problem: the value has to come from somewhere. In rough order of how much we like them:
- A secrets manager, read at startup and on rotation. Camel 3.16+ ships vault integrations for AWS Secrets Manager, Azure Key Vault, Google Secret Manager, and HashiCorp Vault, wired in as a property function. The route says
{{aws:partner-api/apiKey}}; the value never lands in a repo or an image. - Kubernetes Secrets mounted as files, with Camel reading them through a properties location. Less centralized than a vault, but auditable and adequate when the platform team already treats Secrets as first-class.
- Environment variables. Workable, and better than a file in the artifact, but they leak: process listings, crash dumps,
/proc, and the logs of any tool that helpfully prints its environment. - Properties files in the deployment artifact. Fine for non-secret configuration. Not fine for credentials, no matter how restricted the repo.
Whichever you pick, decide the resolution order once and write it down. Camel's properties component supports multiple locations with override semantics, and a fleet where dev overrides come from one place and prod from another — with a documented precedence — is a fleet where a wrong value is diagnosable. Undocumented layering produces the worst class of integration bug: correct code, wrong config, no error.
Rotation is the part people skip
A vault lookup at startup is an improvement in secret storage, not in secret lifecycle. If rotating the partner API key still means a rolling restart coordinated with the partner's change window, rotation will keep getting deferred.
Camel's vault integrations can subscribe to rotation events — an AWS EventBridge/SQS notification, an Azure Event Grid event, a periodic Vault refresh — and trigger a context reload, re-resolving properties and restarting the affected routes with the new value. That is genuinely useful. It is also a route restart, so plan for it like one:
- In-flight exchanges: a reload is a stop/start. Set a shutdown timeout that lets current exchanges finish, and make sure consumers are transactional or acknowledge late, so a message in flight at reload time is redelivered rather than lost.
- Blast radius: reloading the whole context because one partner's key rotated will bounce every route. Scope reload to the routes that use the secret where the component allows it, and accept a narrower blast radius over elegance.
- Dual-validity windows: rotation is only non-disruptive if the old credential stays valid while the new one propagates. That is the provider's property, not Camel's. Confirm it before promising zero-downtime rotation.
- Observability: emit a log line and a metric on every reload, with the secret's name and version but never its value. "Did the 03:14 blip line up with a key rotation?" should take one query, not a guess.
If the provider gives no overlap window, be honest about it: rotation is a short, scheduled outage for that flow. Say so, schedule it, and move on. Pretending otherwise just means the rotation never happens.
Keeping secrets out of everything downstream
The credential store is not the only place a secret shows up.
- Logging: Camel masks known password parameters when it logs endpoint URIs, but that masking covers recognized parameter names, not a token you stuffed into a custom query parameter or a header. Audit what your
log:steps and your tracer actually print, especially anywhereshowHeadersorshowAllis enabled. - Error payloads: a stack trace from an HTTP component can include the request line. Dead letter channels persist exchanges. Decide what your DLQ is allowed to store before an auditor asks.
- JMX and management endpoints: endpoint URIs are visible there. Lock them down like any other admin surface.
- Support bundles: the config dump someone sends a vendor during an incident is the least-controlled copy of your configuration that exists.
What we recommend on a review
The practical checklist we leave behind is short. Every credential resolves from a managed store, not from the artifact. Every route URI uses placeholders, with a documented resolution order. Rotation has been exercised at least once in a lower environment, with the reload behavior and the in-flight semantics observed rather than assumed. And logs, DLQs, and management surfaces have been checked for leakage.
None of it is hard. It is just work that no feature ticket ever asks for — which is exactly why it is usually the cheapest finding in the report to fix and the most expensive one to keep ignoring.