JavaEvent-Driven Systems
June 7, 2026

Designing Java Integration Routes with ESB Patterns and Camel

Middleware is useful when it removes repeated plumbing from application code. In enterprise systems, one of the most common forms of middleware is application integration: the layer that helps one application talk to another application, a legacy system, a queue, a file share, or a database.

The main design question is where that integration logic should live. Sometimes every application owns its own connectors and transformations. Sometimes a central integration layer mediates communication. Both approaches are valid, but they create different tradeoffs for coupling, operations, scalability, and governance.

The Problem

A modern Java backend rarely lives alone. It may expose REST endpoints to a mobile app, call a customer system, publish events to a broker, write settlement files for a legacy platform, and transform payloads between JSON and XML.

Without a clear integration design, teams often end up with duplicated code:

  • Each service has its own connector to the same system.
  • Payload transformation logic is repeated in many places.
  • Retry and error handling rules are inconsistent.
  • Operations teams cannot easily see where messages go.
  • Legacy integration details leak into business code.

A simple payment platform may look like this:

Payment API
  |
  | JSON over HTTP
  v
Integration route
  |
  | XML file
  v
Legacy settlement folder

The payment service should not need to know every detail of the settlement folder, XML serialization, file naming, and route monitoring. That is integration work.

Core Idea

Integration can be designed in two broad styles.

Point-to-point integration puts the integration logic inside each application component. The services talk directly to each other. This can be simple and modular, but it may duplicate connectors and transformations across the system.

Centralized integration uses a mediation layer, traditionally an Enterprise Service Bus, to hide some technical details of each system. The ESB receives messages, routes them, transforms them, and forwards them to the right destination.

A practical comparison:

Approach Works well when Watch out for
Point-to-point Teams need autonomy and integrations are simple Duplicate transformation and connector logic
Centralized ESB Many systems share common integration requirements Central bottlenecks and operational complexity
Embedded integration engine A service owns its integration route but uses reusable patterns Governance can become harder across many teams

The best architecture is often not purely one or the other. A large enterprise may keep some central integration capabilities while using smaller, embedded routes for project-specific flows.

Message Structure

An ESB-style integration usually revolves around messages. Messages commonly have two parts:

  • A header for metadata.
  • A body for the actual payload.

The header may include a unique identifier, creation timestamp, sender identifier, route metadata, or other keys needed by the integration flow. The body contains the business data, such as a payment request.

Message
  |
  +-- Header
  |     +-- MessageID
  |     +-- CreatedAt
  |     +-- Sender
  |     +-- CorrelationID
  |
  +-- Body
        +-- Payment data

Thinking in messages helps you keep the integration layer focused. The route should move, route, transform, and observe data. It should not silently become the place where core business rules live.

Enterprise Integration Patterns

Many integration problems repeat. Enterprise integration patterns give names to common solutions.

Message Routing

Routing patterns decide where a message should go.

A message filter discards messages that do not match a policy. The policy can be simple, such as checking a field value, or more complex.

A content-based router sends a message to one of several destinations based on the message content or metadata.

An aggregator collects related messages and creates one larger message. This is stateful, so failures must be considered carefully.

A splitter does the opposite: it breaks one complex message into smaller messages.

Incoming messages
  |
  v
Content-based router
  |--------- valid settlement -------> Settlement route
  |--------- audit only -------------> Audit route
  |--------- ignored ----------------> End

Message Transformation

Transformation patterns change the content of a message. Common examples include converting JSON to XML, mapping field names, enriching a payload with data from another system, or reducing a payload before sending it downstream.

Transformation is often the first thing teams think about when they hear integration, but it is only one part of the design.

System Management

System management patterns help operate the integration layer.

Message history records which steps a message passed through. Message store records message contents or intermediate transformations for troubleshooting, audit, or regulatory reasons. Test messages are special messages injected into the route to check whether the route is alive and whether intermediate steps are losing or delaying messages.

These patterns are less about business behavior and more about making the route observable and maintainable.

Apache Camel as an Integration Engine

Apache Camel is a widely used open-source integration framework. It is not exactly an ESB, but it can be used as one. A better way to think about Camel is as an integration engine that implements many enterprise integration patterns and provides many technology connectors and data format helpers.

Camel can run in different shapes:

  • Standalone.
  • On top of runtimes such as Quarkus.
  • As a centralized integration layer.
  • Embedded inside applications that need local integration capabilities.

Camel routes are defined as a sequence of steps. The Java DSL uses fluent Java code to describe where data comes from, what happens to it, and where it goes.

A basic JSON-to-XML route looks like this:

from(platformHttp("/camel/hello"))
    .unmarshal()
    .json(JsonLibrary.Jackson, MyClass.class)
    .marshal()
    .jacksonxml()
    .to(file("/myfilePath?fileName=camelTest.xml"));

The route starts from an HTTP endpoint, maps JSON into a Java object, maps the Java object to XML, and writes the result to a file.

Example: Payment Settlement Route

Suppose a payment service is modern and exposes JSON over HTTP, but the settlement system is older and consumes XML files from a shared folder. That is a good integration use case because the main work is plumbing: accept one format, transform it, and deliver it to a legacy destination.

A minimal payment object can be represented as a Java class:

public class Payment {
    private String id;
    private Date date;
    private String currency;
    private String sender;
    private String recipient;
    private String signature;
    private float amount;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
}

The route can then receive a JSON payment, map it to Payment, extract the payment ID into a header, serialize the payment to XML, and write the file using that ID.

@ApplicationScoped
public class PaymentSettlement extends EndpointRouteBuilder {
    @Override
    public void configure() throws Exception {
        from(platformHttp("/camel/settlement"))
            .unmarshal()
            .json(JsonLibrary.Jackson, Payment.class)
            .setHeader("PaymentID", simple("${body.id}"))
            .marshal()
            .jacksonxml()
            .to(file("{{settlement.path}}?fileName=${header.PaymentID}.xml"));
    }
}

A request body for the route can look like this:

{
  "id": "1ef43029-f1eb-4dd8-85c4-1c332b69173c",
  "date": 1616504158091,
  "currency": "EUR",
  "sender": "giuseppe@test.it",
  "recipient": "stefano@domain.com",
  "signature": "169e8dbf-90b0-4b45-b0f9-97789d66dee7",
  "amount": 10.0
}

The important detail is not the amount of code. The important detail is the separation of responsibility. The payment domain can keep focusing on payment behavior, while the route owns the legacy settlement integration.

Practical Workflow

  1. Identify the systems that need to communicate.
  2. Define the message body and the metadata needed in the header.
  3. Decide whether the integration should be point-to-point, centralized, or embedded.
  4. List the required patterns: filter, router, transformer, splitter, aggregator, message store, or message history.
  5. Keep business decisions outside the route unless they are truly routing decisions.
  6. Add route observability from the beginning.
  7. Make file paths, endpoints, and destination names configurable.
  8. Test with normal messages, invalid messages, and route health-check messages.
  9. Document which system owns each payload format.
  10. Monitor failed deliveries and unexpected payloads.

Common Mistakes

The first mistake is placing too much business logic in the integration route. A route should not become a hidden business service. It should coordinate communication.

The second mistake is ignoring stateful patterns. Aggregators and message stores need careful failure handling because they keep data across steps.

The third mistake is treating the ESB as a magic simplifier. Centralization can reduce duplication, but it can also create bottlenecks and ownership problems.

The fourth mistake is skipping route-level observability. When an integration fails, the first question is usually where the message stopped. Message history and message store patterns exist for that reason.

Checklist

  • The integration style is intentionally chosen.
  • Message headers and payloads are clearly defined.
  • Transformations are owned by the integration layer.
  • Domain decisions remain in domain services.
  • Routes are observable.
  • Failed messages have a recovery path.
  • Configuration is externalized.
  • Test messages do not damage downstream systems.
  • Legacy details do not leak into the core service.
  • Operations teams can trace the route end-to-end.

Conclusion

Application integration is not just connecting systems. It is the discipline of moving data safely, transforming it clearly, and making the route understandable in production.

Java teams can use enterprise integration patterns and Apache Camel to build integration routes that are explicit, reusable, and easier to operate. The goal is simple: let business services focus on business behavior while integration code handles the communication details.

Share:

Comments0

Home Profile Menu Sidebar
Top