Build a Native Quarkus and Camel Application using Mandrel and Docker
Apache Camel is a Java framework for routing and integration, and when we talk about integration means we are talking about lightweight and fast response time. And this is where Apache Camel and Quarkus comes into the picture.
Utilizing Quarkus capability of native compilation, we can compile our Camel Framework application into a native application without the necessity of using JVM. Therefore making a lightweight Apache Camel into more lighweight and faster.
For this project, we will start with a simple Maven file
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.edw</groupId>
<artifactId>quarkus-camel-native</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<compiler-plugin.version>3.13.0</compiler-plugin.version>
<maven.compiler.release>21</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
<quarkus.platform.version>3.16.3</quarkus.platform.version>
<skipITs>true</skipITs>
<surefire-plugin.version>3.5.0</surefire-plugin.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>${quarkus.platform.artifact-id}</artifactId>
<version>${quarkus.platform.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>quarkus-camel-bom</artifactId>
<version>${quarkus.platform.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-direct</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-jackson</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-rest</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<version>${quarkus.platform.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>build</goal>
<goal>generate-code</goal>
<goal>generate-code-tests</goal>
<goal>native-image-agent</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>${compiler-plugin.version}</version>
<configuration>
<parameters>true</parameters>
</configuration>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<configuration>
<systemPropertyVariables>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
<configuration>
<systemPropertyVariables>
<native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>native</id>
<activation>
<property>
<name>native</name>
</property>
</activation>
<properties>
<skipITs>false</skipITs>
<quarkus.native.enabled>true</quarkus.native.enabled>
</properties>
</profile>
</profiles>
</project>
a properties file,
# default
quarkus.http.port=8080
quarkus.log.level=INFO
quarkus.log.category."com.edw".level=DEBUG
quarkus.log.console.format=%d{yyyy-MM-dd HH:mm:ss,SSS} %h %N[%i] %-5p [%c{3.}] (%t) %s%e%n
# disable sending anonymous statistics
quarkus.analytics.disabled=true
and with a simple Java file,
package com.edw.route;
import jakarta.enterprise.context.ApplicationScoped;
import org.apache.camel.builder.RouteBuilder;
@ApplicationScoped
public class HelloWorldRoute extends RouteBuilder {
@Override
public void configure() throws Exception {
rest("/api")
.get("/hello-world")
.produces("application/json")
.to("direct:hello-world");
from("direct:hello-world")
.routeId("hello-world-api")
.log("calling getHelloWorld")
.setBody(constant("{\"hello\":\"world\"}"));
}
}
and finally, a Dockerfile
## Stage 1 : build with maven builder image with native capabilities FROM quay.io/quarkus/ubi-quarkus-mandrel-builder-image:jdk-21 AS build COPY --chown=quarkus:quarkus --chmod=0755 mvnw /code/mvnw COPY --chown=quarkus:quarkus .mvn /code/.mvn COPY --chown=quarkus:quarkus pom.xml /code/ USER quarkus WORKDIR /code RUN ./mvnw -B org.apache.maven.plugins:maven-dependency-plugin:3.1.2:go-offline COPY src /code/src RUN ./mvnw package -Dnative ## Stage 2 : create the docker final image FROM quay.io/quarkus/quarkus-micro-image:2.0 WORKDIR /work/ COPY --from=build /code/target/*-runner /work/application # set up permissions for user `1001` RUN chmod 775 /work /work/application \ && chown -R 1001 /work \ && chmod -R "g+rwX" /work \ && chown -R 1001:root /work EXPOSE 8080 USER 1001 CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]
As we can see, it is a multi-stage docker build and we are using Mandrel to compile Quarkus into a native application. Next is to do a docker build, and see our Quarkus add compiled into native
$ podman build -t quarkus-camel-native -f multistage.dockerfile . [1/8] Initializing... (11.5s @ 0.12GB) Java version: 21.0.5+11-LTS, vendor version: Mandrel-23.1.5.0-Final Graal compiler: optimization level: 2, target machine: x86-64-v3 C compiler: gcc (redhat, x86_64, 8.5.0) Garbage collector: Serial GC (max heap size: 80% of RAM) 4 user-specific feature(s): - com.oracle.svm.thirdparty.gson.GsonFeature - io.quarkus.runner.Feature: Auto-generated class by Quarkus from the existing extensions - io.quarkus.runtime.graal.DisableLoggingFeature: Disables INFO logging during the analysis phase - org.eclipse.angus.activation.nativeimage.AngusActivationFeature ....... Produced artifacts: /code/target/quarkus-camel-native-1.0-SNAPSHOT-native-image-source-jar/build-artifacts.json (build_info) /code/target/quarkus-camel-native-1.0-SNAPSHOT-native-image-source-jar/quarkus-camel-native-1.0-SNAPSHOT-runner (executable) /code/target/quarkus-camel-native-1.0-SNAPSHOT-native-image-source-jar/quarkus-camel-native-1.0-SNAPSHOT-runner-build-output-stats.json (build_info) ======================================================================================================================== Finished generating 'quarkus-camel-native-1.0-SNAPSHOT-runner' in 3m 24s.
We can gain some benefits from native compilation such as a lighter image and less utilization
$ podman stats -a ID NAME CPU % MEM USAGE / LIMIT MEM % NET IO BLOCK IO PIDS CPU TIME AVG CPU % f8745a7b7ce2 gracious_goldberg 0.01% 37.86MB / 4.097GB 0.92% 0B / 0B 0B / 12.29kB 12 1.155723s 0.41%
Code for this activity can be found here,
https://github.com/edwin/quarkus-camel-native
