Camel with Spring Boot example

Camel is a first citizen in Spring Boot application. In this tutorial we will learn how to bootstrap a Camel Route from within a Spring Boot REST application.

Spring Boot provides out of the box support for Camel with starters for most Camel components. Besides, the auto-configuration of the Camel context is able to auto-detect any Camel routes available in the Spring context. Let’s start from the configuration required and then we will add an example Camel Route and a Spring Boot controller.

Setting up a Spring Boot application with Camel

Firstly, we will set up our application. The simplest way to do that, is via Spring Boot Camel archetype:

mvn archetype:generate   -DgroupId="sample.camel"  \ 
                         -DartifactId="reset-producer" \  
                         -DarchetypeGroupId="org.apache.camel.archetypes" \  
                         -DarchetypeArtifactId="camel-archetype-spring-boot" \ 
                         -DinteractiveMode=false 

Next, let’s have a look at our project structure:

src
├── main
│   ├── java
│   │   └── sample
│   │       └── camel
│   │           ├── MySpringBean.java
│   │           ├── MySpringBootApplication.java
│   │           └── MySpringBootRouter.java
│   └── resources
│       ├── application.properties
│       └── META-INF
│           ├── LICENSE.txt
│           └── NOTICE.txt
└── test
    ├── java
    │   └── sample
    │       └── camel
    │           └── MySpringBootApplicationTest.java
    └── resources

As you can see, you have already in place a sample Spring Boot application with a basic Camel Route. Also, we have the Bill of Materials both for Spring Boot and for Camel:

<dependencyManagement>
    <dependencies>
      <!-- Spring Boot BOM -->
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>${spring.boot-version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <!-- Camel BOM -->
      <dependency>
        <groupId>org.apache.camel.springboot</groupId>
        <artifactId>camel-spring-boot-dependencies</artifactId>
        <version>${camel-version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
</dependencyManagement>

Latest version of Spring Boot and Camel + Spring Boot:

  • Spring Boot: 2.7.0
  • Camel – Spring Boot: 3.16.0

Check that the following dependencies are in your pom.xml, otherwise integrate your project with them::

<dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      <exclusions>
        <exclusion>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-undertow</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>

    <dependency>
      <groupId>org.apache.camel.springboot</groupId>
      <artifactId>camel-spring-boot-starter</artifactId>
    </dependency>
 
    <!-- Test -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.camel</groupId>
      <artifactId>camel-test-spring-junit5</artifactId>
      <scope>test</scope>
    </dependency>
</dependencies>

Finally, as our application requires REST Services, we need to add the Camel HTTP dependency to our project:

<dependency>
	<groupId>org.apache.camel.springboot</groupId>
	<artifactId>camel-http-starter</artifactId>
</dependency>

Failure to add the camel-http-starter dependency, will result in the following Exception if the is not in your Spring Boot Runtime:

java.lang.IllegalStateException: Cannot find RestProducerFactory in Registry or as a Component to use

Coding the Camel + Spring Boot Application

The Camel Route calls a REST Service using a Timer. The Route passes a random id to the REST endpoint to generate a random password:

@Component
public class RestRoute extends RouteBuilder {

    @Override
    public void configure() throws Exception {
        restConfiguration().host("localhost").port(8080);

        from("timer:hello?period={{timer.period}}")
            .setHeader("id", simple("${random(6,9)}"))
            .to("rest:get:example/{id}")
            .log("${body}");
    }

}

Please note this class contains the @Component annotation to make Camel auto detect this route when starting. The Endpoint is a simple Spring Boot Controller which generates the Random number based on the length id:

@RestController
public class MyController {

	@GetMapping(value = "/example/{id}")
	public String getRandomString(@PathVariable("id") Integer id) {
		return "Got " + randomString(id);
	}

	String randomString(int len) {
		String AB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
		SecureRandom rnd = new SecureRandom();

		StringBuilder sb = new StringBuilder(len);
		for (int i = 0; i < len; i++)
			sb.append(AB.charAt(rnd.nextInt(AB.length())));
		
		return sb.toString();
	}
}

Finally, to kick-start the Spring Boot application, we will add a basic Spring Application class with a main method:

@SpringBootApplication
public class RestApplication {

    public static void main(String[] args) {
        SpringApplication.run(RestApplication.class, args);
    }

}

Running the application

You can run the application just like any Spring Boot application:

mvn clean install spring-boot:run

In conclusion, you will see from the Console, the Timer calls repeatedly the REST Service which produces a Random String:

camel spring boot example

You can find the source code for this example here: https://github.com/fmarchioni/masterspringboot/tree/master/camel/rest-producer

Another Camel Spring Boot example: a Transformer

Let’s see another example. At first, we will add another Router class named MySpringBootRouter which uses a transformer to log some text messages:

@Component
public class MySpringBootRouter extends RouteBuilder {
  @Override
  public void configure() {
    from("timer:hello?period={{timer.period}}")
        .routeId("hello")
        .transform()
        .method("myBean", "saySomething")
        .filter(simple("${body} contains 'foo'"))
        .to("log:foo")
        .end()
        .to("stream:out");
  }
}

This Route has a Timer component in it which fires according to the “timer.period” (defined in application.properties). Each time the Timer is fired, the Bean registered under the context “myBean” will be invoked through its method “saySomething”. A Sample filter is included to fetch and log messages whose body contains ‘foo’.

Next, let’s check the Transformation done by the Bean “myBean”:

@Component("myBean")
public class MySpringBean {
  @Value("${greeting}")
  private String say;

  public String saySomething() {
    return say.toUpperCase();
  }
}

In the method saySomething we have changed the default example to transform the text to uppercase.

The Test class included in the project is in charge to intercept the Camel Route and replace the consumer with a direct consumer.

@SpringBootTest @CamelSpringBootTest public class MySpringBootApplicationTest {
  @Autowired private CamelContext camelContext;
  @Autowired private ProducerTemplate producerTemplate;
  @Test public void test() throws Exception {
      MockEndpoint mock = camelContext.getEndpoint("mock:stream:out", MockEndpoint.class);
      AdviceWith.adviceWith(camelContext, "hello",
        // intercepting an exchange on route
        r -> {
          // replacing consumer with direct component

          r.replaceFromWith("direct:start"); // mocking producer 					r.mockEndpoints("stream*"); 				} 		);  		

          // setting expectations 		

          mock.expectedMessageCount(1);mock.expectedBodiesReceived("HELLO WORLD");

          // invoking consumer 		

          producerTemplate.sendBody("direct:start", null);

          // asserting mock is satisfied 		

          mock.assertIsSatisfied();
        }
      }

Please note we have set to upper case the expected value of the “Hello World” message.

Within the application.properties there are several default message properties. For the scope of this example, the following properties can be adjusted:

# what to say
greeting = Hello World
# how often to trigger the timer
timer.period = 2000

You can run the application with:

mvn install spring-boot:run

You should see that the “HELLO WORLD” message is printed on the Camel spring Boot console:

2021-02-21 18:12:28.138  INFO 6030 --- [           main] com.sample.MySpringBootApplication       : Started MySpringBootApplication in 1.991 seconds (JVM running for 2.311) HELLO WORLD HELLO WORLD

Using the REST Controller as Template Producer

Let’s spice a bit our example As an example, we will add a REST Controller to produce the greeting message:

@RestController
public class MyRestController {
  @Autowired private ProducerTemplate template;

  @RequestMapping("/hello")
  public String sayHello(@RequestParam(defaultValue = "frank") String name) {
    return template.requestBody("direct:sayHello", name).toString();
  }
}

As you can see, we are using the direct Component which is the abstract way to initialize a Route from a ProducerTemplate object. In a nutshell, the ProducerTemplate will fire the synchronous call to “direct:sayHello” passing as parameter the value for “name” contained in the HTTP request. If no request object has been added, the default value of ‘frank’ will be used.

In order to use this example, we will replace the timer component with “direct:sayHello“:

@Component
public class MySpringBootRouter extends RouteBuilder {
  @Override
  public void configure() {
    from("direct:sayHello")
        .routeId("hello")
        .transform()
        .method("myBean", "saySomething")
        .filter(simple("${body} contains 'foo'"))
        .to("log:foo")
        .end()
        .to("stream:out");
  }
}

That’s it. Adjust the Test class accordingly. (You can check the full source code below).

You can run the application with:

mvn install spring-boot:run

You can test it as follows:

curl http://localhost:8080/hello?name=Jack

We have just covered two basic examples of Camel applications built using Spring Boot. If you want to read more about Spring Boot applications, check this tutorial: Spring Boot Hello World REST Service

Source code for the second Camel Spring Boot example: https://github.com/fmarchioni/masterspringboot/tree/master/camel/camel-helloworld-spring