YAML DSL
Since Camel 3.9
The YAML DSL provides the capability to define your Camel routes, route templates & REST DSL configuration in YAML.
Defining a route
A route is collection of elements defined as follows:
- from: (1)
uri: "direct:start"
steps: (2)
- filter:
expression:
simple: "${in.header.continue} == true"
steps: (2)
- to:
uri: "log:filtered"
- to:
uri: "log:original" | 1 | route entry point, by default from and rest are supported |
| 2 | processing steps |
| Each step is represented by a YAML map that has a single entry where the field name is the EIP name |
As a general rule, each step provides all the parameters the related definition declares, but there are some minor differences/enhancements:
-
Output Aware Steps
Some steps such as
filterandsplithave their own pipeline. When an exchange matches the filter expression or for the items generated by the split expression, such a pipeline can be defined by thestepsfield:filter: expression: simple: "${in.header.continue} == true" steps: - to: uri: "log:filtered" -
Expression Aware Steps
Some EIPs such as
filterandsplitsupport the definition of an expression through theexpressionfield:Explicit Expression fieldfilter: expression: simple: "${in.header.continue} == true"To make the DSL less verbose, the
expressionfield can be omitted:Implicit Expression fieldfilter: simple: "${in.header.continue} == true"In general,
expressioncan be defined inline like in the examples above. But in case you need to provide more information, you can 'unroll' the expression definition and configure any single parameter the expression defines.Full Expression definitionfilter: tokenize: token: "<" endToken: ">" -
Data Format Aware Steps
The EIP
marshalandunmarshalsupports the definition of data formats:marshal: json: library: GsonIn case you want to use the data-format’s default settings, you need to place an empty block as data format parameters, like
json: {}
Extending YAML route steps
Optional Camel modules can contribute YAML route step deserializers without changing camel-yaml-dsl. This is useful when a module provides a custom route model definition, or wants to expose a short YAML step name that maps to a normal Camel route model definition.
To contribute custom step deserializers, add a resource named:
META-INF/services/org/apache/camel/YamlDeserializerResolver The resource contains one org.apache.camel.dsl.yaml.common.YamlDeserializerResolver implementation class name per line. Blank lines and comments starting with # are ignored. When packaging an application as an uber JAR or shaded JAR, merge this service resource from all contributing modules.
com.acme.camel.MyYamlStepResolver The resolver returns a SnakeYAML ConstructNode for the step names, or model class names, it supports. The deserializer should create and configure the normal Camel model object that the custom YAML step represents:
This SPI only controls YAML deserialization. It does not add route execution behavior. The returned object must be a Camel route model definition that Camel can already execute through existing model reifier or ProcessorFactory support, or the optional module must provide that runtime support using the normal Camel extension mechanisms.
import org.apache.camel.dsl.yaml.common.YamlDeserializerBase;
import org.apache.camel.dsl.yaml.common.YamlDeserializerResolver;
import org.apache.camel.model.StepDefinition;
import org.snakeyaml.engine.v2.api.ConstructNode;
import org.snakeyaml.engine.v2.nodes.Node;
import static org.apache.camel.dsl.yaml.common.YamlDeserializerSupport.asText;
public final class MyYamlStepResolver implements YamlDeserializerResolver {
@Override
public int getOrder() {
return YamlDeserializerResolver.ORDER_DEFAULT + 1;
}
@Override
public ConstructNode resolve(String id) {
if ("myStep".equals(id)) {
return new MyStepDeserializer();
}
return null;
}
}
final class MyStepDeserializer extends YamlDeserializerBase<StepDefinition> {
public MyStepDeserializer() {
super(StepDefinition.class);
}
@Override
protected StepDefinition newInstance() {
return new StepDefinition();
}
@Override
protected boolean setProperty(StepDefinition target, String propertyKey, String propertyName, Node value) {
switch (propertyKey) {
case "id":
target.setId(asText(value));
return true;
case "steps":
setSteps(target, value);
return true;
default:
return false;
}
}
} This allows YAML such as:
- from:
uri: "direct:start"
steps:
- myStep:
id: "custom-step"
steps:
- to:
uri: "mock:result" Resolvers are discovered when YAML routes are parsed. Camel starts with the built-in YAML DSL resolvers, then adds the resolvers returned by the active YamlDeserializerResolverProvider and any YamlDeserializerResolver beans found in the Camel registry. When no custom provider is registered as a Camel context plugin, the default provider discovers resolver class names from META-INF/services/org/apache/camel/YamlDeserializerResolver. It looks for resolver resources through the Camel class resolver, the application context classloader, and any classloaders registered with the Camel class resolver. Runtime integrations that perform build-time discovery, such as native-image runtimes, can register their own YamlDeserializerResolverProvider as a Camel context plugin to supply the automatically available resolvers instead of using the default runtime classpath resource scanning provider.
All resolvers are ordered by YamlDeserializerResolver#getOrder(), where lower order values have higher precedence. Built-in YAML DSL resolvers win over provider-discovered and registry-provided resolvers unless the external resolver uses an order lower than YamlDeserializerResolver.ORDER_DEFAULT. For external resolvers with the same order, registry-provided resolvers are tried before provider-discovered resolvers. Within the same source and order, resolvers are ordered by fully qualified class name, and registry resolvers use the registry bean name as a final tie-breaker. If several resolvers can resolve the same step name, the first matching resolver wins.
The built-in resolver for hand-written YAML DSL conveniences uses YamlDeserializerResolver.ORDER_DEFAULT, and the generated model resolver is also treated as a built-in resolver. Use an order lower than ORDER_DEFAULT only when a custom resolver intentionally needs to override any built-in YAML name.
Resolver classes found through the default provider are instantiated through Camel’s injector. If a provider-discovered or registry-provided resolver implements CamelContextAware, Camel sets the current CamelContext before the resolver is used. Resolver discovery and resolver execution are fail-fast: a broken service entry, provider error, or resolver exception aborts YAML route loading instead of being silently skipped. Resolver implementations should be lightweight and should not rely on Camel Service lifecycle callbacks from the YAML deserialization context. Registry resolvers are owned by the registry, and custom providers own the lifecycle of any resolver instances they return. The same discovery mechanism is used by the YAML routes loader and the Kamelet routes loader, so custom route steps can be used in regular YAML routes and in Kamelet template step lists.
When a custom step maps to a model definition that supports child outputs, the deserializer can parse nested YAML steps using YamlDeserializerSupport.setSteps. Nested steps are only meaningful for model definitions that can contain outputs, such as Block implementations. If the optional module that provides the resolver is not on the classpath, Camel does not load that resolver. YAML files that use the custom step name then fail with an unknown node error; YAML files that do not use the custom step are unaffected. If the service file names a resolver class that cannot be loaded, YAML route parsing fails when resolver discovery runs.
Custom YAML step names are runtime extensions. The generated YAML DSL JSON schemas only describe the built-in DSL and do not validate module-provided step names. This also applies to IDE validation and schema-based tooling, including the YAML DSL validator Maven plugin. Runtime route-loading parsers can use custom resolvers that are available on their classpath, but schema validation remains limited to the generated built-in DSL schema. Modules that provide custom YAML steps should include route-loading tests that put the resolver service resource on the test classpath, load a YAML route using the custom step, and assert that Camel created the expected model definition or that the route executes as expected. For output-aware custom steps, include at least one nested steps entry.
YAML routes and resolver providers are trusted route-author and deployment inputs under Camel’s security model. This SPI is not a sandbox for processing untrusted YAML documents. Resolver implementations should not fetch external resources, load classes named by route fields, or evaluate route fields as expressions while parsing YAML.
Defining endpoints
To define an endpoint with the YAML dsl you have two options:
-
Using a classic Camel URI:
- from: uri: "timer:tick?period=1s" steps: - to: uri: "telegram:bots?authorizationToken=XXX" -
Using URI and parameters:
- from: uri: "timer://tick" parameters: period: "1s" steps: - to: uri: "telegram:bots" parameters: authorizationToken: "XXX"
Map values for parameters
Available as of Camel 4.15
It is now possible to inline Maps in the parameters section. However Camel components rarely have options that are Map based, but when they do this makes it easier to use. For example the plc4x component allow to configure tags as a Map:
- from:
uri: "timer:tick"
parameters:
period: "1000"
steps:
- to:
uri: "plc4x"
parameters:
driver: "some driver url here"
tags:
"tags_2": "XXX"
"tags_6": "YYY" In this example the tags options is of Map type and can be configured using YAML map syntax. Because the keys use underscore, then they are quoted.
To use map values for parameters, then the uri must only refer to the name of the component, ie plc4x and not plc4x:foo |
Defining beans
In addition to the general support for creating beans provided by Camel Main, the YAML DSL provides a convenient syntax to define and configure them:
- beans:
- name: beanFromMap (1)
type: com.acme.MyBean (2)
properties: (3)
foo: bar | 1 | the name of the bean which will be used to bound the instance to the Camel Registry |
| 2 | the full qualified class name of the bean |
| 3 | the properties of the bean to be set |
The properties of the bean can be defined using either a map or properties style, as shown in the example below:
- beans:
# map style
- name: beanFromMap
type: com.acme.MyBean
properties:
field1: 'f1'
field2: 'f2'
nested:
field1: 'nf1'
field2: 'nf2'
# properties style
- name: beanFromProps
type: com.acme.MyBean
properties:
field1: 'f1_p'
field2: 'f2_p'
nested.field1: 'nf1_p'
nested.field2: 'nf2_p' | The |
Creating bean using constructors
When beans must be created with constructor arguments, then this is made easier in Camel 4.1 onwards.
For example as shown below:
- beans:
- name: myBean
type: com.acme.MyBean
constructors:
0: true
1: "Hello World" The constructors is index based so the keys must be numbers starting from zero.
| You can use both constructors and properties. |
Creating beans from factory method
A bean can also be created from a factory method (public static) as shown below:
- beans:
- name: myBean
type: com.acme.MyBean
factoryMethod: createMyBean
constructors:
0: true
1: "Hello World" When using factoryMethod then the arguments to this method is taken from constructors. So in the example above, this means that class com.acme.MyBean should be as follows:
public class MyBean {
public static MyBean createMyBean(boolean important, String message) {
MyBean answer = ...
// create and configure the bean
return answer;
}
} The factory method must be public static and from the same class as the created class itself. |
Creating beans from factory bean
A bean can also be created from a factory bean as shown below:
- beans:
- name: myBean
type: com.acme.MyBean
factoryBean: com.acme.MyHelper
factoryMethod: createMyBean
constructors:
0: true
1: "Hello World" factoryBean can also refer to an existing bean by bean id instead of FQN classname. |
When using factoryBean and factoryMethod then the arguments to this method is taken from constructors. So in the example above, this means that class com.acme.MyHelper should be as follows:
public class MyHelper {
public static MyBean createMyBean(boolean important, String message) {
MyBean answer = ...
// create and configure the bean
return answer;
}
} The factory method must be public static. |
Creating beans from builder classes
A bean can also be created from another builder class as shown below:
- beans:
- name: myBean
type: com.acme.MyBean
builderClass: com.acme.MyBeanBuilder
builderMethod: createMyBean
properties:
id: 123
name: 'Acme' The builder class must be public and have a no-arg default constructor. |
The builder class is then used to create the actual bean by using fluent builder style configuration. So the properties will be set on the builder class, and the bean is created by invoking the builderMethod at the end. The invocation of this method is done via Java reflection.
Creating beans using script language
For advanced use-cases then Camel allows to inline a script language, such as groovy, java, javascript, etc, to create the bean. This gives flexibility to use a bit of programming to create and configure the bean.
- beans:
- name: myBean
type: com.acme.MyBean
scriptLanguage: groovy
script: >
// some groovy script here to create the bean
bean = ...
...
return bean When using script then constructors and factory bean/method is not in use |
You can refer to property placeholder values using {{ }} syntax inside the script, such as:
- beans:
- name: myBean
type: com.acme.MyBean
scriptLanguage: groovy
script: >
// some groovy script here to create the bean
bean = ...
bean.street = '{{addressLine1}}`
bean.street2 = '{{addressLine2}}`
...
return bean If there is some problems with using {{ }} inside the script, then this can be turned off by setting scriptPropertyPlaceholders: false. Instead, you can invoke the APIs on CamelContext such as:
- beans:
- name: myBean
type: com.acme.MyBean
scriptLanguage: groovy
scriptPropertyPlaceholders: false
script: >
// some groovy script here to create the bean
bean = ...
bean.street = context.resolvePropertyPlaceholders('addressLine1')
bean.street2 = context.resolvePropertyPlaceholders('addressLine2')
...
return bean Using init and destroy methods on beans
Sometimes beans need to do some initialization and cleanup work before a bean is ready to be used. For this you can use initMethod and destroyMethod that Camel triggers accordingly.
Those methods must be public void and have no arguments, as shown below:
public class MyBean {
public void initMe() {
// do init work here
}
public void destroyMe() {
// do cleanup work here
}
} You then have to declare those methods in YAML DSL as follows:
- beans:
- name: myBean
type: com.acme.MyBean
initMethod: initMe
destroyMethod: destroyMe
constructors:
0: true
1: "Hello World" The init and destroy methods are optional, so a bean does not have to have both, for example you may only have destroy methods.
Configuring options on languages
Some Languages have additional configurations you may need to use.
For example, the JSONPath can be configured to ignore JSon parsing errors. This is intended when you use a Content Based Router and want to route the message to different endpoints. But the JSon payload of the message can be in different forms; meaning that the JSonPath expressions in some cases would fail with an exception, and other times not. In this situation, you need to set suppress-exception to true, as shown below:
- from:
uri: "direct:start"
steps:
- choice:
when:
- jsonpath:
expression: "person.middlename"
suppressExceptions: true
steps:
- to:
uri: mock:middle
- jsonpath:
expression: "person.lastname"
suppressExceptions: true
steps:
- to:
uri: mock:last
otherwise:
steps:
- to:
uri: mock:other In the route above, the following message
{
"person": {
"firstname": "John",
"lastname": "Doe"
}
} Would have failed the JSonPath expression person.middlename because the JSon payload does not have a middlename field. To remedy this we have suppressed the exception.
Schema Variants
The YAML DSL provides two JSON Schema variants for validation:
Classic Schema (camelYamlDsl.json)
The default schema supports several convenient shorthands to make YAML routes more concise:
-
String shorthands: Some EIPs like
logaccept a plain string instead of an object. For example,log: "${body}"is equivalent tolog: { message: "${body}" }. -
Implicit expressions: Expression-aware EIPs like
filter,setBody, andsplitallow the expression language to be specified directly without theexpressionwrapper. For example,setBody: { simple: "Hello" }is equivalent tosetBody: { expression: { simple: { expression: "Hello" } } }.
While these shorthands make hand-written YAML more concise, they also increase the complexity of the JSON Schema with oneOf, anyOf, and not constructs. This can cause problems for tooling such as IDE auto-completion, schema-based code generators, and AI assistants that struggle with these patterns.
Canonical Schema (camelYamlDsl-canonical.json)
The canonical schema removes all implicit patterns:
-
No string shorthands: EIPs like
logmust use the object form with explicit properties. -
No implicit expressions: Expression-aware EIPs require the
expressionwrapper. -
No
oneOf/anyOf/notconstructs: The schema uses only simpletype: objectwithproperties.
This results in a schema that is approximately 25% smaller and significantly easier for tooling to process.
Example: setBody with expression
-
Classic
-
Canonical
- setBody:
simple: "Hello Camel from ${routeId}" - setBody:
expression:
simple:
expression: "Hello Camel from ${routeId}" Example: setHeader with constant
-
Classic
-
Canonical
- setHeader:
name: Content-Type
constant: "application/json" - setHeader:
name: Content-Type
expression:
constant:
expression: "application/json" Example: choice with when
-
Classic
-
Canonical
- choice:
when:
- simple: "${body} == 'Hello'"
steps:
- setProperty:
name: myProp
simple: "${body}"
otherwise:
steps:
- setProperty:
name: myProp
constant: "default" - choice:
when:
- expression:
simple:
expression: "${body} == 'Hello'"
steps:
- setProperty:
name: myProp
expression:
simple:
expression: "${body}"
otherwise:
steps:
- setProperty:
name: myProp
expression:
constant:
expression: "default" Example: complete route
-
Classic
-
Canonical
- route:
from:
uri: timer:yaml
steps:
- setBody:
simple: "Hello Camel from ${routeId}"
- log: "${body}" - route:
from:
uri: timer:yaml
steps:
- setBody:
expression:
simple:
expression: "Hello Camel from ${routeId}"
- log:
message: "${body}" Both forms are accepted by the Camel runtime. The classic schema validates both forms, while the canonical schema only validates the explicit form. In other words, the canonical form is a strict subset of the classic form: any YAML file that validates against the canonical schema will also validate against the classic schema and work with the Camel runtime.
The canonical schema is intended for use by tooling (IDEs, code generators, AI assistants) that benefits from a simpler, more predictable schema structure. The classic schema remains the default for human-authored YAML.
Using the schemas with IDEs
The classic schema is registered in the JSON Schema Store and is automatically used by IDEs like VS Code and IntelliJ for files matching .camel.yaml and .camelk.yaml.
To use the canonical schema instead, configure your IDE to point to the canonical schema URL:
https://raw.githubusercontent.com/apache/camel/main/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-canonical.json
Or reference the schema locally from the camel-yaml-dsl JAR at /schema/camelYamlDsl-canonical.json.
Normalizing YAML routes
To convert existing YAML routes from the classic (shorthand) form to the canonical (explicit) form, use the Camel CLI normalize command:
camel validate normalize myroute.yaml This parses the YAML routes and rewrites them in canonical form, expanding all shorthands and implicit expressions. The output is printed to the console by default, or can be written to a file or directory using the --output option:
camel validate normalize --output normalized/ myroute.yaml Compact notation warning
Camel logs a WARN message when YAML routes use compact (shorthand) notation instead of the canonical (explicit) form. This is to encourage adopting the canonical style which is more friendly for tooling and AI assistants.
The warning is logged once per resource file and looks like:
YAML DSL compact notation detected in: myroute.yaml. It is recommended to use canonical/normalized YAML DSL notation which is more tooling and AI friendly. Use Camel CLI to normalize: camel validate normalize <file>
To disable this warning, set the following property in application.properties:
camel.main.yamlDslCompactNotationWarn = false Validating with the canonical schema
To validate YAML routes against the canonical schema:
camel validate yaml --canonical myroute.yaml This reports any shorthands or implicit forms that are not valid in the canonical schema.
The YamlValidator class supports both schemas programmatically:
// Classic validation (default)
YamlValidator validator = new YamlValidator();
// Canonical validation
YamlValidator canonicalValidator = new YamlValidator(true); External examples
You can find a set of examples using main-yaml in Camel Examples which demonstrate creating Camel Routes with YAML.
Another way to find examples of YAML DSL is to look in Camel Kamelets where each Kamelet is defined using YAML.