Apache Camel's Transformer EIP enables declarative, type-safe message conversion between POJOs in routes. Below is a fully self-contained, single-file example using nested classes and Camel's Main class.

Complete Single-File Example

import org.apache.camel.Message;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.main.Main;
import org.apache.camel.spi.DataType;
import org.apache.camel.spi.Transformer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Apache Camel Transformer EIP example demonstrating declarative type-safe message conversion.
 * All classes (Lead, Customer, LeadToCustomerTransformer) are in this single file.
 */
public class TransformerDemo {
    private static final Logger LOG = LoggerFactory.getLogger(TransformerDemo.class);

    /**
     * Lead POJO - Input model
     */
    public static class Lead {
        private String name;
        private String company;
        private String city;

        public Lead() {}

        public Lead(String name, String company, String city) {
            this.name = name;
            this.company = company;
            this.city = city;
        }

        // Getters and setters
        public String getName() { return name; }
        public void setName(String name) { this.name = name; }
        public String getCompany() { return company; }
        public void setCompany(String company) { this.company = company; }
        public String getCity() { return city; }
        public void setCity(String city) { this.city = city; }

        @Override
        public String toString() {
            return "Lead{name='" + name + "', company='" + company + "', city='" + city + "'}";
        }
    }

    /**
     * Customer POJO - Output model
     */
    public static class Customer {
        private String fullName;
        private String organization;
        private String location;

        public Customer() {}

        public Customer(String fullName, String organization, String location) {
            this.fullName = fullName;
            this.organization = organization;
            this.location = location;
        }

        // Getters and setters
        public String getFullName() { return fullName; }
        public void setFullName(String fullName) { this.fullName = fullName; }
        public String getOrganization() { return organization; }
        public void setOrganization(String organization) { this.organization = organization; }
        public String getLocation() { return location; }
        public void setLocation(String location) { this.location = location; }

        @Override
        public String toString() {
            return "Customer{fullName='" + fullName + "', organization='" + organization + "', location='" + location + "'}";
        }
    }

    /**
     * Transformer implementation for converting Lead to Customer
     */
    public static class LeadToCustomerTransformer extends Transformer {
        @Override
        public void transform(Message message, DataType fromType, DataType toType) throws Exception {
            Lead lead = message.getMandatoryBody(Lead.class);
            Customer customer = new Customer(
                lead.getName().toUpperCase(),
                lead.getCompany(),
                lead.getCity()
            );
            message.setBody(customer);
        }
    }

    public static void main(String[] args) throws Exception {
        Main main = new Main();
        main.configure().addRoutesBuilder(new RouteBuilder() {
            @Override
            public void configure() {
                // Register the transformer
                transformer()
                    .fromType("java:TransformerDemo$Lead")
                    .toType("java:TransformerDemo$Customer")
                    .withJava(LeadToCustomerTransformer.class);

                // Main route with automatic transformation
                from("direct:start")
                    .inputType("java:TransformerDemo$Lead")
                    .log("INPUT Lead: ${body}")
                    .to("direct:process")  // Triggers transformer automatically
                    .log("OUTPUT Customer: ${body}");

                from("direct:process")
                    .inputType("java:TransformerDemo$Customer")
                    .log("PROCESSED Customer: ${body}")
                    .to("log:final?showAll=true");
            }
        });

        // Add a startup listener to send test message after Camel starts
        main.addMainListener(new org.apache.camel.main.MainListenerSupport() {
            @Override
            public void afterStart(org.apache.camel.main.BaseMainSupport mainSupport) {
                try {
                    LOG.info("Sending test Lead message...");
                    mainSupport.getCamelContext().createProducerTemplate()
                        .sendBody("direct:start", new Lead("John Doe", "Acme Corp", "Auckland"));
                    LOG.info("Test message sent successfully!");
                } catch (Exception e) {
                    LOG.error("Failed to send test message", e);
                }
            }
        });

        // Run Camel (will run until Ctrl+C is pressed)
        main.run(args);
    }
}

Expected Console Output

Sending test Lead message...
INPUT Lead: Lead{name='John Doe', company='Acme Corp', city='Auckland'}
PROCESSED Customer: Customer{fullName='JOHN DOE', organization='Acme Corp', location='Auckland'}
Exchange[Id: D0B1A9A95984A67-0000000000000000, RouteGroup: null, RouteId: route2, ExchangePattern: InOnly, Properties: {CamelToEndpoint=log://final?showAll=true}, Headers: {}, BodyType: TransformerDemo.Customer, Body: Customer{fullName='JOHN DOE', organization='Acme Corp', location='Auckland'}]
OUTPUT Customer: Customer{fullName='JOHN DOE', organization='Acme Corp', location='Auckland'}
Test message sent successfully!

Key Implementation Details

Nested Class References: Uses TransformerDemo$Lead syntax to reference nested classes in data type strings.

Transformer Method Signature: Uses transform(Message message, DataType fromType, DataType toType)—the modern Camel 4.x+ API.

Automatic Startup Test: MainListenerSupport.afterStart() sends test message automatically when Camel context starts.

Single-File Design: All POJOs, transformer, and routes contained in one compilable .java file.

How the Transformation Triggers

  1. Input validationinputType("java:TransformerDemo$Lead") expects Lead
  2. Route boundaryto("direct:process") requires Customer input type
  3. Type mismatch → Camel matches Lead→Customer transformer automatically
  4. Conversion executesLeadToCustomerTransformer.transform() runs
  5. Type contract satisfied → Route continues with Customer