25:00
Focus
Lesson 3

Build Your First Camel Route

~12 min100 XP

Introduction

Apache Camel is a powerful integration framework that lets you connect systems, transform data, and automate workflows using a clean, expressive API. In this lesson, you'll write your very first Camel route — a simple pipeline that picks up data, transforms it, and sends it somewhere new. By the end, you'll understand the core building blocks that power every Camel application, from the simplest file mover to the most complex enterprise integration.

What Is a Camel Route?

Think of a Camel route like a conveyor belt in a factory. Raw materials (your data) arrive at one end, pass through various stations that shape or modify them, and finally arrive at the destination in the desired form. In Camel, this conveyor belt is called a route, and it's defined using a fluent Java DSL (Domain-Specific Language) or XML.

Every route has three core ideas:

  1. Consumer (from) — Where does data come from? This could be a file folder, an HTTP endpoint, a message queue, a timer, and hundreds of other sources.
  2. Processor / Transformer — What happens to the data along the way? You can filter it, modify it, split it, enrich it, or convert its format.
  3. Producer (to) — Where does the data go when it's done? Another file, a database, a REST API, an email inbox, and so on.

Each source or destination is identified by a URI (Uniform Resource Identifier) string like file:input, timer:tick, or log:output. These URIs map to components — pluggable connectors that Camel ships with (there are over 300 of them!).

Camel wraps each piece of data traveling through a route in an Exchange object. Think of the Exchange as a shipping box. Inside the box is the Message, and the Message contains a body (the actual payload, like a string or file contents) plus headers (metadata, like labels on the box). Processors read and write to this Exchange as data flows from step to step.

Note: You don't need to know all 300+ components to be productive. Most real-world routes use only a handful of them — file, timer, log, http, and a few others cover an enormous range of use cases.

Exercise 1Multiple Choice
In a Camel route, what is the role of the 'from' endpoint?

Setting Up Your Camel Project

Before writing any routes, you need a working project. The easiest way to get started is with Apache Camel Quarkus or plain Apache Camel with Spring Boot. For this lesson, we'll use plain Camel with Maven, which has the fewest moving parts and is easiest to follow.

Here's what your pom.xml needs:

<dependencies>
  <!-- Camel core runtime -->
  <dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-core</artifactId>
    <version>4.4.0</version>
  </dependency>

  <!-- File component (included in camel-core) -->
  <!-- Log component (included in camel-core) -->
</dependencies>

Your project also needs a main method to start the CamelContext — the runtime container that manages all your routes, components, and lifecycle. Think of the CamelContext as the factory floor manager who ensures all the conveyor belts are running.

import org.apache.camel.CamelContext;
import org.apache.camel.impl.DefaultCamelContext;

public class MyApp {
    public static void main(String[] args) throws Exception {
        CamelContext context = new DefaultCamelContext();
        // We'll add routes here
        context.start();
        Thread.sleep(10000); // Keep alive for 10 seconds
        context.stop();
    }
}

A few important things to notice:

  • You create a DefaultCamelContext, which is the standard implementation.
  • You call context.start() to begin processing, and context.stop() to cleanly shut down.
  • The Thread.sleep() keeps the app alive long enough to process some messages. In production apps, you'd use a proper lifecycle hook instead.

Note: When using Camel with Spring Boot or Quarkus, the framework manages the CamelContext lifecycle for you automatically — you only write the routes themselves.

Writing Your First Route with the Java DSL

Now for the exciting part. Camel routes are defined inside a class called a RouteBuilder. You extend this class and override its configure() method, where you write your route logic using the fluent API.

Here's the simplest possible route — a timer that fires every second and logs a message:

import org.apache.camel.builder.RouteBuilder;

public class MyFirstRoute extends RouteBuilder {
    @Override
    public void configure() {
        from("timer:tick?period=1000")
            .setBody(constant("Hello, Camel!"))
            .to("log:output");
    }
}

Breaking this down line by line:

  • from("timer:tick?period=1000") — starts the route with a timer named "tick" that fires every 1000 milliseconds. The ?period=1000 is a URI query parameter that configures the component.
  • .setBody(constant("Hello, Camel!")) — sets the message body to the static string "Hello, Camel!" using a processor built into the DSL.
  • .to("log:output") — sends the exchange to the log component, which prints the message body to the console.

To register this route with the CamelContext, update your main method:

context.addRoutes(new MyFirstRoute());
context.start();

When you run this, you'll see Hello, Camel! logged to your console every second. Congratulations — you've built your first Camel route!

Exercise 2Fill in the Blank
In the Java DSL, every Camel route is defined inside a class that extends ___.

Moving Files Between Folders

One of the most classic Camel use cases is the File Mover pattern — watch a folder for new files, pick them up, and move them to another folder. This is genuinely useful for batch data ingestion, report processing, and legacy system integration.

Here's a route that moves .txt files from an input/ folder to an output/ folder:

from("file:input?include=.*\\.txt&noop=false")
    .to("file:output");

The file: component is smart by default:

  • It polls the directory at a regular interval (every 500ms by default).
  • After successfully processing a file, it moves the file to a .camel/ subfolder inside input/ to prevent reprocessing. Setting noop=true instead would leave the file untouched (useful for read-only scenarios).
  • The include parameter is a regex filter — .*\\.txt means "only pick up .txt files."

A common mistake beginners make is to point from and to at the same folder, creating an infinite loop where Camel picks up files it just wrote. Always use different source and destination directories.

Note: Camel's file component uses an idempotent repository under the hood — it tracks which files it has already processed using a file ID, so even if you restart the app, it won't reprocess the same files (unless you clear the repository).

Transforming Data Inside a Route

Moving data as-is is useful, but Camel's real strength is transforming data mid-flight. There are several ways to modify the message body as it flows through a route.

Using .setBody() — replaces the entire message body with a new value:

.setBody(simple("Processed: ${body}"))

Using .process() — gives you direct access to the Exchange for custom logic:

.process(exchange -> {
    String original = exchange.getIn().getBody(String.class);
    exchange.getIn().setBody(original.toUpperCase());
})

Using .transform() — similar to .setBody() but semantically signals intent to transform:

.transform(body().append(" [DONE]"))

The Simple expression language (simple(...)) is Camel's built-in templating mini-language. It lets you reference message properties with placeholders like ${body}, ${header.myHeader}, or ${date:now:yyyy-MM-dd}. It's not a full programming language, but it handles the 80% case cleanly without requiring Java code.

For heavier transformations — like converting CSV to JSON or XML to POJO — Camel has dedicated Data Format components (camel-jackson, camel-csv, camel-jaxb) that you chain with .marshal() and .unmarshal().

Here's a complete route that reads files, uppercases their content, and writes the result:

from("file:input")
    .convertBodyTo(String.class)           // read bytes as String
    .process(exchange -> {
        String text = exchange.getIn().getBody(String.class);
        exchange.getIn().setBody(text.toUpperCase());
    })
    .to("file:output");

Note: Always call .convertBodyTo(String.class) when reading files before manipulating the body as a string. By default, the file component delivers the body as a raw byte stream, which will cause errors if you try to treat it as text directly.

Exercise 3Multiple Choice
Which Camel DSL method gives you direct access to the Exchange object for custom Java transformation logic?

Adding Error Handling and Logging

Real routes encounter real problems — files get corrupted, network connections drop, downstream services go down. Camel has a built-in error handling framework that lets you define what to do when something goes wrong, without cluttering your route logic.

The simplest approach is onException(), which you declare at the top of your configure() method before any routes:

@Override
public void configure() {
    onException(Exception.class)
        .log("Something went wrong: ${exception.message}")
        .to("file:errors")
        .handled(true);    // prevents Camel from re-throwing the exception

    from("file:input")
        .convertBodyTo(String.class)
        .process(exchange -> { /* your logic */ })
        .to("file:output");
}

The .handled(true) call is critical — without it, Camel would still propagate the exception upward after your handler runs, potentially crashing the route. With it, the exception is fully absorbed and the route continues picking up the next message.

You can also add logging at any step in a route using .log():

from("file:input")
    .log("Picked up file: ${header.CamelFileName}")
    .convertBodyTo(String.class)
    .log("File content: ${body}")
    .to("file:output")
    .log("Successfully processed ${header.CamelFileName}");

${header.CamelFileName} is a special header that Camel's file component automatically sets to the original filename. Camel components populate many such Camel-specific headers automatically — they're a clean way to access metadata without touching the message body.

Note: Camel's .log() DSL step uses SLF4J under the hood. Add a logging implementation like Logback or Log4j2 to your classpath to control formatting and log levels in production.

Exercise 4True or False
Calling .handled(true) in a Camel onException block prevents the exception from propagating further, allowing the route to continue processing subsequent messages.

Key Takeaways

  • A Camel route connects a Consumer (from) to a Producer (to), with optional processors in between — think of it as a configurable conveyor belt for data.
  • Every piece of data in a route is wrapped in an Exchange, which contains a Message (body + headers) that processors read and modify.
  • Components are identified by URI strings like file:input or timer:tick?period=1000, and query parameters configure their behavior.
  • Use .process() for full Java logic, .setBody() or .transform() with the Simple expression language for lighter transformations, and .convertBodyTo() to safely coerce types.
  • Define onException() at the top of configure() and always use .handled(true) to gracefully absorb errors without crashing the entire route.
  • The CamelContext is the runtime that manages all routes — always start() it before processing begins and stop() it for a clean shutdown.
Check Your Understanding

In Apache Camel, data moves through a route wrapped inside an Exchange object that carries both the message body and headers. Imagine you are explaining Camel's route structure to a colleague who has never used it before. Walk through the three core building blocks of a Camel route — the consumer, processor/transformer, and producer — describing the role each one plays and how the Exchange object supports data as it travels between them. Use the conveyor belt analogy or one of your own to make the flow of data concrete.

🔒Upgrade to submit written responses and get AI feedback
Go deeper
  • What's the difference between a Processor and a Transformer in Camel?🔒
  • Can one route send data to multiple destinations simultaneously?🔒
  • How do Camel headers differ from the message body?🔒
  • What happens when a route encounters an error mid-pipeline?🔒
  • Can routes be chained together to form larger workflows?🔒