Skip to content

How to create RESTful Web Service with Spring Boot and Apache Camel?

I’m learning Apache Camel in Spring Boot project and I try to create a Retful Webservice and the service is starting but the problem is that I get 404 when I call the endpoint.

@Component
@RequiredArgsConstructor
public class RestJavaDsl extends RouteBuilder {

    private final WeatherDataProvider weatherDataProvider;

    @Override
    public void configure() throws Exception {

        from("rest:get:javadsl/weather/{city}?produces=application/json")
                .outputType(WeatherDto.class)
                .process(this::getWeatherData);
    }

    private void getWeatherData(Exchange exchange) {
        String city = exchange.getMessage().getHeader("city", String.class);
        WeatherDto currentWeather = weatherDataProvider.getCurrentWeather(city);

        Message message = new DefaultMessage(exchange.getContext());
        message.setBody(currentWeather);
        exchange.setMessage(message);

    }

}

I created this class to hardcode some data:

@Component
public class WeatherDataProvider {

    private static Map<String, WeatherDto> weatherData = new HashMap<>();

    public WeatherDataProvider() {
        WeatherDto dto = WeatherDto.builder().city("London").temp("10").unit("C").receivedTime(new Date().toString()).id(1).build();
        weatherData.put("LONDON", dto);
    }

    public WeatherDto getCurrentWeather(String city) {
        return weatherData.get(city.toUpperCase());
    }

    public void setCurrentWeather(WeatherDto dto) {
        dto.setReceivedTime(new Date().toString());
        weatherData.put(dto.getCity().toUpperCase(), dto);
    }
}

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class WeatherDto implements Serializable {
    static int counter = 1;
    private int id = counter++;
    private String city;
    private String temp;
    private String unit;
    private String receivedTime;
}

application.yml

camel:
  component:
    servlet:
      mapping:
        context-path: /services/*

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.dgs</groupId>
    <artifactId>camel-rest-springboot</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>camel-rest-springboot</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>11</java.version>
        <camel.version>3.14.0</camel.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.camel.springboot</groupId>
            <artifactId>camel-spring-boot-starter</artifactId>
            <version>${camel.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.camel</groupId>
            <artifactId>camel-test</artifactId>
            <scope>test</scope>
            <version>${camel.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.camel</groupId>
            <artifactId>camel-jackson</artifactId>
            <version>${camel.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.camel</groupId>
            <artifactId>camel-jaxb</artifactId>
            <version>${camel.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.camel</groupId>
            <artifactId>camel-servlet</artifactId>
            <version>${camel.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.camel.springboot</groupId>
            <artifactId>camel-servlet-starter</artifactId>
            <version>${camel.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.camel.springboot</groupId>
            <artifactId>camel-rest-starter</artifactId>
            <version>${camel.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

The serviceis starting and this is the console log:

2022-01-24 20:01:40.353  INFO 15796 --- [           main] c.d.c.CamelRestSpringbootApplication     : Starting CamelRestSpringbootApplication using Java 11.0.5 on pc-PC with PID 15796 (D:Spring Bootcamel-rest-springboottargetclasses started by pc in D:Spring Bootcamel-rest-springboot)
2022-01-24 20:01:40.357  INFO 15796 --- [           main] c.d.c.CamelRestSpringbootApplication     : No active profile set, falling back to default profiles: default
2022-01-24 20:01:43.583  INFO 15796 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2022-01-24 20:01:43.604  INFO 15796 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2022-01-24 20:01:43.604  INFO 15796 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.56]
2022-01-24 20:01:43.820  INFO 15796 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2022-01-24 20:01:43.821  INFO 15796 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 3235 ms
2022-01-24 20:01:45.228  INFO 15796 --- [           main] o.a.c.c.s.CamelHttpTransportServlet      : Initialized CamelHttpTransportServlet[name=CamelServlet, contextPath=]
2022-01-24 20:01:45.233  INFO 15796 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2022-01-24 20:01:45.592  INFO 15796 --- [           main] o.a.c.impl.engine.AbstractCamelContext   : Message DataType is enabled on CamelContext: camel-1
2022-01-24 20:01:45.607  INFO 15796 --- [           main] o.a.c.impl.engine.AbstractCamelContext   : Routes startup (total:1 started:1)
2022-01-24 20:01:45.607  INFO 15796 --- [           main] o.a.c.impl.engine.AbstractCamelContext   :     Started route1 (rest://get:javadsl/weather/%7Bcity%7D)
2022-01-24 20:01:45.607  INFO 15796 --- [           main] o.a.c.impl.engine.AbstractCamelContext   : Apache Camel 3.14.0 (camel-1) started in 370ms (build:83ms init:269ms start:18ms)
2022-01-24 20:01:45.617  INFO 15796 --- [           main] c.d.c.CamelRestSpringbootApplication     : Started CamelRestSpringbootApplication in 6.569 seconds (JVM running for 8.087)

But when I try to call the endpoint http://localhost:8080/services/javadsl/weather/london

It looks like this endpoint doesn’t exist, the rest isn’t created. I used debugger and the method getWeatherData() isn’t called. And I think this log is not ok: Started route1 (rest://get:javadsl/weather/%7Bcity%7D), it should be something like that: route1 started and consuming from servlet:/javadsl/weather/%7Bcity%7D And the tutorial is from here: https://www.youtube.com/watch?v=spDjbC8mZf0&t=433s Thank you in advance!

Answer

You can start by creating example project using official camel-archetype-spring-boot maven archetype. You can generate project based on the archetype using your IDE or from command-line using following maven command.

mvn archetype:generate -DarchetypeArtifactId="camel-archetype-spring-boot" -DarchetypeGroupId="org.apache.camel.archetypes" -DarchetypeVersion="3.14.0"

Now in the maven project file pom.xml add following block to the dependencies

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

Note that version doesn’t need to be specified as it is provided by camel-spring-boot-dependencies BOM (bill of materials) in dependencyManagement section.

After that you can create new file ConfigureCamelContext.java where we can configure our CamelContext and provide it with RestConfiguration. We can do this by implementing CamelContextConfiguration and @Component annotation.

The annotation tells spring-framework that it should create instance of the class and inject it to objects that request it based on its class-type or interface. Camel is configured to automatically ask spring-framework for these RestConfiguration instances which it will proceed to use configure CamelContext.

package com.example;

import org.apache.camel.CamelContext;
import org.apache.camel.spi.RestConfiguration;
import org.apache.camel.spring.boot.CamelContextConfiguration;
import org.springframework.stereotype.Component;

@Component
public class ConfigureCamelContext implements CamelContextConfiguration {

    @Override
    public void beforeApplicationStart(CamelContext camelContext) {
        
        RestConfiguration restConfiguration = new RestConfiguration();
        restConfiguration.setApiComponent("jetty");
        restConfiguration.setApiHost("localhost");
        restConfiguration.setPort(8081);

        camelContext.setRestConfiguration(restConfiguration);
    }

    @Override
    public void afterApplicationStart(CamelContext camelContext) {   
    }
}

This should work with RestDSL and rest-component.

Edit MySpringBootRouter.java to something like this:

package com.example;

import org.apache.camel.Exchange;
import org.apache.camel.builder.RouteBuilder;
import org.springframework.stereotype.Component;

@Component
public class MySpringBootRouter extends RouteBuilder {

    static final String CONTET_TYPE_TEXT = "text/plain";

    @Override
    public void configure() {
        
        rest()
            .get("/hello/")
                .route()
                    .setHeader(Exchange.CONTENT_TYPE,  constant(CONTET_TYPE_TEXT))
                    .setBody().constant("Hello world")
                .end()
            .endRest()
            .get("/hello/{name}")
                .route()
                    .setHeader(Exchange.CONTENT_TYPE,  constant(CONTET_TYPE_TEXT))
                    .setBody().simple("Hello ${headers.name}")
                .end()
            .endRest();

        from("rest:get:test?produces=plain/text")
            .setBody().constant("Hello from JavaDSL");
    }
}

Comment out the class under src/test/java/<grouId>/ as the example test provided by the archetype is not applicable for for MySpringBootRouter and will interrupt build.

To run the project you can use command

mvn spring-boot:run

Now you should be able to open up localhost:8081/hello, localhost:8081/hello/<Name> or localhost:8081/test get response in plain text.