Chapter 1. Getting Started with Red Hat build of Apache Camel for Quarkus
This guide introduces Red Hat build of Apache Camel for Quarkus, the various ways to create a project and how to get started building an application using Red Hat build of Apache Camel for Quarkus:
1.1. Red Hat build of Apache Camel for Quarkus overview
Red Hat build of Apache Camel for Quarkus brings the integration capabilities of Apache Camel and its vast component library to the Quarkus runtime.
The benefits of using Red Hat build of Apache Camel for Quarkus include the following:
- Enables users to take advantage of the performance benefits, developer joy and the container first ethos which Quarkus provides.
- Provides Quarkus extensions for many of the Apache Camel components.
- Takes advantage of the many performance improvements made in Camel 3, which results in a lower memory footprint, less reliance on reflection and faster startup times.
- You can define Camel routes using the Java DSL.
1.2. Tooling
1.2.1. IDE plugins
Quarkus has plugins for most of the popular development IDEs which provide Quarkus language support, code/configuration completion, project creation wizards and much more. The plugins are available at each respective IDE marketplace.
- VS Code extension
- Eclipse plugin (currently not supported)
- IntelliJ plugin (currently not supported)
Check the plugin documentation to discover how to create projects for your preferred IDE.
1.2.2. Camel content assist
The following plugins provide support for content assist when editing Camel routes and application.properties
:
- VS Code Language support for Camel - a part of the Camel extension pack
Debug Adapter for Apache Camel to debug Camel integrations written in Java, YAML or XML locally.
- For more information about scope of development support, see Development Support Scope of Coverage
- Eclipse Desktop Language Support for Camel - a part of Jboss Tools
- Apache Camel IDEA plugin (not always up to date)
- Users of other IDEs supporting Language Server Protocol may choose to install and configure Camel Language Server manually
1.3. Building your first project with Red Hat build of Apache Camel for Quarkus
1.3.1. Overview
You can use code.quarkus.redhat.com to generate a Quarkus Maven project which automatically adds and configures the extensions that you want to use in your application.
This section walks you through the process of creating a Quarkus Maven project with Red Hat build of Apache Camel for Quarkus including:
- Creating the skeleton application using code.quarkus.redhat.com
- Adding a simple Camel route
- Exploring the application code
- Compiling the application in development mode
- Testing the application
1.3.2. Generating the skeleton application with code.quarkus.redhat.com
You can bootstrap and generate projects on code.quarkus.redhat.com.
The Red Hat build of Apache Camel for Quarkus extensions are located under the 'Integration' heading.
If you need additional extensions, use the 'search' field to find them.
Select the component extensions that you want to work with and click 'Generate your application' to download a basic skeleton project.
You can also push the project directly to GitHub.
For more information about using code.quarkus.redhat.com
to generate Quarkus Maven projects, see Creating a Quarkus Maven project using code.quarkus.redhat.com in the Getting started with Red Hat build of Quarkus guide.
Procedure
In the code.quarkus.redhat.com website, select the following extensions:
-
camel-quarkus-rest
-
camel-quarkus-jackson
camel-quarkus-direct
NoteDo not compile the application on code.quarkus.redhat.com (in the final step of the procedure). Instead, use the compile command described in the Section 1.3.5, “Development mode” section below.
-
Navigate to the directory where you extracted the generated project files from the previous step:
$ cd <directory_name>
1.3.3. Explore the application code
The application has two compile dependencies which are managed within the com.redhat.quarkus.platform:quarkus-camel-bom
that is imported in <dependencyManagement>
.:
pom.xml
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id> <quarkus.platform.group-id>com.redhat.quarkus.platform</quarkus.platform.group-id> <quarkus.platform.version> <!-- The latest 3.2.x version from https://maven.repository.redhat.com/ga/com/redhat/quarkus/platform/quarkus-bom --> </quarkus.platform.version> ... <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>
For more information about BOM dependency management, see Developing Applications with Red Hat build of Apache Camel for Quarkus
The application is configured by properties defined within src/main/resources/application.properties
, for example, the camel.context.name
can be set there.
1.3.4. Adding a simple Camel route
Procedure
-
Create a file named
Routes.java
in thesrc/main/java/org/acme/
subfolder. Add a Camel Rest route as shown in the following code snippet:
Routes.java
package org.acme; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.model.rest.RestBindingMode; import io.quarkus.runtime.annotations.RegisterForReflection; public class Routes extends RouteBuilder { private final List<Fruit> fruits = new CopyOnWriteArrayList<>(Arrays.asList(new Fruit("Apple"))); @Override public void configure() throws Exception { restConfiguration().bindingMode(RestBindingMode.json); rest("/customers/") .get("/{id}").to("direct:customerDetail") .get("/{id}/orders").to("direct:customerOrders") .post("/neworder").to("direct:customerNewOrder"); } @RegisterForReflection // Let Quarkus register this class for reflection during the native build public static class Fruit { private String name; public Fruit() { } public Fruit(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public int hashCode() { return Objects.hash(name); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Fruit other = (Fruit) obj; return Objects.equals(name, other.name); } } }
1.3.5. Development mode
$ mvn clean compile quarkus:dev
This command compiles the project, starts your application, and lets the Quarkus tooling watch for changes in your workspace. Any modifications you make to your project will automatically take effect in the running application.
You can check the application in your browser. (For example, for the rest-json
sample application, access http://localhost:8080/fruits
)
If you change the application code, for example, change 'Apple' to 'Orange', your application automatically updates. To see the changes applied, refresh your browser.
Refer to Quarkus documentation Development mode section for more details about the development mode.
1.3.6. Testing
1.3.6.1. JVM mode
To test the Camel Rest route that we have created in JVM mode, add a test class as follows:
Procedure
-
Create a file named
RoutesTest.java
in thesrc/test/java/org/acme/
subfolder. Add the
RoutesTest
class as shown in the following code snippet:RoutesTest.java
package org.acme; import io.quarkus.test.junit.QuarkusTest; import org.junit.jupiter.api.Test; import static io.restassured.RestAssured.given; import org.hamcrest.Matchers; @QuarkusTest public class RoutesTest { @Test public void testFruitsEndpoint() { /* Assert the initial fruit is there */ given() .when().get("/fruits") .then() .statusCode(200) .body( "$.size()", Matchers.is(1), "name", Matchers.contains("Orange")); /* Add a new fruit */ given() .body("{\"name\": \"Pear\"}") .header("Content-Type", "application/json") .when() .post("/fruits") .then() .statusCode(200); /* Assert that pear was added */ given() .when().get("/fruits") .then() .statusCode(200) .body( "$.size()", Matchers.is(2), "name", Matchers.contains("Orange", "Pear")); } }
The JVM mode tests are run by maven-surefire-plugin
in the test
Maven phase:
$ mvn clean test
1.3.6.2. Native mode
To test the Camel Rest route that we have created in Native mode, add a test class as follows:
Procedure
-
Create a file named
NativeRoutesIT.java
in thesrc/test/java/org/acme/
subfolder. Add the
NativeRoutesIT
class as shown in the following code snippet:NativeRoutesIT.java
package org.acme; import io.quarkus.test.junit.NativeImageTest; @NativeImageTest public class NativeRoutesIT extends RoutesTest { // Execute the same tests but in native mode. }
The native mode tests are verified by
maven-failsafe-plugin
in theverify
phase.Pass the
native
property to activate the profile that runs them:$ mvn clean verify -Pnative
For more details, and how to use the CamelTestSupport
style of testing, see Testing Camel Quarkus Extensions.
1.3.7. Packaging and running the application
1.3.7.1. JVM mode
Procedure
Run
mvn package
to prepare a thinjar
for running on a stock JVM:$ mvn clean package $ ls -lh target/quarkus-app ... -rw-r--r--. 1 user user 238K Oct 11 18:55 quarkus-run.jar ...
NoteThe thin
jar
contains just the application code. You also need the dependencies intarget/quarkus-app/lib
to run it.Run the jar as follows:
$ java -jar target/quarkus-app/quarkus-run.jar ... [io.quarkus] (main) Quarkus started in 1.163s. Listening on: http://[::]:8080
The boot time should be around a second.
1.3.7.2. Native mode
Procedure
To prepare a native executable, do as follows:
Run the command
mvn clean package -Pnative
:$ mvn clean package -Pnative $ ls -lh target ... -rwxr-xr-x. 1 user user 46M Oct 11 18:57 code-with-quarkus-1.0.0-SNAPSHOT-runner ...
NoteThe
runner
has no.jar
extension and has thex
(executable) permission set. You can run it directly:$ ./target/*-runner ... [io.quarkus] (main) Quarkus started in 0.013s. Listening on: http://[::]:8080 ...
The application started in 13 milliseconds.
View the memory usage with the
ps -o rss,command -p $(pgrep code-with)
command :$ ps -o rss,command -p $(pgrep code-with) RSS COMMAND 65852 ./target/code-with-quarkus-1.0.0-SNAPSHOT-runner
The application uses 65 MB of memory.
See Producing a native executable in the Compiling your Quarkus applications to native executables guide for additional information about preparing a native executable.
Quarkus Native executable guide contains more details, including steps for creating a container image.
1.4. Testing Camel Quarkus Extensions
Testing offers a good way to ensure Camel routes behave as expected over time. If you haven’t already, read the Camel Quarkus user guide First Steps and the Quarkus documentation link:Testing your application section.
The easiest way of testing a route in Quarkus is to write local integration tests. This has the advantage of covering both JVM and native mode.
In JVM mode, you can use the CamelTestSupport
style of testing.
1.4.1. Running in JVM mode
In JVM mode, use the @QuarkusTest
annotation to bootstrap Quarkus and start Camel routes before the @Test
logic executes.
For example:
import io.quarkus.test.junit.QuarkusTest; import org.junit.jupiter.api.Test; @QuarkusTest class MyTest { @Test public void test() { // Use any suitable code that sends test data to the route and then assert outcomes ... } }
You can find a sample implementation in the Camel Quarkus source:
1.4.2. Running in native mode
Always test that your application works in native mode for all supported extensions.
You can reuse the test logic defined for JVM mode by inheriting the logic from the respective JVM mode class.
Add the @QuarkusIntegrationTest
annotation to tell the Quarkus JUnit extension to compile the application under test to native image and start it before running the tests.
import io.quarkus.test.junit.QuarkusIntegrationTest; @QuarkusIntegrationTest class MyIT extends MyTest { ... }
You can find a sample implementation in the Camel Quarkus source:
1.4.3. Differences between @QuarkusTest
and @QuarkusIntegrationTest
A native executable does not need a JVM to run, and cannot run in a JVM, because it is native code, not bytecode.
There is no point in compiling tests to native code so they run using a traditional JVM.
This means that communication between tests and the application must go over the network (HTTP/REST, or any other protocol your application speaks), through watching filesystems (log files for example), or any other interprocess communication.
1.4.3.1. @QuarkusTest
in JVM mode
In JVM mode, tests annotated with @QuarkusTest
execute in the same JVM as the application under test.
This means you can use @Inject
to add beans from the application into the test code.
You can also define new beans or even override the beans from the application using @javax.enterprise.inject.Alternative
and @javax.annotation.Priority
.
1.4.3.2. @QuarkusIntegrationTest
in native mode
In native mode, tests annotated with @QuarkusIntegrationTest
execute in a JVM hosted in a process separate from the running native application.
QuarkusIntegrationTest
provides additional features that are not available through @QuarkusTest
:
- In JVM mode, you can launch and test the runnable application JAR produced by the Quarkus build.
- In native mode, you can launch and test the native application produced by the Quarkus build.
- If you add a container image to the build, a container starts, and tests execute against it.
For more information about QuarkusIntegrationTest
, see the Quarkus testing guide.
1.4.4. Testing with external services
1.4.4.1. Testcontainers
Sometimes your application needs to access some external resource, such as a messaging broker, a database, or other service.
If a container image is available for the service of interest, you can use Testcontainers to start and configure the services during testing.
1.4.4.1.1. Passing configuration data with QuarkusTestResourceLifecycleManager
For the application to work properly, it is often essential to pass the connection configuration data (host, port, user, password of the remote service) to the application before it starts.
In the Quarkus ecosystem, QuarkusTestResourceLifecycleManager
serves this purpose.
You can start one or more Testcontainers in the start()
method and return the connection configuration from the method in the form of a Map
.
The entries of this map are then passed to the application in different ways depending on the mode:
-
Native mode: a command line (
-Dkey=value
) - JVM Mode: a special MicroProfile configuration provider
These settings have a higher precedence than the settings in the application.properties
file.
import java.util.Map; import java.util.HashMap; import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.wait.strategy.Wait; public class MyTestResource implements QuarkusTestResourceLifecycleManager { private GenericContainer myContainer; @Override public Map<String, String> start() { // Start the needed container(s) myContainer = new GenericContainer(...) .withExposedPorts(1234) .waitingFor(Wait.forListeningPort()); myContainer.start(); // Pass the configuration to the application under test return new HashMap<>() {{ put("my-container.host", container.getContainerIpAddress()); put("my-container.port", "" + container.getMappedPort(1234)); }}; } @Override public void stop() { // Stop the needed container(s) myContainer.stop(); ... }
Reference the defined test resource from the test classes with @QuarkusTestResource
:
import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.QuarkusTest; @QuarkusTest @QuarkusTestResource(MyTestResource.class) class MyTest { ... }
You can find a sample implementation in the Camel Quarkus source:
1.4.4.2. WireMock
Instead of having the tests connect to live endpoints, for example, if they are unavailable, unreliable, or expensive, you can stub HTTP interactions with third-party services & APIs.
You can use WireMock for mocking & recording HTTP interactions. It is used extensively throughout the Camel Quarkus test suite for various component extensions.
1.4.4.2.1. Setting up WireMock
Procedure
Set up the WireMock server.
NoteIt is important to configure the Camel component under test to pass any HTTP interactions through the WireMock proxy. You can achieve this by configuring a component property that determines the API endpoint URL.
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import java.util.HashMap; import java.util.Map; import com.github.tomakehurst.wiremock.WireMockServer; import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; public class WireMockTestResource implements QuarkusTestResourceLifecycleManager { private WireMockServer server; @Override public Map<String, String> start() { // Setup & start the server server = new WireMockServer( wireMockConfig().dynamicPort() ); server.start(); // Stub a HTTP endpoint. Note that WireMock also supports a record and playback mode // http://wiremock.org/docs/record-playback/ server.stubFor( get(urlEqualTo("/api/greeting")) .willReturn(aResponse() .withHeader("Content-Type", "application/json") .withBody("{\"message\": \"Hello World\"}"))); // Ensure the camel component API client passes requests through the WireMock proxy Map<String, String> conf = new HashMap<>(); conf.put("camel.component.foo.server-url", server.baseUrl()); return conf; } @Override public void stop() { if (server != null) { server.stop(); } } }
NoteSometimes things are less straightforward, and some extra work is required to configure the API client library. For example, for Twilio.
-
Ensure your test class has the
@QuarkusTestResource
annotation with the appropriate test resource class specified as the value.
import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.QuarkusTest; @QuarkusTest @QuarkusTestResource(WireMockTestResource.class) class MyTest { ... }
The WireMock server starts before all tests execute and shuts down when all tests finish.
You can find a sample implementation in the Camel Quarkus integration test source tree:
1.4.5. Using CamelQuarkusTestSupport
Since Camel Quarkus 2.13.0, you can use CamelQuarkusTestSupport
for testing. It is a replacement for CamelTestSupport
.
This will only work in JVM mode.
1.4.5.1. Testing with CamelQuarkusTestSupport
in JVM mode
Add the following dependency into your module (preferably in the test
scope):
<dependency> <groupId>org.apache.camel.quarkus</groupId> <artifactId>camel-quarkus-junit5</artifactId> <scope>test</scope> </dependency>
You can use CamelQuarkusTestSupport
in your test like this:
@QuarkusTest @TestProfile(SimpleTest.class) //necessary only if "newly created" context is required for the test (worse performance) public class SimpleTest extends CamelQuarkusTestSupport { ... }
1.4.5.2. Limitations when using CamelQuarkusTestSupport
When using `CamelQuarkusTestSupport, there are several limitations:
1.4.5.2.1. Methods
Some methods do not execute. Use the new methods starting with do
instead:
Not executed | Use instead |
---|---|
|
|
|
|
|
|
|
|
|
|
If you use @TestInstance(TestInstance.Lifecycle.PER_METHOD)
, doAfterConstruct
means a callback before each test. This is different from beforeAll
.
1.4.5.2.2. Annotations
You must annotate the test class with @io.quarkus.test.junit.QuarkusTest
and extend org.apache.camel.quarkus.test.CamelQuarkusTestSupport
.
1.4.5.2.3. Starting and stopping
-
You cannot stop and restart the same
CamelContext
instance within the life cycle of a single application. You can callCamelContext.stop()
, butCamelContext.start()
won’t work. -
CamelContext
is generally bound to starting and stopping the application, also when testing. - The application under test starts once for all test classes of the given Maven/Gradle module. Quarkus JUnit Extension controls the start and stop of the application. You must explicitly tell the application to stop.
1.4.5.2.4. Restarting the application
To force Quarkus JUnit Extension to restart the application and CamelContext
for a given test class, you need to assign a unique @io.quarkus.test.junit.TestProfile
to that class.
For instructions, see testing different profiles in the Quarkus documentation.
For a similar effect, you can also use @io.quarkus.test.common.QuarkusTestResource
.
1.4.5.2.5. Beans production
Camel Quarkus executes the production of beans during the build phase. Because the tests are built together, exclusion behavior is implemented into CamelQuarkusTestSupport
. If a producer of the specific type and name is used in one test, the instance will be the same for the rest of the tests.
1.4.5.2.6. JUnit Jupiter callbacks may not work
These JUnit Jupiter callbacks and annotations may not work:
Callbacks | Annotations |
---|---|
|
|
|
|
|
|
|
|
| |
|
For more information, see the Enrichment via QuarkusTest*Callback documentation.
1.4.5.2.7. Using adviceWith
When adviceWith
is set to true, all unadvised routes do not start. You must execute the method CamelQuarkusTestSupport.startRouteDefinitions()
for those routes to start them.
1.4.5.2.8. Using @Produces
Use @Produces
with the overridden method createRouteBuilder()
. The combination of @Produces
and RouteBuilder()
may not work correctly.
1.4.5.2.9. Configuring routes
To configure which routes from the application (src/main/java
) to include or exclude, you can use the following:
-
quarkus.camel.routes-discovery.exclude-patterns
-
quarkus.camel.routes-discovery.include-patterns
For more details, see the Core documentation.