Cameleer

YAML DSL reference

The YAML DSL describes Cameleer routes in *.camel.yaml files: 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

yaml example: yaml-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:greetings

Three things to notice:

  • The file is a YAML list. Each item has exactly one key: the element type (route, rest, routeConfiguration, ...).
  • steps belongs to from (Camel 4 style). A steps list next to from, at route level, is accepted too and runs after the steps under from.
  • Every step is a list item with one key: the step name (setBody, log, to, ...).

Files: naming, finding and loading

File names

file namewhat it holds
*.camel.yaml (recommended), any *.yaml or *.ymlroutes and other top-level elements
*.kamelet.yamlone Kamelet resource (kind: Kamelet)
*.pipe.yamlone Pipe (kind: Pipe or the older KameletBinding)
*.integration.yamlone Camel K Integration resource (its spec.flows are routes)
*.xmlroutes in the XML DSL (same steps, same option names)
application.properties, application.yamlconfiguration, 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

sh
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.yaml
  • cameleer run with no file loads every .yaml, .yml and .xml file of the current folder whose name does not contain application.

  • 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 (in application.properties, as --prop, or the environment variable CAMEL_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 a file: or classpath: prefix is ignored, so Camel values work:

    sh
    cameleer run --prop camel.main.routes-include-pattern=routes/**/*.camel.yaml
  • --dev checks 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 the dev profile (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 of camel.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).
yaml example: yaml-multi-document
# 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:stored

Top-level elements

elementwhat it doessection
routea route: from plus stepsroute
froma route without the route: wrapperfrom
routeConfigurationonException, onCompletion, intercepts and an error handler shared by routesrouteConfiguration
onException, onCompletion, intercept, interceptFrom, interceptSendToEndpointthe same, written directly at top level (an anonymous route configuration that applies to every route without routeConfigurationId)routeConfiguration
errorHandlerthe error handler of every route in the contexterrorHandler
routeTemplate (or routeTemplates)a parameterized routetemplates
templatedRoute (or templatedRoutes)a route created from a templatetemplates
restREST endpoints (the REST DSL)REST DSL
restConfigurationthe HTTP server and binding for restREST DSL
beansnamed beans created by registered bean factoriesbeans
dataFormatsaccepted 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

keydefaultdescription
fromrequiredthe input endpoint: a URI string, or a mapping with uri, parameters and steps
stepsmore steps, run after the steps under from
id (or routeId)route1, route2, ...the route id, used by management, logs and cameleer get routes
descriptionfree text
groupa group name for management
autoStartuptruefalse creates the route stopped; start it with cameleer cmd start-route --id ID
startupOrderan integer; lower numbers start first and stop last
routeConfigurationIdthe ids of the routeConfigurations to apply, comma separated; * patterns match
errorHandlerthe context'san error handler for this route only (same shape as the top-level errorHandler)
errorHandlerRefthe 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.

yaml example: yaml-route-options
- 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: pong

from: the short form

A route can be written without the route: wrapper when it needs no route options:

yaml example: yaml-from-short-form
- from:
    uri: direct:short
    steps:
      - setBody:
          simple: "short form: ${body}"
      - to: mock:short

routeConfiguration

A routeConfiguration holds error handling and interceptors that several routes share, as in Camel 4. Its keys:

keydescription
idthe id routes refer to with routeConfigurationId
errorHandleran error handler for the routes it applies to
onExceptiona list of - onException: clauses (see error handling steps)
onCompletiona list of - onCompletion: clauses
intercept, interceptFrom, interceptSendToEndpointlists of interceptors
precondition, descriptionaccepted and ignored

Which routes get it:

  • a configuration with an id applies to the routes that list it in routeConfigurationId;
  • a configuration without an id applies to every route that has no routeConfigurationId;
  • onException and the other clauses written directly at top level count as a configuration without an id.

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.

yaml example: yaml-route-configuration
- 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:

handlerkeys
defaultErrorHandlerredeliveryPolicy, redeliveryPolicyRef, useOriginalMessage, useOriginalBody, retryWhile, onRedeliveryRef, onExceptionOccurredRef, onPrepareFailureRef
deadLetterChanneldeadLetterUri (required) plus the keys above and deadLetterHandleNewException
noErrorHandlernone: failures go straight back to the caller
refErrorHandlerthe 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.

yaml example: yaml-error-handler
- 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 down

routeTemplate 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 keydescription
idrequired: the template id
parametersa 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
beanstemplate-local beans, created when the template is used
descriptionfree text
templatedRoute keydescription
routeTemplateRefrequired: the template id
routeIdthe id of the new route
prefixIda prefix added to the id of every step
parametersa list of {name, value} or a plain mapping {name: value}
beansbeans 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.

yaml example: yaml-route-template
- 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: K

rest 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 keydefaultdescription
componentplatform-httpthe HTTP server component
host, portcamel.server.port (8080)where to listen
contextPatha prefix for every path, for example /api
bindingModeoffjson, xml, json_xml, auto or off
schemehttphttp or https
apiContextPathserve the OpenAPI document at this path (turns apiDocs on)
apiDocs, enableCORS, skipBindingOnErrorCodebooleans
corsHeaders, apiProperty, endpointProperty, consumerPropertylists of {key, value}
rest keydescription
paththe base path of the operations
get, post, put, delete, patch, head, optionslists of operations
verboperations with an explicit method
consumes, produces, bindingMode, description, tagdefaults 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.

yaml example: yaml-rest
- 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).

yaml example: yaml-beans
- beans:
    - name: greeter
      type: com.acme.Greeter
      properties:
        prefix: Hello
- route:
    from:
      uri: direct:beans
      steps:
        - to: mock:beans

Kamelets 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.

yaml example: yaml-kamelet-source
- 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-kamelet

A 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:

yaml
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:sink

A 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:

yaml
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:piped

cameleer 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:

yaml example: yaml-endpoint-forms
- 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 with RAW(, 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 in RAW(...): password=RAW(se+cr&t). RAW{...} works too when the value contains ).
  • from takes 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.
yaml example: yaml-endpoint-raw
- 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:raw

Property placeholders

Any string (URIs, step options, expressions) can contain placeholders, resolved when the route is created:

placeholdervalue
{{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
yaml example: yaml-placeholders
- route:
    from:
      uri: "timer:config?period={{poll.period:200}}&repeatCount=1"
      steps:
        - setBody:
            simple: "{{greeting:Hello}} from {{env:SITE_NAME:the lab}}"
        - to: mock:configured

A 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

stepkeysdescription
toa URI string, or uri, parameters, pattern (InOnly/InOut)send the message and continue with the reply
toDthe same plus ignoreInvalidEndpoint (default false)a dynamic URI, evaluated with Simple per message
inOnly, inOutlike toto with a fixed exchange pattern (deprecated in Camel 4, kept for old routes)
wireTapURI or uri/parameters, copy (default true), body, setHeader (list of {name, <expression>}), onPrepare, ignoreInvalidEndpointsend a copy asynchronously and continue at once
enrichURI, uri/parameters, or an expression; aggregationStrategy, aggregateOnException, ignoreInvalidEndpoint, shareUnitOfWorkcall an endpoint and merge its reply (default: the reply replaces the message)
pollEnrichlike enrich, plus timeout (ms; default -1 = wait)consume one message from an endpoint (file, seda, ...) and merge it
pollURI or uri/parameters, timeout (default 20000; whole milliseconds only, a unit such as 2s is rejected)consume one message and make it the message
kameleta name, or name and parameterssend to kamelet:name
beanref (or beanType), method, scopecall a bean bound in the registry (bean: endpoint)
processa name, or refrun a processor bound in the registry
serviceCallname, uri, component, pattern, expression, staticServiceDiscovery, serviceDiscoveryRef, filters, randomLoadBalancer/roundRobinLoadBalancercall 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:

yaml example: yaml-polling
- 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
yaml example: yaml-sending
- 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.

formexamplemeaning
shortsimple: "${body.length()}"a language key and the expression text
longsimple: {expression: "${header.n}", resultType: int}the text plus language options
wrappedexpression: {simple: "${body}"}the Camel schema's explicit form (also expression: {constant: x})
plain stringsetBody: "Hello ${body}"a Simple expression
plain number or booleansetBody: 42a constant
generic languagelanguage: {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.

yaml example: yaml-expression-forms
- 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:expressions

Language keys

keylanguagenotes
simpleSimplethe default; ${body}, ${header.x}, operators, functions
constantConstanta fixed value
headerHeaderthe value of a header: header: orderId
exchangePropertyExchange propertythe value of an exchange property
variableVariablethe value of a variable
jsonpathJSONPathjsonpath: "$.items[*].sku"
jqjqjq: ".items | length"
xpathXPathXPath 1.0
xqueryXQuery
tokenizeTokenizetokenize: "," or tokenize: {token: ",", group: 10}
methodBean methoda bean in the registry
refRefan expression bound in the registry
groovy, js/javaScript, python, mvel, ognl, spel, datasonnet, csimple, joor/javascript languages, for example groovy and pythoninterpreters from cameleer-script
hl7terserHL7 terser
languageanylanguage: {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.

yaml example: yaml-languages
- 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:languages

Predicates

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 by cameleer get routes and in statistics; also applies to when and otherwise).
  • 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

stepkeysdescription
setBodyan expressionreplace the body
transforman expressionthe same as setBody (the Message Translator name)
setHeadername, an expressionset one header
setHeadersheaders: a list of {name, <expression>}set several headers in order
setPropertyname, an expressionset an exchange property
setVariablename, an expressionset a variable
setVariablesvariables: a list of {name, <expression>}set several variables
removeHeadera name, or nameremove one header
removeHeadersa pattern, or pattern and excludePattern (list or comma separated)remove matching headers (* wildcards or a regex)
removeProperty, removePropertieslike the header stepsremove exchange properties
removeVariablea name, or nameremove a variable
convertBodyToa type, or type, mandatory (default false)convert the body: String, Integer, Long, Double, Boolean, byte[], Java names like java.lang.String
convertHeaderToname, type, mandatoryconvert a header
marshal, unmarshala data format: json: {}, csv: {delimiter: ";"}, custom: {ref: name}, or a plain name (- marshal: json)object to text/bytes and back; see data formats
transformDataTypetoType, fromTypeapply a registered data type transformer
scriptan expressionrun a script; its result is discarded, its changes to the message are kept
loga Simple message, or message, loggingLevel (default INFO), logName, markerlog a line
setExchangePatternInOnly or InOut (or pattern)change the exchange pattern
claimCheckoperation (Set, Get, GetAndRemove, Push, Pop), key, filterstore part of the message and get it back later
sortan expression, comparatorsort a list body
yaml example: yaml-message-steps
- 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
yaml example: yaml-message-more
- 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:sorted

Routing

stepkeysdescription
filtera predicate, stepsrun the steps only for matching messages; others continue after the filter
choicewhen (list of {<predicate>, steps}), otherwise ({steps} or a list), preconditionthe first matching when runs; otherwise otherwise
multicaststeps, parallelProcessing, aggregationStrategy, stopOnException, streaming, timeout, shareUnitOfWorksend a copy of the message to each step
recipientListan expression, delimiter (default ,), ignoreInvalidEndpoints, plus the multicast keyssend to the endpoints the expression returns
routingSlipan expression, uriDelimiter (default ,), ignoreInvalidEndpointssend through the endpoints one after another
dynamicRouteran expression, uriDelimiter, ignoreInvalidEndpointsask the expression for the next endpoint until it returns nothing
loadBalanceone policy (roundRobin, random, topic, sticky: {correlationExpression}, weighted: {distributionRatio, roundRobin}, failover: {exception, maximumFailoverAttempts, roundRobin, sticky}, custom: {ref}), stepssend each message to one of the steps (default roundRobin)
loopan expression (a count, or a predicate with doWhile: true), copy (default false), stepsrepeat the steps; CamelLoopIndex holds the index
pipelinestepsrun steps in sequence (useful inside multicast)
stepsteps, idgroup steps under one id for statistics
stopnonestop routing this message
yaml example: yaml-choice
- 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
yaml example: yaml-multicast-loop
- 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
yaml example: yaml-dynamic-routing
- 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

stepkeysdescription
splitan 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
aggregatecorrelationExpression (required), aggregationStrategy (required), completionSize, completionTimeout, completionInterval, completionPredicate, completionSizeExpression, completionTimeoutExpression, completionFromBatchConsumer, completionOnNewCorrelationGroup, eagerCheckCompletion, forceCompletionOnStop, discardOnCompletionTimeout, ignoreInvalidCorrelationKeys, closeCorrelationKeyOnCompletion, aggregationRepository, stepscombine related messages into one
resequencean expression, batchConfig: {batchSize: 100, batchTimeout: 1000, allowDuplicates, reverse, ignoreInvalidExchanges} or streamConfig: {capacity: 1000, timeout: 1000, rejectOld, ignoreInvalidExchanges}, stepsput 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.

yaml example: yaml-split-aggregate
- 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
yaml example: yaml-resequence
- route:
    from:
      uri: direct:out-of-order
      steps:
        - resequence:
            header: seq
            batchConfig:
              batchSize: 3
              batchTimeout: 200
            steps:
              - to: mock:in-order

Error handling

stepkeysdescription
doTrysteps, doCatch (list of {exception: [...], onWhen, steps}), doFinally ({steps})try/catch/finally inside a route
onExceptionexception (list of kinds), handled, continued, onWhen, retryWhile, redeliveryPolicy, redeliveryPolicyRef, useOriginalMessage, useOriginalBody, onRedeliveryRef, onExceptionOccurredRef, stepsreact to a kind of failure; as a route step it applies to that route, in a routeConfiguration to many
onCompletiononCompleteOnly, onFailureOnly, mode (AfterConsumer default, BeforeConsumer), onWhen, stepsrun steps when the exchange finishes
circuitBreakersteps, 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
throwExceptionexceptionType (required), messagefail the exchange with that kind of exception
validatea predicatefail with PredicateValidationException when it is false
rollbacka message, or message, markRollbackOnly, markRollbackOnlyLastroll back the current transaction
transacteda policy name, or ref; stepsrun in a transaction (without steps: the rest of the route)
policyref, stepswrap steps in a registered policy (without steps: the rest of the route)
sagapropagation, completionMode, timeout, compensation, completion, option (list of {key, <expression>}), sagaService, stepsa long-running transaction with compensation
intercept, interceptFrom, interceptSendToEndpointonWhen, steps; interceptFrom takes uri (default *); interceptSendToEndpoint takes uri (required) and skipSendToOriginalEndpointrun 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.

yaml example: yaml-error-steps
- 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-done

Interceptors and completions are usually written once, at top level or in a routeConfiguration, and apply to every route without routeConfigurationId:

yaml example: yaml-intercept
- 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 purpose

Flow control

stepkeysdescription
throttlea constant expression or maximumRequests, timePeriodMillis (default 1000), correlationExpression, rejectExecution, asyncDelayedat most N messages per period
delayan expression (milliseconds), asyncDelayed (default true)wait before continuing
samplea period, or samplePeriod (default 1 s) or messageFrequencylet one message through per period or per N messages
idempotentConsumeran expression (the message id), steps, idempotentRepository (default: in memory, 1000 ids), eager (true), skipDuplicate (true), removeOnFailure (true), completionEager (false)process each id once
threadspoolSize, maxPoolSize, keepAliveTime (+ timeUnit, default seconds), maxQueueSize, allowCoreThreadTimeOut, rejectedPolicy, callerRunsWhenRejected, threadName, executorService, stepscontinue on a thread pool (without steps: the rest of the list)
yaml example: yaml-flow-control
- route:
    from:
      uri: direct:events
      steps:
        - idempotentConsumer:
            header: eventId
            steps:
              - throttle:
                  constant: 100
                  timePeriodMillis: 1000
              - delay:
                  constant: 10
              - to: mock:unique-events

Values: booleans, numbers and durations

  • Booleans are true and false (any case). YAML 1.2 is used, so yes, no, on and off are strings: a boolean option set to yes fails with 'yes' is not a boolean, and disabled: yes does 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 the timeout of poll, which takes whole milliseconds only. waitDurationInOpenState of resilience4jConfiguration and keepAliveTime of threads are in seconds, as in Camel.
  • Endpoint option values are text: period: 1000 and period: "1000" are the same.

Ids and descriptions

  • Route ids: id on the route; a missing id becomes route1, route2, ... Ids must be unique in the context, across all files.
  • Step ids: id on any step, when or otherwise. With prefixId, a templatedRoute adds a prefix to each.
  • description on 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:

text
- 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:

text
- to: mock:a
  pattern: InOut       # wrong: belongs under `to`
- to:
    uri: mock:a
    pattern: InOut     # right

Unquoted special characters. These need quotes:

valuewhywrite
log: Status: ${body}: starts a mappinglog: "Status: ${body}"
simple: Order #${body} # starts a commentsimple: "Order #${body}"
simple: {"id": 1}{ starts a flow mappingsimple: '{"id": 1}'
uri: {{broker}}{{ starts a flow mappinguri: "{{broker}}"
constant: 007a number (7)constant: "007"
constant: truea boolean, not textconstant: "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 with from but without the route: wrapper.
  • Beans of Java classes are not instantiated (see beans); processors and strategies are bound from Rust.
  • throttle needs a constant maximum; interceptSendToEndpoint afterUri is ignored with a warning; dataFormats at top level is stored but not used; unknown step keys are ignored (Camel's schema rejects them); cameleer validate reports them.
  • The loader uses YAML 1.2 (like Camel's snakeyaml-engine), so yes/no are 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.

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.

Edit this page on GitHub