Creating a REST service with Apache Camel
So you’ve learned some of the basics of Apache Camel. You now know how to do something with a message. Great! So what’s next?
You probably want to start working with web services.
So in this tutorial, I’ll show how you can use Camel to expose a REST endpoint from your application.
I’ll cover the dependencies and the configuration you need to get started. At the end, there’s an example for you to follow, so you can see how to create your own RESTful service, using Apache Camel and Spring Boot.
If you’re not familiar with Apache Camel, then I encourage you to read the first part of my Apache Camel Tutorial.
The basics
How do you create a REST service with Camel?
You probably know by now that Camel is a framework for integrating between different systems. It usually doesn’t do something by itself, but instead creates and configures something (e.g. a component) that will actually do the work.
Creating REST services in Camel follows pretty much the same pattern. Write a some instructions for Camel, and it will use a bunch of other components to expose your REST service for you. You just need to pick exactly how you want the service implemented, and Camel will take care of the configuration.
This means that there are a gazillion different ways of creating a REST service with Camel. So instead of covering every different approach, I’ll just briefly mention the main ways that you can work with REST services in Apache Camel. You can:
-
Use Camel to bootstrap and expose a REST service using one of its supported component: You define your REST service operations using Camel’s REST DSL or REST component, and choose a component which will be configured to implement your service. Then, Camel will use that information to bootstrap the component, create your service and its operations, deploy it into a web server, like Jetty or Undertow, and generate Swagger/OpenAPI documentation for you. (This is surprisingly easy, and it’s the way I’m going to cover in this tutorial.)
-
Use Camel to bootstrap a service using JAX-RS: Camel has tight integration with Apache CXF, which is one implementation of Java’s JAX-RS specification for web services. So if you’re thinking of using the JAX-RS standard to define your REST service, then you can use Camel to bootstrap your service for you, using CXF. Check out the CXFRS component for more info on this.
-
Use Camel to provide the business logic for an existing REST service: If you’ve already exposed a REST service somewhere else in your application – e.g. using JAX-RS, or Spring Rest – then you can use Camel to implement the actual business logic, if you like.
For example, you might already have written a service class containing methods annotated with the
javax.ws.rs
annotations (@Path
,@GET
,@POST
, and so on) and have configured your implementation (e.g. CXF, Jersey or Resteasy). If so, then inside your service’s methods, you can send a message to a Camel route using Camel’s Java API (ProducerTemplate
, etc.) and then use the response from Camel to pass back to your consumer.
So is there a “best way”?
Well, no. :)
But in this tutorial I’ll show you my preferred way of doing it. We’ll use Camel’s REST DSL to configure the service and deploy it into an embedded web server, which is running inside Spring Boot.
Creating a REST service with the REST DSL
So how do you define a REST service in Camel? The nicest way of doing it is using the REST DSL.
The REST DSL is an extension to Camel’s existing route-building syntax, but specifically for describing REST services. It’s easy to write and configure.
Behind the scenes, Camel uses your REST DSL code to configure the underlying component that actually provides your REST service. You don’t need to worry about those details for now, as Camel will set you up with most of the defaults.
The REST DSL makes it easy to define REST services, without having to know too much of the underlying detail, or any complicated wiring.
At a very high level, when creating a REST service with Camel, we need to do these things:
-
Declare our RESTful service’s endpoints and operations
-
Choose which component will implement the service (see further below for the list of supported components)
-
Add code to work with parameters
-
Add configuration to get Camel to help us work with JSON or XML.
Defining endpoints and operations
To define the operations of our service, we can use the REST DSL syntax. Camel introduced the REST DSL in Camel 2.14. The REST DSL is basically a Camel syntax for defining REST services.
It looks like a bit like a normal Camel route…. except it starts with rest()
. To define a service you use rest(...)
followed by your operations, which take the form of HTTP verbs, like .get()
and .post()
, etc.
In the Java DSL it looks like this:
rest("/api/customers")
.get()
.route()
.to("direct:getCustomers")
.post()
.route()
.to("file:output/customers");
And in Camel’s XML DSL:
<rest path="/api/customers">
<get>
<route>
<to uri="direct:getCustomers"/>
</route>
</get>
<post>
<route>
<to uri="file:output/customers"/>
</route>
</post>
</rest>
Configure the implementation
Now we need to set some information about the service itself. We need to choose which component will implement the service, and set any properties, like hostname and port.
The first thing is to choose which component Camel should bootstrap to implement your REST service. Here, you’ve got a choice of:
-
servlet
-
spark-rest
-
netty-http
-
jetty
It’s entirely up to you which component you choose, but for Spring Boot applications I would go with servlet, because you already have an embedded web server that Camel can use for the servlet (Tomcat).
Then you can set this configuration inside a restConfiguration()
block, like this:
// Define the implementing component - and accept the default host and port
restConfiguration().component("servlet");
// Define the component and hostname and port
restConfiguration().component("servlet")
.host("localhost").port(8080);
Working with parameters
Your REST API will probably need to accept parameters from the consumer. It’s easy to do this, whether you’re taking parameters in the body, URL, or in the query string.
POST body
Operations like POST are easy. The body from the request will be stored in the Camel Exchange Body. So you can access it like this:
rest("/customers")
.post().log("The body is ${body}!");
URL parameters
When you need to read a parameter from the URL – like a Customer ID, or Product ID – then you need to:
-
Define a placeholder in the URI of your operation, e.g.
/customers/{id}
-
Fetch your input later, using a Header of the same name - e.g.
${header.id}
Here’s an example - a REST delete operation which takes a customer ID. The parameter {id}
is declared in the uri
- e.g. /customers/12345
. It can then be retrieved using ${header.id}
:
Java DSL
rest("/customers")
.delete("{id}").to("bean:customer?method=delete(${header.id})");
XML DSL
<rest path="/customers">
<delete uri="{id}">
<to uri="bean:customer?method=delete(${header.id})"/>
</delete>
</rest>
Using my example route above, I could implement a method like this in my customer
bean, which would be able to do something with my id
value:
public String delete(String id) {
// Your code would delete the customer record here
return "Deleted customer " + id;
}
Query string parameters
Reading query string parameters is very similar to reading parameters inside a URI path.
To read a parameter from the query string (for example, from a GET request):
-
Define a placeholder in the URI of your operation, in query string format - e.g.
/search?city={city}
-
Fetch your variable later from a Header of the same name - e.g.
${header.city}
Here’s an example of a REST GET operation which takes parameters in the query string. The query string parameter {country}
is declared in the uri
- e.g. /search?country=London
. It can then be retrieved using ${header.country}
:
Java DSL
rest("/api/apartments")
.get("/search?country={country}")
.to("bean:searchBean?method=byCountry(${header.country})");
XML DSL
<rest path="/api/apartments">
<get uri="/search?country={country}">
<to uri="bean:searchBean?method=byCountry(${header.country})"/>
</get>
</rest>
Set up JSON to POJO conversion
Finally we need to figure out what we want to do with our request and response messages. If you’re receiving or sending JSON, then how can Camel help out?
You could write your own JSON strings manually, but it’s pretty cumbersome, and it’s no fun writing a whole bunch of string concatenations!
The answer is to use POJOs for your request and response types. Yes, Java objects. When you use POJOs for your input and output messages, Camel can easily convert these to and from JSON for your REST service consumers. Camel is able to do this if Jackson is on the classpath.
Here’s an example for you to check out. First, here’s a class I’ve written called ResponseType, which models the response from my RESTful API. It contains one field, message
:
public class ResponseType {
private String message;
public ResponseType(String message) {
this.message = message;
}
public String getMessage() { }
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
To get Camel to automatically marshal this to JSON from my REST service, I ensure that .outType()
is set, and that it references my custom ResponseType
class. This tells Camel that it should expect to return a message of type ResponseType
back to the consumer:
rest().path("/my-api").....
.get()
.outType(ResponseType.class)
.to("bean:helloBean"); // this sends the Body to the bean
As the business logic for my REST operation is implemented in a Java bean, I just need to make sure my business logic method returns an object of type ResponseType
:
public class HelloBean {
public ResponseType sayHello() {
return new ResponseType("Hello, world!");
}
}
Finally, I need to give a hint to Camel to bind JSON to Java objects. This is done using the bindingMode
configuration, which I add to the restConfiguration
block:
restConfiguration()
.component("servlet")
.bindingMode(RestBindingMode.auto);
Or, in the XML DSL if you prefer:
<restConfiguration component="servlet" bindingMode="auto"/>
Now when I test the service using curl, HelloBean will return a ResponseType object. And Camel will use Jackson to automatically marshal this into JSON for me - note how I get a JSON object containing one field, message
, which is just like my example POJO above:
$ curl http://localhost:8080/services/my-api
{"message":"Hello, world!"}
Example - Creating a RESTful service in Camel using the REST DSL
Now we know what we need to create a RESTful service with Camel - a component that will implement the service, and some REST DSL sugar to configure it. This example will:
-
Use the servlet component to host the RESTful service
-
Run on the default Spring Boot web port, which is 8080
-
Automatically convert between JSON and Java objects so we don’t have to mess around doing that ourselves
To create the service, follow these steps:
-
In your Maven POM, add
camel-servlet-starter
as a dependency. This will allow Camel to deploy our service into the embedded Tomcat container:<dependency> <groupId>org.apache.camel.springboot</groupId> <artifactId>camel-servlet-starter</artifactId> </dependency>
-
Add
camel-jackson-starter
as a dependency. This will allow Camel to marshal to/from JSON:<dependency> <groupId>org.apache.camel.springboot</groupId> <artifactId>camel-jackson-starter</artifactId> </dependency>
-
In the
RouteBuilder
of your new project, add arestConfiguration()
element.This initialises the component that will provide the REST service. In this example we’re using the Servlet component for this.
We do this first before defining any operations for the service:
public void configure() throws Exception { restConfiguration() .component("servlet") .bindingMode(RestBindingMode.auto); }
-
Define a REST endpoint, and add skeletons for each of your operations - GET, POST, etc.
First, define a REST endpoint, using
rest
, with the path (URI) of your service. Then append each of your operations - these are your HTTP verbs.The syntax for a REST verb/operation looks just like a familiar Camel route. However, instead of
from
, we start each operation with the HTTP verb you want to use, such asget
,post
anddelete
.Do it like this:
Java DSL
rest("/api/customers") .get().route().to("...") .post().route().to("...") .delete().route().to("...");
XML DSL
<rest path="/api/customers"> <get> <route> <to uri="..."/> </route> </get> <post> <route> <to uri="..."/> </route> </post> </rest>
-
Now build your Camel routes for your REST operations! Here’s an example of a REST service that’s been filled out:
Java DSL
rest("/customers") .get().route().to("bean:customerService?method=listCustomers") .post().route().to("jms:queue:CUSTOMERS");
XML DSL
<rest path="/customers"> <get> <route> <to uri="bean:customerService?method=listCustomers"/> </route> </get> <post> <route> <to uri="direct:post"/> </route> </post> </rest>
As you can see, the GET operation passes the request to be serviced by a bean,
customerService
. But the POST operation passes the request to a Direct component,direct:post
. -
To add support for automatically converting requests and responses to/from JSON, define POJOs representing your request and response messages, e.g.:
public class CreateCustomerResponse { private String result; // Add some getters and setters here... }
Now, declare your request and response types, so that Camel knows how to marshal them to/from JSON:
rest() .post() .route() .type(Customer.class) .outType(CreateCustomerResponse.class) .to("direct:post");
And also ensure that you’ve set binding mode in the REST configuration:
restConfiguration() .component("servlet") .bindingMode(RestBindingMode.auto);
-
That’s it! Now keep building up your REST API with as many endpoints and operations as you need.
Just want to see the complete, worked example? Click on the button below to see a full example:
Get the example code on GitHub
🌞 That’s it! Comments? Questions? Do I need to correct something in this article? Please leave your thoughts below!