REST DSL

Apache Camel offers a REST styled DSL.

The intention is to allow end users to define REST services (hosted by Camel) using a REST style with verbs such as get, post, delete etc.

How it works

The Rest DSL is a facade that builds Rest endpoints as consumers for Camel routes. The actual REST transport is leveraged by using Camel REST components such as Netty HTTP, Servlet, and others that has native REST integration.

Components supporting Rest DSL

The following Camel components supports the Rest DSL:

Rest DSL with Java DSL

To use the Rest DSL in Java DSL then just do as with regular Camel routes by extending the RouteBuilder and define the routes in the configure method.

A simple REST service can be defined as follows, where we use rest() to define the services as shown below:

@Override
public void configure() throws Exception {
    rest("/say")
        .get("/hello").to("direct:hello")
        .get("/bye").consumes("application/json").to("direct:bye")
        .post("/bye").to("mock:update");

    from("direct:hello")
        .transform().constant("Hello World");

    from("direct:bye")
        .transform().constant("Bye World");
}

This defines a REST service with the following url mappings:

Base Path Uri template Verb Consumes

/say

/hello

get

all

/say

/bye

get

application/json

/say

/bye

post

all

Notice that in the REST service we route directly to a Camel endpoint using to(). This is because the Rest DSL has a short-hand for routing directly to an endpoint using to().

Rest DSL with XML DSL

The example above can be defined in XML as shown below:

<camelContext xmlns="http://camel.apache.org/schema/spring">
  <rest path="/say">
    <get uri="/hello">
      <to uri="direct:hello"/>
    </get>
    <get uri="/bye" consumes="application/json">
      <to uri="direct:bye"/>
    </get>
    <post uri="/bye">
      <to uri="mock:update"/>
    </post>
  </rest>
  <route>
    <from uri="direct:hello"/>
    <transform>
      <constant>Hello World</constant>
    </transform>
  </route>
  <route>
    <from uri="direct:bye"/>
    <transform>
      <constant>Bye World</constant>
    </transform>
  </route>
</camelContext>

Using base path

The REST DSL allows defining base path to make the DSL a bit more DRY. For example to define a customer path, we can set the base path in rest("/customer") and then provide the uri templates in the verbs, as shown below:

rest("/customers/")
    .get("/{id}").to("direct:customerDetail")
    .get("/{id}/orders").to("direct:customerOrders")
    .post("/neworder").to("direct:customerNewOrder");

And using XML DSL it becomes:

<rest path="/customers/">
  <get uri="/{id}">
    <to uri="direct:customerDetail"/>
  </get>
  <get uri="/{id}/orders">
    <to uri="direct:customerOrders"/>
  </get>
  <post uri="/neworder">
    <to uri="direct:customerNewOrder"/>
  </post>
</rest>
The REST DSL will take care of duplicate path separators when using base path and uri templates. In the example above the rest base path ends with a slash / and the verb starts with a slash /. Camel will take care of this and remove the duplicated slash.

It is not required to use both base path and uri templates. You can omit the base path and define the base path and uri template in the verbs only. The example above can be defined as:

<rest>
  <get uri="/customers/{id}">
    <to uri="direct:customerDetail"/>
  </get>
  <get uri="/customers/{id}/orders">
    <to uri="direct:customerOrders"/>
  </get>
  <post uri="/customers/neworder">
    <to uri="direct:customerNewOrder"/>
  </post>
</rest>

You can combine path parameters to build complex expressions. For example:

 rest("items/")
     .get("{id}/{filename}.{content-type}")
     .to("direct:item")

Using Dynamic To in Rest DSL

The Rest DSL supports Dynamic To EIP (toD) to in the rest-dsl. For example to do a Request Reply over JMS where the queue name is dynamic defined

public void configure() throws Exception {
   rest("/say")
     .get("/hello/{language}").toD("jms:queue:hello-${header.language}");
}

And in XML DSL

<rest uri="/say">
  <get uri="/hello//{language}">
    <toD uri="jms:queue:hello-${header.language}"/>
  </get>
</rest>

Managing Rest services

Each of the rest services becomes a Camel route, so in the first example we have 2 x get and 1 x post REST service, which each become a Camel route. This makes it the same from Camel to manage and run these services - as they are just Camel routes. This means any tooling and API today that deals with Camel routes, also work with the REST services.

To use JMX with Camel then camel-management JAR must be included in the classpath.

This means you can use JMX to stop/start routes, and also get the JMX metrics about the routes, such as number of message processed, and their performance statistics.

There is also a Rest Registry JMX MBean that contains a registry of all REST services which has been defined.

Binding to POJOs using

The Rest DSL supports automatic binding json/xml contents to/from POJOs using Camels Data Format. By default, the binding mode is off, meaning there is no automatic binding happening for incoming and outgoing messages.

You may want to use binding if you develop POJOs that maps to your REST services request and response types. This allows you as a developer to work with the POJOs in Java code.

The binding modes are:

Binding Mode Description

off

Binding is turned off. This is the default option.

auto

Binding is enabled and Camel is relaxed and support json, xml or both if the needed data formats are included in the classpath. Notice that if for example camel-jaxb is not on the classpath, then XML binding is not enabled.

json

Binding to/from json is enabled, and requires a json capable data format on the classpath. By default Camel will use jackson as the data format. See the INFO box below for more details.

xml

Binding to/from xml is enabled, and requires camel-jaxb on the classpath. See the INFO box below for more details.

json_xml

Binding to/from json and xml is enabled and requires both data formats to be on the classpath. See the INFO box below for more details.

When using camel-jaxb for xml bindings, then you can use the option mustBeJAXBElement to relax the output message body must be a class with JAXB annotations. You can use this in situations where the message body is already in XML format, and you want to use the message body as-is as the output type. If that is the case, then set the dataFormatProperty option mustBeJAXBElement to false value.

The binding from POJO to JSon/JAXB will only happen if the content-type header includes the word json or xml representatively. This allows you to specify a custom content-type if the message body should not attempt to be marshalled using the binding. For example if the message body is a custom binary payload etc.

When automatic binding from POJO to JSon/JAXB takes place the existing content-type header will by default be replaced with either application/json or application/xml. To disable the default behavior and be able to produce JSon/JAXB responses with custom content-type headers (e.g. application/user.v2+json) you configure this in Java DSL as shown below:

restConfiguration().dataFormatProperty("contentTypeHeader", "false");

To use binding you must include the necessary data formats on the classpath, such as camel-jaxb and/or camel-jackson. And then enable the binding mode. You can configure the binding mode globally on the rest configuration, and then override per rest service as well.

To enable binding you configure this in Java DSL as shown below:

restConfiguration().component("netty-http").host("localhost").port(portNum).bindingMode(RestBindingMode.auto);

And in XML DSL:

<restConfiguration bindingMode="auto" component="netty-http" port="8080"/>

When binding is enabled Camel will bind the incoming and outgoing messages automatic, accordingly to the content type of the message. If the message is json, then json binding happens; and so if the message is xml then xml binding happens. The binding happens for incoming and reply messages. The table below summaries what binding occurs for incoming and reply messages.

Message Body Direction Binding Mode Message Body

XML

Incoming

auto, xml, json_xml

POJO

POJO

Outgoing

auto, xml, json_xml

XML

JSON

Incoming

auto, json, json_xml

POJO

POJO

Outgoing

auto, json, json_xml

JSON

When using binding you must also configure what POJO type to map to. This is mandatory for incoming messages, and optional for outgoing.

For example to map from xml/json to a pojo class UserPojo you do this in Java DSL as shown below:

// configure to use netty-http on localhost with the given port
// and enable auto binding mode
restConfiguration().component("netty-http").host("localhost").port(portNum).bindingMode(RestBindingMode.auto);

// use the rest DSL to define the rest services
rest("/users/")
    .post().type(UserPojo.class)
        .to("direct:newUser");

Notice we use type to define the incoming type. We can optionally define an outgoing type (which can be a good idea, to make it known from the DSL and also for tooling and JMX APIs to know both the incoming and outgoing types of the REST services). To define the outgoing type, we use outType as shown below:

// configure to use netty-http on localhost with the given port
// and enable auto binding mode
restConfiguration().component("netty-http").host("localhost").port(portNum).bindingMode(RestBindingMode.auto);

// use the rest DSL to define the rest services
rest("/users/")
    .post().type(UserPojo.class).outType(CountryPojo.class)
        .to("direct:newUser");

And in XML DSL:

<rest uri="/users/">
  <post type="UserPojo" outType="CountryPojo">
    <to uri="direct:newUser"/>
  </post>
</rest>

To specify input and/or output using an array, append [] to the end of the canonical class name as shown in the following Java DSL:

// configure to use netty-http on localhost with the given port
// and enable auto binding mode
restConfiguration().component("netty-http").host("localhost").port(portNum).bindingMode(RestBindingMode.auto);

// use the rest DSL to define the rest services
rest("/users/")
    .post().type(UserPojo[].class).outType(CountryPojo[].class)
        .to("direct:newUser");

The UserPojo is just a plain pojo with getter/setter as shown:

public class UserPojo {
    private int id;
    private String name;
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

The UserPojo only supports json, as XML requires to use JAXB annotations, so we can add those annotations if we want to support XML also

@XmlRootElement(name = "user")
@XmlAccessorType(XmlAccessType.FIELD)
public class UserPojo {
    @XmlAttribute
    private int id;
    @XmlAttribute
    private String name;
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

By having the JAXB annotations the POJO supports both json and xml bindings.

Camel Rest-DSL configurations

The Rest DSL supports the following options:

Name Description Default Type

apiComponent

Sets the name of the Camel component to use as the REST API (such as swagger or openapi)

String

apiContextIdPattern

Optional CamelContext id pattern to only allow Rest APIs from rest services within CamelContext’s which name matches the pattern. The pattern name refers to the CamelContext name, to match on the current CamelContext only. For any other value, the pattern uses the rules from org.apache.camel.support.EndpointHelper#matchPattern(String,String)

String

apiContextPath

Sets a leading API context-path the REST API services will be using. This can be used when using components such as camel-servlet where the deployed web application is deployed using a context-path.

String

apiHost

To use a specific hostname for the API documentation (such as swagger or openapi) This can be used to override the generated host with this configured hostname

String

apiProperties

Sets additional options on api level

Map

apiVendorExtension

Whether vendor extension is enabled in the Rest APIs. If enabled then Camel will include additional information as vendor extension (eg keys starting with x-) such as route ids, class names etc. Not all 3rd party API gateways and tools supports vendor-extensions when importing your API docs.

false

boolean

bindingMode

Sets the binding mode to be used by the REST consumer

RestBindingMode.off

RestBindingMode

clientRequestValidation

Whether to enable validation of the client request to check whether the Content-Type and Accept headers from the client is supported by the Rest-DSL configuration of its consumes/produces settings. This can be turned on, to enable this check. In case of validation error, then HTTP Status codes 415 or 406 is returned. The default value is false.

false

boolean

component

Sets the name of the Camel component to use as the REST consumer

String

componentProperties

Sets additional options on component level

Map

consumerProperties

Sets additional options on consumer level

Map

contextPath

Sets a leading context-path the REST services will be using. This can be used when using components such as camel-servlet where the deployed web application is deployed using a context-path. Or for components such as camel-jetty or camel-netty-http that includes a HTTP server.

String

corsHeaders

Sets the CORS headers to use if CORS has been enabled.

Map

dataFormatProperties

Sets additional options on data format level

Map

enableCORS

To specify whether to enable CORS which means Camel will automatic include CORS in the HTTP headers in the response. This option is default false

false

boolean

endpointProperties

Sets additional options on endpoint level

Map

host

Sets the hostname to use by the REST consumer

String

hostNameResolver

Sets the resolver to use for resolving hostname

RestHostNameResolver.allLocalIp

RestHostNameResolver

jsonDataFormat

Sets a custom json data format to be used Important: This option is only for setting a custom name of the data format, not to refer to an existing data format instance.

String

port

Sets the port to use by the REST consumer

int

producerApiDoc

Sets the location of the api document (swagger api) the REST producer will use to validate the REST uri and query parameters are valid accordingly to the api document. This requires adding camel-swagger-java to the classpath, and any miss configuration will let Camel fail on startup and report the error(s). The location of the api document is loaded from classpath by default, but you can use file: or http: to refer to resources to load from file or http url.

String

producerComponent

Sets the name of the Camel component to use as the REST producer

String

scheme

Sets the scheme to use by the REST consumer

String

skipBindingOnErrorCode

Whether to skip binding output if there is a custom HTTP error code, and instead use the response body as-is. This option is default true.

true

boolean

useXForwardHeaders

Whether to use X-Forward headers to set host etc. for Swagger. This option is default true.

true

boolean

xmlDataFormat

Sets a custom xml data format to be used. Important: This option is only for setting a custom name of the data format, not to refer to an existing data format instance.

String

For example to configure to use the jetty component on port 9091, then we can do as follows:

restConfiguration().component("jetty").port(9091).componentProperty("foo", "123");

And with XML DSL:

<restConfiguration component="jetty" port="9091">
  <componentProperty key="foo" value="123"/>
</restConfiguration>

If no component has been explicit configured, then Camel will lookup if there is a Camel component that integrates with the Rest DSL, or if a org.apache.camel.spi.RestConsumerFactory is registered in the registry. If either one is found, then that is being used.

You can configure properties on these levels.

  • component - Is used to set any options on the Component class. You can also configure these directly on the component.

  • endpoint - Is used set any option on the endpoint level. Many of the Camel components has many options you can set on endpoint level.

  • consumer - Is used to set any option on the consumer level.

  • data format - Is used to set any option on the data formats. For example to enable pretty print in the json data format.

  • cors headers - If cors is enabled, then custom CORS headers can be set. See below for the default values which are in used. If a custom header is set then that value takes precedence over the default value.

You can set multiple options of the same level, so you can for example configure 2 component options, and 3 endpoint options etc.

Enabling or disabling Jackson JSON features

When using JSON binding you may want to turn specific Jackson features on or off. For example to disable failing on unknown properties (eg json input has a property which cannot be mapped to a POJO) then configure this using the dataFormatProperty as shown below:

restConfiguration().component("jetty").host("localhost").port(getPort()).bindingMode(RestBindingMode.json)
   .dataFormatProperty("json.in.disableFeatures", "FAIL_ON_UNKNOWN_PROPERTIES");

You can disable more features by separating the values using comma, such as:

.dataFormatProperty("json.in.disableFeatures", "FAIL_ON_UNKNOWN_PROPERTIES,ADJUST_DATES_TO_CONTEXT_TIME_ZONE");

Likewise you can enable features using the enableFeatures such as:

restConfiguration().component("jetty").host("localhost").port(getPort()).bindingMode(RestBindingMode.json)
   .dataFormatProperty("json.in.disableFeatures", "FAIL_ON_UNKNOWN_PROPERTIES,ADJUST_DATES_TO_CONTEXT_TIME_ZONE")
   .dataFormatProperty("json.in.enableFeatures", "FAIL_ON_NUMBERS_FOR_ENUMS,USE_BIG_DECIMAL_FOR_FLOATS");

The values that can be used for enabling and disabling features on Jackson are the names of the enums from the following three Jackson classes

  • com.fasterxml.jackson.databind.SerializationFeature

  • com.fasterxml.jackson.databind.DeserializationFeature

  • com.fasterxml.jackson.databind.MapperFeature

The rest configuration is of course also possible using XML DSL:

<restConfiguration component="jetty" host="localhost" port="9090" bindingMode="json">
  <dataFormatProperty key="json.in.disableFeatures" value="FAIL_ON_UNKNOWN_PROPERTIES,ADJUST_DATES_TO_CONTEXT_TIME_ZONE"/>
  <dataFormatProperty key="json.in.enableFeatures" value="FAIL_ON_NUMBERS_FOR_ENUMS,USE_BIG_DECIMAL_FOR_FLOATS"/>
</restConfiguration>

Default CORS headers

If CORS is enabled then the follow headers is in use by default. You can configure custom CORS headers which takes precedence over the default value.

Key Value

Access-Control-Allow-Origin

*

Access-Control-Allow-Methods

GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, CONNECT, PATCH

Access-Control-Allow-Headers

Origin, Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers

Access-Control-Max-Age

3600

Defining a custom error message as-is

If you want to define custom error messages to be sent back to the client with a HTTP error code (eg such as 400, 404 etc.) then you just set a header with the key Exchange.HTTP_RESPONSE_CODE to the error code (must be 300+) such as 404. And then the message body with any reply message, and optionally set the content-type header as well. There is a little example shown below:

restConfiguration().component("netty-http").host("localhost").port(portNum).bindingMode(RestBindingMode.json);
// use the rest DSL to define the rest services
rest("/users/")
    .post("lives").type(UserPojo.class).outType(CountryPojo.class)
        .route()
            .choice()
                .when().simple("${body.id} < 100")
                    .bean(new UserErrorService(), "idToLowError")
                .otherwise()
                    .bean(new UserService(), "livesWhere");

In this example if the input id is a number that is below 100, we want to send back a custom error message, using the UserErrorService bean, which is implemented as shown:

public class UserErrorService {
    public void idToLowError(Exchange exchange) {
        exchange.getIn().setBody("id value is too low");
        exchange.getIn().setHeader(Exchange.CONTENT_TYPE, "text/plain");
        exchange.getIn().setHeader(Exchange.HTTP_RESPONSE_CODE, 400);
    }
}

In the UserErrorService bean we build our custom error message, and set the HTTP error code to 400. This is important, as that tells rest-dsl that this is a custom error message, and the message should not use the output pojo binding (eg would otherwise bind to CountryPojo).

Catching JsonParserException and returning a custom error message

You can return a custom message as-is (see previous section). So we can leverage this with Camel error handler to catch JsonParserException, handle that exception and build our custom response message. For example to return a HTTP error code 400 with a hardcoded message, we can do as shown below:

onException(JsonParseException.class)
    .handled(true)
    .setHeader(Exchange.HTTP_RESPONSE_CODE, constant(400))
    .setHeader(Exchange.CONTENT_TYPE, constant("text/plain"))
    .setBody().constant("Invalid json data");

Query/Header Parameter default Values

You can specify default values for parameters in the rest-dsl, such as the verbose parameter below:

  rest("/customers/")
      .get("/{id}").to("direct:customerDetail")
      .get("/{id}/orders")
        .param().name("verbose").type(RestParamType.query).defaultValue("false").description("Verbose order details").endParam()
          .to("direct:customerOrders")
      .post("/neworder").to("direct:customerNewOrder");

The default value is automatic set as header on the incoming Camel Message. So if the call the /customers/id/orders do not include a query parameter with key verbose then Camel will now include a header with key verbose and the value false because it was declared as the default value. This functionality is only applicable for query parameters. Request headers may also be defaulted in the same way.

  rest("/customers/")
      .get("/{id}").to("direct:customerDetail")
      .get("/{id}/orders")
        .param().name("indicator").type(RestParamType.header).defaultValue("disabled").description("Feature Enabled Indicator").endParam()
          .to("direct:customerOrders")
      .post("/neworder").to("direct:customerNewOrder");

Client Request Validation

It is possible to enable validation of the incoming client request. The validation checks for the following:

  • Content-Type header matches what the Rest DSL consumes. (Returns HTTP Status 415)

  • Accept header matches what the Rest DSL produces. (Returns HTTP Status 406)

  • Missing required data (query parameters, HTTP headers, body). (Returns HTTP Status 400)

  • Parsing error of the message body (JSon, XML or Auto binding mode must be enabled). (Returns HTTP Status 400)

If the validation fails then Rest DSL will return a response with an HTTP error code.

The validation is by default turned off (to be backwards compatible). It can be turned on via clientRequestValidation as shown below:

restConfiguration().component("jetty").host("localhost")
    .clientRequestValidation(true);

OpenAPI / Swagger API

The Rest DSL supports OpenAPI and Swagger by the camel-openapi-java and camel-swagger-java modules.

You can define each parameter fine-grained with details such as name, description, data type, parameter type and so on, using the param. For example to define the id path parameter you can do as shown below:

<!-- this is a rest GET to view an user by the given id -->
<get uri="/{id}" outType="org.apache.camel.example.rest.User">
  <description>Find user by id</description>
  <param name="id" type="path" description="The id of the user to get" dataType="int"/>
  <to uri="bean:userService?method=getUser(${header.id})"/>
</get>

And in Java DSL

.get("/{id}").description("Find user by id").outType(User.class)
    .param().name("id").type(path).description("The id of the user to get").dataType("int").endParam()
    .to("bean:userService?method=getUser(${header.id})")

The body parameter type requires to use body as well for the name. For example a REST PUT operation to create/update an user could be done as:

<!-- this is a rest PUT to create/update an user -->
<put type="org.apache.camel.example.rest.User">
  <description>Updates or create a user</description>
  <param name="body" type="body" description="The user to update or create"/>
  <to uri="bean:userService?method=updateUser"/>
</put>

And in Java DSL:

.put().description("Updates or create a user").type(User.class)
    .param().name("body").type(body).description("The user to update or create").endParam()
    .to("bean:userService?method=updateUser")

Vendor Extensions

The generated API documentation can be configured to include vendor extensions (https://swagger.io/specification/#specificationExtensions) which document the operations and definitions with additional information, such as class name of model classes, camel context id and route id’s. This information can be very helpful for developers especially during troubleshooting. However, at production usage you may wish to not have this turned on to avoid leaking implementation details into your API docs.

The vendor extension information is stored in the API documentation with keys starting with x-.

Not all 3rd party API gateways and tools supports vendor-extensions when importing your API docs.

The vendor extensions can be turned on RestConfiguration via the apiVendorExtension option:

restConfiguration()
    .component("servlet")
    .bindingMode(RestBindingMode.json)
    .dataFormatProperty("prettyPrint", "true")
    .apiContextPath("api-doc")
    .apiVendorExtension(true)
        .apiProperty("api.title", "User API").apiProperty("api.version", "1.0.0")
        .apiProperty("cors", "true");

And in XML DSL:

 <restConfiguration component="servlet" bindingMode="json"
                       apiContextPath="api-docs"
                       apiVendorExtension="true">

      <!-- we want json output in pretty mode -->
      <dataFormatProperty key="prettyPrint" value="true"/>

      <!-- setup swagger api descriptions -->
      <apiProperty key="api.version" value="1.0.0"/>
      <apiProperty key="api.title" value="User API"/>

</restConfiguration>

Supported API properties

The following table lists supported API properties and explains their effect. To set them use apiProperty(String, String) in the Java DSL or <apiProperty> when defining the REST API via XML configuration. Properties in bold are required by the OpenAPI 2.0 specification. Most of the properties affect the OpenAPI Info object, License object or Contact object.

Property

Description

api.version

Version of the API

api.title

Title of the API

api.description

Description of the API

api.termsOfService

API Terms of Service of the API

api.license.name

License information of the API

api.license.url

URL for the License of the API

api.contact.name

The identifying name of the contact person/organization

api.contact.url

The URL pointing to the contact information

api.contact.email

The email address of the contact person/organization

api.specification.contentType.json

The Content-Type of the served OpenAPI JSON specification, application/json by default

api.specification.contentType.yaml

The Content-Type of the served OpenAPI YAML specification, text/yaml by default