Apache Camel’s Java DSL lets you plug data formats directly into your routes using fluent marshal() and unmarshal() calls, including both built‑in and custom implementations.

Basics: marshal and unmarshal in Java DSL

In Java DSL, marshal() and unmarshal() are methods on RouteBuilder that apply a DataFormat to the message body.

  • marshal() converts a Java object (or other in‑memory representation) into a binary or textual format for the “wire”.
  • unmarshal() converts incoming bytes/text into a Java representation, often a POJO, Map, or List.

Example with a built‑in CSV data format:

public class CsvRoute extends RouteBuilder {
    @Override
    public void configure() {
        from("direct:format")
            .setBody(constant(Map.of("foo", "abc", "bar", 123)))
            .marshal().csv()        // Java Map -> CSV line
            .to("log:csv");
    }
}

The rationale is that your route expresses what transformation should happen (JSON, CSV, XML, etc.) while Camel’s data formats handle how to do it.

Three Java DSL styles for data formats

The Java DSL gives you several ways to use data formats.

1. Passing a DataFormat instance

You create and configure a DataFormat in Java and pass it to marshal()/unmarshal().

import org.apache.camel.dataformat.bindy.csv.BindyCsvDataFormat;

public class BindyRoute extends RouteBuilder {
    @Override
    public void configure() {
        DataFormat myCsv = new BindyCsvDataFormat(MyModel.class);

        from("file:data/in?noop=true")
            .unmarshal(myCsv)      // CSV -> MyModel instances
            .to("bean:processModel");
    }
}

Why use this: you get full type‑safe configuration in code and can reuse the same DataFormat instance across routes.

2. Short “dot” helpers: .marshal().json(), .unmarshal().csv()

For common formats, you can use the fluent helpers returned by marshal() and unmarshal().

public class JsonRoute extends RouteBuilder {
    @Override
    public void configure() {
        from("direct:toJson")
            .marshal().json()      // uses default JSON data format (e.g. Jackson)
            .to("log:json");
    }
}

This is concise but exposes only basic configuration options; you switch to the other styles if you need more control (like custom delimiters or modules).

3. Data Format DSL: marshal(dataFormat().csv().delimiter(","))

Camel 4 adds a dedicated Data Format DSL accessed via dataFormat(), which you can pass into marshal()/unmarshal().

public class CsvDslRoute extends RouteBuilder {
    @Override
    public void configure() {

        from("direct:format")
            .setBody(constant(Map.of("foo", "abc", "bar", 123)))
            .marshal(
                dataFormat()
                    .csv()          // choose CSV
                    .delimiter(",") // customize delimiter
                    .end()          // build DataFormat
            )
            .to("log:csv");
    }
}

Rationale: the Data Format DSL is still Java DSL, but it exposes the full configuration surface in a fluent, type‑safe way, without manually constructing the underlying DataFormat.

Custom DataFormat in Java DSL

You can plug any class implementing org.apache.camel.spi.DataFormat directly into the Java DSL. The following example uses the earlier “Hello‑line” format and wires it into routes.

Custom HelloLineDataFormat

import org.apache.camel.Exchange;
import org.apache.camel.spi.DataFormat;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;

public class HelloLineDataFormat implements DataFormat {

    @Override
    public void marshal(Exchange exchange, Object graph, OutputStream stream) throws Exception {
        String body = exchange.getContext()
                .getTypeConverter()
                .mandatoryConvertTo(String.class, graph);

        String encoded = "HELLO|" + body + "\n";
        stream.write(encoded.getBytes(StandardCharsets.UTF_8));
    }

    @Override
    public Object unmarshal(Exchange exchange, InputStream stream) throws Exception {
        String text = toString(stream);
        if (!text.startsWith("HELLO|")) {
            throw new IllegalArgumentException("Invalid format, expected HELLO| prefix: " + text);
        }
        String withoutPrefix = text.substring("HELLO|".length());
        return withoutPrefix.trim();
    }

    private String toString(InputStream in) throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        byte[] buf = new byte[1024];
        int len;
        while ((len = in.read(buf)) != -1) {
            out.write(buf, 0, len);
        }
        return out.toString(StandardCharsets.UTF_8);
    }

    @Override
    public void start() {
        // No special startup logic needed for this data format
    }

    @Override
    public void stop() {
        // No special shutdown logic needed for this data format
    }
}

Rationale: the custom format encapsulates both how to decorate the text and how to validate it, so routes never touch the "HELLO|" prefix logic directly.

Using the custom format with Java DSL

Here’s a minimal working example that wires the custom data format into Java DSL routes.

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

public class HelloLineRouteBuilder extends RouteBuilder {

    private final HelloLineDataFormat helloFormat = new HelloLineDataFormat();

    @Override
    public void configure() {

        // Example: marshal plain text to custom wire format
        from("timer:hello?period=5000")
            .setBody(constant("World"))
            .marshal(helloFormat)           // "World" -> "HELLO|World\n"
            .to("stream:out")
            .to("direct:incomingHello");    // Send marshaled data to the direct route

        // Example: unmarshal from custom wire format back to String
        from("direct:incomingHello")
            .unmarshal(helloFormat)         // "HELLO|World\n" -> "World"
            .log("Decoded body = ${body}");
    }

    // Small bootstrapper
    void main() throws Exception {
        CamelContext context = new DefaultCamelContext();
        context.addRoutes(new HelloLineRouteBuilder());
        context.start();
        Thread.sleep(30_000);
        context.stop();
    }
}

Why this structure:

  • The HelloLineDataFormat instance is a normal field, so you can reuse it in multiple routes in the same RouteBuilder.
  • The Java DSL remains expressive: you can read the route as “take the timer body, marshal with helloFormat, send to stdout”, which keeps data‑format details out of the route’s core logic.
  • The direct:incomingHello route applies .unmarshal(helloFormat) to turn "HELLO|World\n" back into "World", validating the round‑trip symmetry of your custom DataFormat in a realistic producer–consumer arrangement.

Choosing an approach in Java DSL

As a Java‑DSL user, you typically choose between:

  • Helpers (.marshal().json(), .unmarshal().csv()): quickest for standard cases.
  • Explicit instances (new JaxbDataFormat(...)): best when you need programmatic configuration or dependency‑injected collaborators.
  • Data Format DSL (marshal(dataFormat().csv().delimiter(";"))): clean, fluent configuration for complex built‑in formats.
  • Custom DataFormat classes (marshal(new MyCustomFormat())): when the wire format is non‑standard or proprietary.