YAML DSL reference
The YAML DSL describes Cameleer routes in
*.camel.yamlfiles: one input (from), a list of steps (EIPs), endpoints written as URIs, and expressions in Simple or another language, with the same names as the Apache Camel YAML DSL.
This page lists everything the YAML loader (crates/cameleer-yaml) accepts: the file layout,
every top-level element, the endpoint and expression forms, every step with its keys and
defaults, and the mistakes people make most. Every route on this page runs in the test suite
(crates/cameleer-main/tests/docs_yaml.rs): the test sends messages through it and checks the
result, and resolves every endpoint URI the way Apache Camel does, so an unknown option fails
the build.
If you generate routes with a coding assistant, read YAML for coding agents
too, and check every file with cameleer validate.
A first look
# hello.camel.yaml: a list of top-level elements
- route:
id: hello # optional; generated when missing
from:
uri: timer:hello # the input endpoint
parameters:
repeatCount: 1
steps: # the steps, in order
- setBody:
simple: "Hello from ${routeId}"
- log: "${body}"
- to: mock:greetingsThree things to notice:
- The file is a YAML list. Each item has exactly one key: the element type (
route,rest,routeConfiguration, ...). stepsbelongs tofrom(Camel 4 style). Astepslist next tofrom, at route level, is accepted too and runs after the steps underfrom.- Every step is a list item with one key: the step name (
setBody,log,to, ...).
Files: naming, finding and loading
File names
| file name | what it holds |
|---|---|
*.camel.yaml (recommended), any *.yaml or *.yml | routes and other top-level elements |
*.kamelet.yaml | one Kamelet resource (kind: Kamelet) |
*.pipe.yaml | one Pipe (kind: Pipe or the older KameletBinding) |
*.integration.yaml | one Camel K Integration resource (its spec.flows are routes) |
*.xml | routes in the XML DSL (same steps, same option names) |
application.properties, application.yaml | configuration, never routes |
The .camel.yaml suffix is a convention from Camel JBang that makes route files easy to find
and lets editors pick the right schema; the loader only looks at the last extension. Use one
route per file for small services, or group related routes; there is no limit.
cameleer init orders.camel.yaml writes a starter route; cameleer init x.kamelet.yaml,
x.pipe.yaml and x.integration.yaml write starters for the other kinds.
Running files
cameleer run orders.camel.yaml # one file
cameleer run orders.camel.yaml billing.camel.yaml # several files, one context
cameleer run routes/*.camel.yaml # a shell glob
cameleer run # every .yaml/.yml/.xml file of the current folder
cameleer run --dev orders.camel.yaml # reload when a file changes
docker run --rm -v "$PWD:/routes:ro" pmsudhi/cameleer run /routes/orders.camel.yamlcameleer runwith no file loads every.yaml,.ymland.xmlfile of the current folder whose name does not containapplication.A directory is not accepted as a file argument: use a shell glob (
routes/*.camel.yaml) or the include pattern below.camel.main.routes-include-pattern(inapplication.properties, as--prop, or the environment variableCAMEL_MAIN_ROUTES_INCLUDE_PATTERN) adds files by pattern. It is a comma separated list of Ant-style patterns:*matches within a folder,**matches any number of folders, and afile:orclasspath:prefix is ignored, so Camel values work:shcameleer run --prop camel.main.routes-include-pattern=routes/**/*.camel.yaml
--devchecks the loaded route files about three times a second and, when one changed, removes every route that came from a file and loads all the files again. It also selects thedevprofile (application-dev.properties). A file that fails to load is logged, and its routes stay missing until you fix the file and save it again.- All files are loaded into one context. Route ids must be unique across files;
direct:endpoints connect routes from different files. - Kamelets in
./kamelets/(or the folders ofcamel.main.kamelet-locations) are loaded before the route files.
Inside a file
- Comments start with
#(at the start of a line, or after a space). - Several YAML documents in one file are allowed, separated by
---; each is a list of top-level elements, loaded in order. - A document may also be a single element written as a mapping (
route: ...without the leading-), but lists are the norm. - Anchors (
&name), aliases (*name) and merge keys (<<) work, so a repeated block can be written once. - Errors name the place:
at [0].route.from.steps[1].setBody (line 7, column 13 in orders.camel.yaml).
# first document: the HTTP-facing route
- route:
id: intake
from:
uri: direct:intake
steps:
- to: direct:store
---
# second document: the route it calls
- route:
id: store
from:
uri: direct:store
steps:
- setBody:
simple: "stored ${body}"
- to: mock:storedTop-level elements
| element | what it does | section |
|---|---|---|
route | a route: from plus steps | route |
from | a route without the route: wrapper | from |
routeConfiguration | onException, onCompletion, intercepts and an error handler shared by routes | routeConfiguration |
onException, onCompletion, intercept, interceptFrom, interceptSendToEndpoint | the same, written directly at top level (an anonymous route configuration that applies to every route without routeConfigurationId) | routeConfiguration |
errorHandler | the error handler of every route in the context | errorHandler |
routeTemplate (or routeTemplates) | a parameterized route | templates |
templatedRoute (or templatedRoutes) | a route created from a template | templates |
rest | REST endpoints (the REST DSL) | REST DSL |
restConfiguration | the HTTP server and binding for rest | REST DSL |
beans | named beans created by registered bean factories | beans |
dataFormats | accepted and stored, but not used: configure data formats on the marshal/unmarshal step |
A document whose top level is a mapping with apiVersion and kind is a Kubernetes-style
resource instead: kind: Kamelet, kind: Pipe, kind: KameletBinding or
kind: Integration (see Kamelets and Pipes).
Any other element name fails with Unknown top-level element '...' and the list of supported
names.
route
| key | default | description |
|---|---|---|
from | required | the input endpoint: a URI string, or a mapping with uri, parameters and steps |
steps | more steps, run after the steps under from | |
id (or routeId) | route1, route2, ... | the route id, used by management, logs and cameleer get routes |
description | free text | |
group | a group name for management | |
autoStartup | true | false creates the route stopped; start it with cameleer cmd start-route --id ID |
startupOrder | an integer; lower numbers start first and stop last | |
routeConfigurationId | the ids of the routeConfigurations to apply, comma separated; * patterns match | |
errorHandler | the context's | an error handler for this route only (same shape as the top-level errorHandler) |
errorHandlerRef | the name of an error handler bound in the registry | |
routePolicy (or routePolicyRef) | names of RoutePolicy beans in the registry, comma separated | |
inputType, outputType | {urn, validate} data type contracts, see transformDataType |
Other keys are kept as route properties and do not change behaviour, so a misspelled option
(for example autostartup) is silently ignored by the loader. cameleer validate reports it.
- route:
id: nightly-report
description: Builds the nightly report; started by an operator
group: reports
autoStartup: false
from:
uri: direct:report
steps:
- to: mock:report
- route:
id: always-on
startupOrder: 10
from:
uri: direct:ping
steps:
- setBody:
constant: pongfrom: the short form
A route can be written without the route: wrapper when it needs no route options:
- from:
uri: direct:short
steps:
- setBody:
simple: "short form: ${body}"
- to: mock:shortrouteConfiguration
A routeConfiguration holds error handling and interceptors that several routes share, as
in Camel 4. Its keys:
| key | description |
|---|---|
id | the id routes refer to with routeConfigurationId |
errorHandler | an error handler for the routes it applies to |
onException | a list of - onException: clauses (see error handling steps) |
onCompletion | a list of - onCompletion: clauses |
intercept, interceptFrom, interceptSendToEndpoint | lists of interceptors |
precondition, description | accepted and ignored |
Which routes get it:
- a configuration with an
idapplies to the routes that list it inrouteConfigurationId; - a configuration without an
idapplies to every route that has norouteConfigurationId; onExceptionand the other clauses written directly at top level count as a configuration without anid.
It does not matter whether the configuration comes before or after the routes in the file.
A routeConfigurationId that matches nothing fails at load time.
- routeConfiguration:
id: api-errors
onException:
- onException:
exception:
- java.lang.IllegalArgumentException
handled:
constant: true
steps:
- setBody:
simple: "rejected: ${exception.message}"
- to: mock:rejected
onCompletion:
- onCompletion:
onCompleteOnly: true
steps:
- to: mock:audit
- route:
id: create-order
routeConfigurationId: api-errors
from:
uri: direct:create-order
steps:
- filter:
simple: "${body} == ''"
steps:
- throwException:
exceptionType: java.lang.IllegalArgumentException
message: empty order
- setBody:
simple: "created ${body}"errorHandler
The top-level errorHandler applies to every route of the context; the errorHandler key of a
route or a routeConfiguration overrides it. It holds exactly one of:
| handler | keys |
|---|---|
defaultErrorHandler | redeliveryPolicy, redeliveryPolicyRef, useOriginalMessage, useOriginalBody, retryWhile, onRedeliveryRef, onExceptionOccurredRef, onPrepareFailureRef |
deadLetterChannel | deadLetterUri (required) plus the keys above and deadLetterHandleNewException |
noErrorHandler | none: failures go straight back to the caller |
refErrorHandler | the name of an error handler bound in the registry (or {ref: name}) |
redeliveryPolicy keys: maximumRedeliveries (default 0, -1 = forever), redeliveryDelay
(default 1000 ms), useExponentialBackOff, backOffMultiplier (default 2),
maximumRedeliveryDelay, useCollisionAvoidance, collisionAvoidanceFactor, delayPattern
(0:1000;5:5000), retryAttemptedLogLevel, retriesExhaustedLogLevel, logStackTrace,
logExhausted, logRetryAttempted, asyncDelayedRedelivery. Durations take milliseconds or a
unit (250ms, 5s, PT1M). Other Camel logging options are accepted and ignored. The
error handling guide explains the behaviour.
- errorHandler:
deadLetterChannel:
deadLetterUri: mock:dead-letters
useOriginalMessage: true
redeliveryPolicy:
maximumRedeliveries: 2
redeliveryDelay: 10ms
- route:
id: charge
from:
uri: direct:charge
steps:
- setBody:
simple: "charging ${body}"
- throwException:
exceptionType: java.io.IOException
message: payment gateway downrouteTemplate and templatedRoute
A routeTemplate is a route with {{parameter}} placeholders; a templatedRoute creates a
route from it. Templates are read before the other elements, so the order in the file does not
matter.
routeTemplate key | description |
|---|---|
id | required: the template id |
parameters | a list of {name, defaultValue, required, description}; a parameter without defaultValue is required unless required: false |
from (or route: {from: ...}) | the route; {{name}} and {{name:default}} are replaced in every string |
beans | template-local beans, created when the template is used |
description | free text |
templatedRoute key | description |
|---|---|
routeTemplateRef | required: the template id |
routeId | the id of the new route |
prefixId | a prefix added to the id of every step |
parameters | a list of {name, value} or a plain mapping {name: value} |
beans | beans to create for this route |
Placeholders that are not template parameters ({{env:...}}, application properties) are left
for the normal property resolution. A missing required parameter fails at load time:
Route template sensor the following mandatory parameters are missing: room.
- routeTemplate:
id: sensor
parameters:
- name: room
- name: unit
defaultValue: C
from:
uri: "direct:{{room}}"
steps:
- setBody:
simple: "{{room}}: ${body} {{unit}}"
- to: mock:readings
- templatedRoute:
routeTemplateRef: sensor
routeId: kitchen-sensor
parameters:
- name: room
value: kitchen
- templatedRoute:
routeTemplateRef: sensor
routeId: lab-sensor
parameters:
room: lab
unit: Krest and restConfiguration
The REST DSL declares HTTP operations and hands each request to a route, usually through
direct:. restConfiguration may appear in any file, before or after the rest elements: like
in Camel it is global, and REST routes are built when the context starts.
restConfiguration key | default | description |
|---|---|---|
component | platform-http | the HTTP server component |
host, port | camel.server.port (8080) | where to listen |
contextPath | a prefix for every path, for example /api | |
bindingMode | off | json, xml, json_xml, auto or off |
scheme | http | http or https |
apiContextPath | serve the OpenAPI document at this path (turns apiDocs on) | |
apiDocs, enableCORS, skipBindingOnErrorCode | booleans | |
corsHeaders, apiProperty, endpointProperty, consumerProperty | lists of {key, value} |
rest key | description |
|---|---|
path | the base path of the operations |
get, post, put, delete, patch, head, options | lists of operations |
verb | operations with an explicit method |
consumes, produces, bindingMode, description, tag | defaults for the operations |
Each operation takes path, to (a URI, or {uri, parameters}; toD is accepted),
id (the route id), description, consumes, produces, bindingMode, type, outType,
param (a list of {name, type: path|query|header|body|formData, required, dataType, defaultValue, description, allowableValues}), responseMessage (a list of {code, message}),
apiDocs and deprecated. Path parameters such as {id} become headers. Operations route
with to; they do not take inline steps.
- restConfiguration:
contextPath: /api
- rest:
path: /orders
get:
- path: /{id}
id: get-order
produces: application/json
to: direct:get-order
post:
- to: direct:create-order
- route:
from:
uri: direct:get-order
steps:
- setBody:
simple: '{"id": "${header.id}", "status": "shipped"}'
- route:
from:
uri: direct:create-order
steps:
- setHeader:
name: CamelHttpResponseCode
constant: 201
- setBody:
simple: "created ${body}"GET /api/orders/42 returns {"id": "42", "status": "shipped"}; POST /api/orders returns
status 201. See the REST component for binding and OpenAPI.
beans
A beans element is a list of {name, type, properties}. Rust cannot create Java classes by name, so a bean is created only when a bean factory is
registered for its type (cameleer_yaml::register_bean_factory) by a program that embeds
Cameleer. The cameleer binary registers no factories: it records the definition and logs
No bean factory registered for bean 'greeter'. To use processors, aggregation strategies or
repositories from YAML, bind them from Rust (ctx.registry().bind_processor(...)) and refer to
them by name; built-in aggregation strategies work by name without any bean (see
split).
- beans:
- name: greeter
type: com.acme.Greeter
properties:
prefix: Hello
- route:
from:
uri: direct:beans
steps:
- to: mock:beansKamelets and Pipes
A Kamelet is a reusable route snippet with typed properties. Use one as a source
(from: kamelet:name?...), a sink or an action (to: kamelet:name?..., or the kamelet
step). Built-in Kamelets: timer-source, log-sink, log-action, insert-header-action and
set-body-action; files in ./kamelets/ add more.
- route:
from:
uri: kamelet:timer-source
parameters:
message: Hello from a Kamelet
period: 100
repeatCount: 1
steps:
- kamelet:
name: insert-header-action
parameters:
name: origin
value: kamelet
- to: mock:from-kameletA Kamelet file (greeting-source.kamelet.yaml) is a resource, not a list of routes. Its
template uses {{property}} placeholders, filled from the URI parameters, then
camel.kamelet.<name>.<property> properties, then the declared default; {{?property}}
drops the option when it has no value:
apiVersion: camel.apache.org/v1
kind: Kamelet
metadata:
name: greeting-source
labels:
camel.apache.org/kamelet.type: source
spec:
definition:
required:
- message
properties:
message:
type: string
period:
type: integer
default: 1000
template:
from:
uri: timer:greeting
parameters:
period: "{{period}}"
steps:
- setBody:
simple: "{{message}}"
- to: kamelet:sinkA Pipe (orders.pipe.yaml) binds a source to a sink, with optional steps in between, and
becomes a route whose id is metadata.name. Each endpoint is a Kamelet ref with
properties or a plain uri; spec.errorHandler takes sink, log or none:
apiVersion: camel.apache.org/v1
kind: Pipe
metadata:
name: greetings-pipe
spec:
source:
ref:
kind: Kamelet
apiVersion: camel.apache.org/v1
name: timer-source
properties:
message: Hello from a Pipe
period: 100
repeatCount: 1
steps:
- ref:
kind: Kamelet
apiVersion: camel.apache.org/v1
name: set-body-action
properties:
value: "piped: ${body}"
sink:
uri: mock:pipedcameleer bind my-pipe --source timer-source --sink log-sink writes the Pipe file my-pipe.yaml
(--output FILE picks another name). The
Kamelets section of the EIP guide has more.
Endpoints
An endpoint is a URI: scheme:path?option=value&option=value. Each component's page lists its
options: docs/components/ (for example kafka), and
cameleer catalog components lists every scheme. Unknown options fail when the route starts,
exactly like Apache Camel: There are 1 parameters that couldn't be set on the endpoint ... Unknown parameters=[perod].
Two equivalent ways to write options:
- route:
from:
uri: direct:forms
steps:
# inline: the whole URI in one string
- to: "seda:inline?size=100&blockWhenFull=true"
# uri + parameters: easier to read, values are URL-encoded for you
- to:
uri: seda:mapped
parameters:
size: 100
blockWhenFull: true
- to: mock:forms- With
parameters, values are URL-encoded (spaces,&,+,=are safe), except values that contain{{,${or start withRAW(, which are passed as they are. A list value is joined with commas. - In an inline URI you encode values yourself;
+means a space. To pass a value exactly as written (passwords, regular expressions), wrap it inRAW(...):password=RAW(se+cr&t).RAW{...}works too when the value contains). fromtakes the same forms:from: {uri: ..., parameters: {...}, steps: [...]}.- Quote a URI when it contains
#after a space,:(colon and space), or starts with{,*,&,!,%or@; quoting is always safe.
- route:
from:
uri: direct:raw
steps:
- to: "log:audit?marker=RAW(a+b&c)"
- to:
uri: log:audit-mapped
parameters:
marker: "a+b&c"
- to: mock:rawProperty placeholders
Any string (URIs, step options, expressions) can contain placeholders, resolved when the route is created:
| placeholder | value |
|---|---|
{{name}} | the property name from application.properties/application.yaml, --prop, CAMEL_* environment variables, or CAMEL_PROPERTY_NAME; fails when missing |
{{name:default}} | the same, with a default (everything after the first :, so {{broker:localhost:9092}} works) |
{{env:NAME}}, {{env:NAME:default}} | the environment variable NAME |
{{sys:NAME}} | the environment variable NAME (there are no JVM system properties) |
{{aws:...}}, {{azure:...}}, {{gcp:...}} | secrets from a cloud vault, see cloud credentials |
- route:
from:
uri: "timer:config?period={{poll.period:200}}&repeatCount=1"
steps:
- setBody:
simple: "{{greeting:Hello}} from {{env:SITE_NAME:the lab}}"
- to: mock:configuredA value that starts with {{ must be quoted ("{{broker}}"), otherwise YAML reads it as a
flow mapping. Never write secrets in route files: use {{env:...}} or a vault.
Sending to endpoints
| step | keys | description |
|---|---|---|
to | a URI string, or uri, parameters, pattern (InOnly/InOut) | send the message and continue with the reply |
toD | the same plus ignoreInvalidEndpoint (default false) | a dynamic URI, evaluated with Simple per message |
inOnly, inOut | like to | to with a fixed exchange pattern (deprecated in Camel 4, kept for old routes) |
wireTap | URI or uri/parameters, copy (default true), body, setHeader (list of {name, <expression>}), onPrepare, ignoreInvalidEndpoint | send a copy asynchronously and continue at once |
enrich | URI, uri/parameters, or an expression; aggregationStrategy, aggregateOnException, ignoreInvalidEndpoint, shareUnitOfWork | call an endpoint and merge its reply (default: the reply replaces the message) |
pollEnrich | like enrich, plus timeout (ms; default -1 = wait) | consume one message from an endpoint (file, seda, ...) and merge it |
poll | URI or uri/parameters, timeout (default 20000; whole milliseconds only, a unit such as 2s is rejected) | consume one message and make it the message |
kamelet | a name, or name and parameters | send to kamelet:name |
bean | ref (or beanType), method, scope | call a bean bound in the registry (bean: endpoint) |
process | a name, or ref | run a processor bound in the registry |
serviceCall | name, uri, component, pattern, expression, staticServiceDiscovery, serviceDiscoveryRef, filters, randomLoadBalancer/roundRobinLoadBalancer | call a named service through a load balancer |
uri values with ${...} need toD; to treats the URI as fixed text.
pollEnrich and poll consume from an endpoint in the middle of a route, for example a
message waiting on a queue or a file in a folder:
- route:
from:
uri: direct:with-config
steps:
- pollEnrich:
uri: seda:latest-config
timeout: 2000
- setBody:
simple: "config is ${body}"
- to: mock:configured-call
- route:
from:
uri: direct:next-job
steps:
- poll:
uri: seda:jobs
timeout: 2000
- to: mock:job- route:
from:
uri: direct:dispatch
steps:
- wireTap: mock:audit-copy
- enrich:
uri: direct:price-lookup
- toD: "mock:region-${header.region}"
- route:
from:
uri: direct:price-lookup
steps:
- setBody:
simple: "${body} costs 42"Expressions and predicates
Steps such as setBody, filter, split and choice/when take an expression (a value) or
a predicate (an expression whose result is read as true or false). Every form below works
wherever a step takes one.
| form | example | meaning |
|---|---|---|
| short | simple: "${body.length()}" | a language key and the expression text |
| long | simple: {expression: "${header.n}", resultType: int} | the text plus language options |
| wrapped | expression: {simple: "${body}"} | the Camel schema's explicit form (also expression: {constant: x}) |
| plain string | setBody: "Hello ${body}" | a Simple expression |
| plain number or boolean | setBody: 42 | a constant |
| generic language | language: {language: groovy, expression: "..."} | any registered language by name |
A step may name only one language key; two (for example simple and constant) fail with
only one expression language may be specified. Language options in the long form (for
example resultType, trim, headerName, source, suppressExceptions) are passed to the
language.
- route:
from:
uri: direct:forms-of-expressions
steps:
- setHeader:
name: short
simple: "${body.toUpperCase()}"
- setHeader:
name: typed
simple:
expression: "${body.length()}"
resultType: java.lang.Integer
- setHeader:
name: wrapped
expression:
constant: fixed
- setHeader:
name: plain
expression: "plain ${body}"
- setBody: "Hello ${body}"
- to: mock:expressionsLanguage keys
| key | language | notes |
|---|---|---|
simple | Simple | the default; ${body}, ${header.x}, operators, functions |
constant | Constant | a fixed value |
header | Header | the value of a header: header: orderId |
exchangeProperty | Exchange property | the value of an exchange property |
variable | Variable | the value of a variable |
jsonpath | JSONPath | jsonpath: "$.items[*].sku" |
jq | jq | jq: ".items | length" |
xpath | XPath | XPath 1.0 |
xquery | XQuery | |
tokenize | Tokenize | tokenize: "," or tokenize: {token: ",", group: 10} |
method | Bean method | a bean in the registry |
ref | Ref | an expression bound in the registry |
groovy, js/javaScript, python, mvel, ognl, spel, datasonnet, csimple, joor/java | script languages, for example groovy and python | interpreters from cameleer-script |
hl7terser | HL7 terser | |
language | any | language: {language: <name>, expression: ...} |
cameleer catalog languages lists the languages of your build. The loader also accepts the
keys xtokenize and wasm, but no language of those names is built in, so a route that uses
them fails when it starts.
- route:
from:
uri: direct:languages
steps:
- setProperty:
name: source
constant: sensor-7
- setHeader:
name: firstSku
jsonpath: "$.items[0].sku"
- setHeader:
name: count
jq: ".items | length"
- setHeader:
name: from
exchangeProperty: source
- setHeader:
name: copy
header: firstSku
- to: mock:languagesPredicates
A predicate is an expression read as a boolean. Simple predicates use operators: ==, !=,
>, >=, <, <=, contains, not contains, startsWith, endsWith, regex, in,
range, is, and && / || to combine them. Quote string literals inside Simple with single
quotes: ${header.type} == 'gold'. Any language works as a predicate (jsonpath, jq,
xpath, ...). The result is read as in Camel: a boolean is its value; a string is false only
when it is false (any case), so even an empty string is true; a list is true when it is not
empty; null is false; anything else (numbers, including 0) is true.
Steps
Every step is a list item with one key. Keys every step accepts:
id: names the step (shown bycameleer get routesand in statistics; also applies towhenandotherwise).disabled: true: skips the step, as if it were not there.
A step with no options can be written as a bare name (- stop) or with an empty mapping
(- stop: {}). Steps that contain other steps (filter, split, doTry, ...) take a
steps list, indented under the step's own keys. Unknown keys of a step are ignored by the
loader; cameleer validate reports them.
The tables below give each step's keys and defaults. The EIP guides explain the patterns in depth: routing, split and aggregate, transformation, flow control, interceptors, completions and templates, and error handling.
Message content
| step | keys | description |
|---|---|---|
setBody | an expression | replace the body |
transform | an expression | the same as setBody (the Message Translator name) |
setHeader | name, an expression | set one header |
setHeaders | headers: a list of {name, <expression>} | set several headers in order |
setProperty | name, an expression | set an exchange property |
setVariable | name, an expression | set a variable |
setVariables | variables: a list of {name, <expression>} | set several variables |
removeHeader | a name, or name | remove one header |
removeHeaders | a pattern, or pattern and excludePattern (list or comma separated) | remove matching headers (* wildcards or a regex) |
removeProperty, removeProperties | like the header steps | remove exchange properties |
removeVariable | a name, or name | remove a variable |
convertBodyTo | a type, or type, mandatory (default false) | convert the body: String, Integer, Long, Double, Boolean, byte[], Java names like java.lang.String |
convertHeaderTo | name, type, mandatory | convert a header |
marshal, unmarshal | a data format: json: {}, csv: {delimiter: ";"}, custom: {ref: name}, or a plain name (- marshal: json) | object to text/bytes and back; see data formats |
transformDataType | toType, fromType | apply a registered data type transformer |
script | an expression | run a script; its result is discarded, its changes to the message are kept |
log | a Simple message, or message, loggingLevel (default INFO), logName, marker | log a line |
setExchangePattern | InOnly or InOut (or pattern) | change the exchange pattern |
claimCheck | operation (Set, Get, GetAndRemove, Push, Pop), key, filter | store part of the message and get it back later |
sort | an expression, comparator | sort a list body |
- route:
from:
uri: direct:message
steps:
- setHeaders:
headers:
- name: customer
simple: "${body[customer]}"
- name: tmpTrace
constant: "abc"
- setProperty:
name: total
simple: "${body[total]}"
- setVariable:
name: greeting
constant: Dear
- removeHeaders:
pattern: "tmp*"
- marshal:
json: {}
- convertBodyTo: String
- log:
message: "order JSON: ${body}"
loggingLevel: DEBUG
logName: orders
- setBody:
simple: "${variable.greeting} ${header.customer}, total ${exchangeProperty.total}"
- to: mock:message- route:
from:
uri: direct:more-message-steps
steps:
- setVariables:
variables:
- name: unit
constant: kg
- name: tmp
constant: scratch
- removeVariable: tmp
- convertHeaderTo:
name: weight
type: Integer
- claimCheck:
operation: Push
- transform:
simple: "${header.weight} ${variable.unit}"
- to: mock:weight
- claimCheck:
operation: Pop
- sort:
simple: "${body}"
- to: mock:sortedRouting
| step | keys | description |
|---|---|---|
filter | a predicate, steps | run the steps only for matching messages; others continue after the filter |
choice | when (list of {<predicate>, steps}), otherwise ({steps} or a list), precondition | the first matching when runs; otherwise otherwise |
multicast | steps, parallelProcessing, aggregationStrategy, stopOnException, streaming, timeout, shareUnitOfWork | send a copy of the message to each step |
recipientList | an expression, delimiter (default ,), ignoreInvalidEndpoints, plus the multicast keys | send to the endpoints the expression returns |
routingSlip | an expression, uriDelimiter (default ,), ignoreInvalidEndpoints | send through the endpoints one after another |
dynamicRouter | an expression, uriDelimiter, ignoreInvalidEndpoints | ask the expression for the next endpoint until it returns nothing |
loadBalance | one policy (roundRobin, random, topic, sticky: {correlationExpression}, weighted: {distributionRatio, roundRobin}, failover: {exception, maximumFailoverAttempts, roundRobin, sticky}, custom: {ref}), steps | send each message to one of the steps (default roundRobin) |
loop | an expression (a count, or a predicate with doWhile: true), copy (default false), steps | repeat the steps; CamelLoopIndex holds the index |
pipeline | steps | run steps in sequence (useful inside multicast) |
step | steps, id | group steps under one id for statistics |
stop | none | stop routing this message |
- route:
from:
uri: direct:tickets
steps:
- choice:
when:
- simple: "${header.priority} == 'P1'"
steps:
- to: mock:page-oncall
- simple: "${header.priority} == 'P2'"
steps:
- to: mock:team-queue
otherwise:
steps:
- to: mock:backlog
- filter:
simple: "${header.priority} == 'P3'"
steps:
- stop
- to: mock:escalated- route:
from:
uri: direct:fan-out
steps:
- multicast:
aggregationStrategy: groupedBody
steps:
- pipeline:
steps:
- setBody:
simple: "email:${body}"
- pipeline:
steps:
- setBody:
simple: "sms:${body}"
- to: mock:fanned-out
- loop:
constant: 3
steps:
- setBody:
simple: "attempt ${exchangeProperty.CamelLoopIndex}"
- to: mock:loop
- loadBalance:
roundRobin: {}
steps:
- to: mock:node-a
- to: mock:node-b- route:
from:
uri: direct:notify
steps:
# endpoints listed in a header, comma separated
- recipientList:
simple: "${header.channels}"
- route:
from:
uri: direct:photo
steps:
# the endpoints in turn, each one's output feeding the next
- routingSlip:
header: slip
- route:
from:
uri: direct:resize
steps:
- setBody:
simple: "${body}-resized"Split, aggregate and reorder
| step | keys | description |
|---|---|---|
split | an expression, steps, aggregationStrategy, parallelProcessing, streaming, stopOnException, timeout, shareUnitOfWork, delimiter (default ,, "false" to keep a string whole) | run the steps once per part; parts get CamelSplitIndex, CamelSplitSize, CamelSplitComplete |
aggregate | correlationExpression (required), aggregationStrategy (required), completionSize, completionTimeout, completionInterval, completionPredicate, completionSizeExpression, completionTimeoutExpression, completionFromBatchConsumer, completionOnNewCorrelationGroup, eagerCheckCompletion, forceCompletionOnStop, discardOnCompletionTimeout, ignoreInvalidCorrelationKeys, closeCorrelationKeyOnCompletion, aggregationRepository, steps | combine related messages into one |
resequence | an expression, batchConfig: {batchSize: 100, batchTimeout: 1000, allowDuplicates, reverse, ignoreInvalidExchanges} or streamConfig: {capacity: 1000, timeout: 1000, rejectOld, ignoreInvalidExchanges}, steps | put messages back in order |
aggregationStrategy takes the name of a strategy bound in the registry, or a built-in one:
groupedBody (a list of bodies), groupedExchange, useLatest, useOriginal, string
(concatenate), also written with Camel class names such as
#class:org.apache.camel.processor.aggregate.GroupedBodyAggregationStrategy. Without a
strategy, split continues with the original message, as in Camel.
- route:
from:
uri: direct:csv-lines
steps:
- split:
tokenize: "\n"
aggregationStrategy: groupedBody
steps:
- setBody:
simple: "${body.toUpperCase()}"
- to: mock:each-line
- to: mock:all-lines
- route:
from:
uri: direct:readings
steps:
- aggregate:
correlationExpression:
header: device
aggregationStrategy: groupedBody
completionSize: 2
completionTimeout: 5s
steps:
- to: mock:device-batch- route:
from:
uri: direct:out-of-order
steps:
- resequence:
header: seq
batchConfig:
batchSize: 3
batchTimeout: 200
steps:
- to: mock:in-orderError handling
| step | keys | description |
|---|---|---|
doTry | steps, doCatch (list of {exception: [...], onWhen, steps}), doFinally ({steps}) | try/catch/finally inside a route |
onException | exception (list of kinds), handled, continued, onWhen, retryWhile, redeliveryPolicy, redeliveryPolicyRef, useOriginalMessage, useOriginalBody, onRedeliveryRef, onExceptionOccurredRef, steps | react to a kind of failure; as a route step it applies to that route, in a routeConfiguration to many |
onCompletion | onCompleteOnly, onFailureOnly, mode (AfterConsumer default, BeforeConsumer), onWhen, steps | run steps when the exchange finishes |
circuitBreaker | steps, onFallback ({steps}), resilience4jConfiguration (failureRateThreshold, minimumNumberOfCalls, slidingWindowSize, waitDurationInOpenState in seconds, permittedNumberOfCallsInHalfOpenState, timeoutEnabled, timeoutDuration), faultToleranceConfiguration, configuration (a registry name) | stop calling a failing service for a while |
throwException | exceptionType (required), message | fail the exchange with that kind of exception |
validate | a predicate | fail with PredicateValidationException when it is false |
rollback | a message, or message, markRollbackOnly, markRollbackOnlyLast | roll back the current transaction |
transacted | a policy name, or ref; steps | run in a transaction (without steps: the rest of the route) |
policy | ref, steps | wrap steps in a registered policy (without steps: the rest of the route) |
saga | propagation, completionMode, timeout, compensation, completion, option (list of {key, <expression>}), sagaService, steps | a long-running transaction with compensation |
intercept, interceptFrom, interceptSendToEndpoint | onWhen, steps; interceptFrom takes uri (default *); interceptSendToEndpoint takes uri (required) and skipSendToOriginalEndpoint | run steps before every step, every input, or every send to matching endpoints (usually in a routeConfiguration) |
Exception kinds are Camel's Java class names; the package is ignored, so
java.lang.IllegalArgumentException and IllegalArgumentException are the same. A kind matches
its subkinds (java.lang.Exception catches everything). handled and continued take true,
false, {constant: true} or a predicate.
- route:
from:
uri: direct:fragile
steps:
- onException:
exception:
- java.io.IOException
redeliveryPolicy:
maximumRedeliveries: 1
redeliveryDelay: 0
handled:
constant: true
steps:
- setBody:
constant: "service unavailable, queued"
- to: mock:retry-later
- doTry:
steps:
- validate:
simple: "${body} != 'bad'"
doCatch:
- exception:
- org.apache.camel.support.processor.validation.PredicateValidationException
steps:
- setBody:
constant: "replaced a bad message"
doFinally:
steps:
- to: mock:checked
- circuitBreaker:
steps:
- filter:
simple: "${body} == 'boom'"
steps:
- throwException:
exceptionType: java.lang.IllegalStateException
message: downstream failed
onFallback:
steps:
- setBody:
constant: "fallback answer"
- filter:
simple: "${body} == 'offline'"
steps:
- throwException:
exceptionType: java.io.IOException
message: disk offline
- to: mock:fragile-doneInterceptors and completions are usually written once, at top level or in a
routeConfiguration, and apply to every route without routeConfigurationId:
- interceptSendToEndpoint:
uri: "mock:legacy*"
skipSendToOriginalEndpoint: true
steps:
- setBody:
simple: "redirected ${body}"
- to: mock:modern
- onCompletion:
onFailureOnly: true
steps:
- to: mock:failures
- route:
from:
uri: direct:intercepted
steps:
- to: mock:legacy-system
- filter:
simple: "${body} contains 'fail'"
steps:
- throwException:
exceptionType: java.lang.IllegalStateException
message: failed on purposeFlow control
| step | keys | description |
|---|---|---|
throttle | a constant expression or maximumRequests, timePeriodMillis (default 1000), correlationExpression, rejectExecution, asyncDelayed | at most N messages per period |
delay | an expression (milliseconds), asyncDelayed (default true) | wait before continuing |
sample | a period, or samplePeriod (default 1 s) or messageFrequency | let one message through per period or per N messages |
idempotentConsumer | an expression (the message id), steps, idempotentRepository (default: in memory, 1000 ids), eager (true), skipDuplicate (true), removeOnFailure (true), completionEager (false) | process each id once |
threads | poolSize, maxPoolSize, keepAliveTime (+ timeUnit, default seconds), maxQueueSize, allowCoreThreadTimeOut, rejectedPolicy, callerRunsWhenRejected, threadName, executorService, steps | continue on a thread pool (without steps: the rest of the list) |
- route:
from:
uri: direct:events
steps:
- idempotentConsumer:
header: eventId
steps:
- throttle:
constant: 100
timePeriodMillis: 1000
- delay:
constant: 10
- to: mock:unique-eventsValues: booleans, numbers and durations
- Booleans are
trueandfalse(any case). YAML 1.2 is used, soyes,no,onandoffare strings: a boolean option set toyesfails with'yes' is not a boolean, anddisabled: yesdoes not disable the step. Placeholders work:autoStartup: "{{orders.enabled:true}}". - Numbers can be written plain (
completionSize: 10) or quoted ("10"). - Durations (
completionTimeout,redeliveryDelay,timePeriodMillis,timeout, ...) are milliseconds when plain (5000), or take a unit:250ms,5s,1m,PT1M. The exception is thetimeoutofpoll, which takes whole milliseconds only.waitDurationInOpenStateofresilience4jConfigurationandkeepAliveTimeofthreadsare in seconds, as in Camel. - Endpoint option values are text:
period: 1000andperiod: "1000"are the same.
Ids and descriptions
- Route ids:
idon the route; a missing id becomesroute1,route2, ... Ids must be unique in the context, across all files. - Step ids:
idon any step,whenorotherwise. WithprefixId, atemplatedRouteadds a prefix to each. descriptionon a route is kept for management; on a step it is ignored.
Common mistakes
steps at the wrong indentation. steps belongs under from (at the same level as
uri) or under route; nested steps belong under their own step, at the level of its other
keys. Indentation decides which mapping a key belongs to:
- route:
from:
uri: direct:a
steps: # right: under from, aligned with uri
- to: mock:b
- route:
from:
uri: direct:c
steps: # wrong: a second key of the list item, rejected with
- to: mock:d # "Each top-level element must have exactly one key"
- route:
from:
uri: direct:e
steps:
- filter:
simple: "${body} == 'x'"
steps: # wrong: a second key of the step, rejected with
- to: mock:f # "A step must have exactly one key"Two keys in one step. Each list item is one step. Writing an option at the step level
fails with A step must have exactly one key:
- to: mock:a
pattern: InOut # wrong: belongs under `to`
- to:
uri: mock:a
pattern: InOut # rightUnquoted special characters. These need quotes:
| value | why | write |
|---|---|---|
log: Status: ${body} | : starts a mapping | log: "Status: ${body}" |
simple: Order #${body} | # starts a comment | simple: "Order #${body}" |
simple: {"id": 1} | { starts a flow mapping | simple: '{"id": 1}' |
uri: {{broker}} | {{ starts a flow mapping | uri: "{{broker}}" |
constant: 007 | a number (7) | constant: "007" |
constant: true | a boolean, not text | constant: "true" |
Single quotes keep backslashes and ${...} literally ('a\nb' is four characters); double
quotes process escapes ("a\nb" has a line break). Use double quotes for tokenize: "\n".
${...} in a to URI. to sends to the URI as written; use toD for a URI built from the
message.
Booleans as words. yes, on and off are not booleans (see values).
: in URIs. Plain URIs like timer:tick?period=1000 need no quotes: a colon only matters
when a space follows it. Quote a URI that contains : or #.
Expecting split to return the parts. Without aggregationStrategy, the message after a
split is the original one.
Two expression languages in one step. setBody: {simple: ..., constant: ...} fails; pick
one.
Differences from Apache Camel
- Same element names, step names and option names as the Camel 4 YAML DSL; old Camel 3 shapes
that the loader still reads:
expression: "text"(Simple),routeTemplates/templatedRoutes,inOnly/inOut. - Extra short forms:
setBody: "text"(a Simple expression),tokenize: ",", steps as bare names (- stop), and a route mapping withfrombut without theroute:wrapper. - Beans of Java classes are not instantiated (see beans); processors and strategies are bound from Rust.
throttleneeds a constant maximum;interceptSendToEndpointafterUriis ignored with a warning;dataFormatsat top level is stored but not used; unknown step keys are ignored (Camel's schema rejects them);cameleer validatereports them.- The loader uses YAML 1.2 (like Camel's snakeyaml-engine), so
yes/noare strings.
Common questions
How do I write a Camel route in YAML?
Create a file ending in .camel.yaml with a list containing one - route:, give it a from
with a uri and a steps list, and run cameleer run file.camel.yaml. Start from the
first look example.
Can I run my Apache Camel YAML routes unchanged?
Yes when they use components and options Cameleer supports: the element, step and option names
are the same. Java beans and processors must be rewritten in Rust. cameleer validate tells
you what does not resolve; see migrating from Apache Camel.
How do I load all route files of a folder?
cameleer run folder/*.camel.yaml, or set camel.main.routes-include-pattern=folder/**/*.camel.yaml.
With no arguments, cameleer run loads the route files of the current folder.
How do I share error handling between routes?
Put onException clauses and an errorHandler in a routeConfiguration. Without an id it
applies to every route; with an id, list it in each route's routeConfigurationId.
Is there a JSON Schema for editors?
Yes: cameleer schema > cameleer.schema.json prints a JSON Schema of the YAML DSL that editors
(VS Code with the YAML extension, IntelliJ) use for completion and checks. Setup steps are in
editor support; cameleer validate covers what a schema cannot,
such as endpoint options and expressions.
Related
- YAML for coding agents and
cameleer validate - Getting started, concepts, EIP guide, error handling
- Components, languages, data formats
- Apache Camel: YAML DSL
Found a problem?
If something on this page doesn't match what Cameleer does, or you hit a bug while building
with it, please help fix it, whether you are a person or a coding agent:
open an issue that describes what you saw,
then fork the repository, fix it, add or update a test, run cargo test --workspace, and open
a pull request. CONTRIBUTING.md
explains the ground rules.