+1 (850) 665-2441

Route Templates and Kamelets: Reusing Camel Routes Without Copy-Paste

Most Camel estates we review have a shape like this: forty routes that are the same route. Poll an SFTP directory, validate, transform, drop on a queue, handle errors. The differences are a hostname, a directory, a schema name, and a destination. They were written by copy-paste over four years, and now a change to the error handling means forty pull requests, thirty-eight of which are identical and two of which quietly drifted years ago.

Camel has two answers to this: route templates and Kamelets. They are closely related — a Kamelet is a route template packaged in YAML with metadata — and choosing between them is mostly a question of who instantiates the thing and where it lives.

Route templates: one definition, many instances

A route template is a route with declared parameters. You define it once, then create instances by supplying values.

routeTemplate("sftp-ingest")
    .templateParameter("partner")
    .templateParameter("host")
    .templateParameter("dir")
    .templateParameter("target")
    .from("sftp://{{host}}/{{dir}}?delay=60000")
        .routeId("ingest-{{partner}}")
        .to("direct:validate")
        .to("{{target}}");

Instantiation is ordinary Java (or YAML, or a templatedRoute definition):

TemplatedRouteBuilder.builder(context, "sftp-ingest")
    .parameter("partner", "acme")
    .parameter("host", "sftp.acme.example")
    .parameter("dir", "/out")
    .parameter("target", "jms:queue:orders.acme")
    .add();

Three details matter in practice. Give every instance a distinct routeId derived from a parameter — otherwise the second instance collides with the first, and your metrics and logs become unreadable even if it doesn't. Declare defaults with templateParameter("delay", "60000") for the knobs most partners never change; required-with-no-default is a runtime failure at instantiation, which is where you want it, loudly, at startup. And remember that direct:validate is global — if a template refers to a shared endpoint, all instances share it. Parameterize the endpoint name when you need per-instance isolation, or accept the sharing deliberately.

The payoff is the one you wanted: error handling, retry policy, and observability live in one definition. A fix ships once.

Instantiating from configuration, not from code

The real win arrives when instances come from data rather than from a Java file. Read a list of partners from properties, a database table, or a ConfigMap, and loop:

for (PartnerConfig p : partnerRepository.findActive()) {
    TemplatedRouteBuilder.builder(context, "sftp-ingest")
        .parameter("partner", p.id())
        .parameter("host", p.host())
        .parameter("dir", p.dir())
        .parameter("target", p.queue())
        .add();
}

Onboarding partner forty-one becomes a row, not a release. That is genuinely valuable for high-churn, low-variance integrations — partner file feeds, per-tenant webhooks, per-region replication.

It also moves risk. A bad row now takes down a route at startup, and "what is actually running?" stops being answerable by reading the repository. If you go this way, do three things: validate configuration rows before instantiating, log the full instantiated set at startup (id, source, target), and expose the live route list — camel-management or the Camel console will tell you what exists. Dynamic routing without an inventory endpoint is how estates become mysteries.

Kamelets: templates as shareable artifacts

A Kamelet is the same idea with a distribution story. It is a YAML file declaring a template plus a JSON-schema-ish property block, referenced as an endpoint:

from("kamelet:sftp-ingest-source?host=sftp.acme.example&dir=/out")
    .to("jms:queue:orders.acme");

Because the properties are declared with types and descriptions, tooling can render them, and because the artifact is a file in a catalog, a platform team can publish a vetted set — "this is how we read from S3 here, with our retry and our tracing already in it" — and application teams consume them without reading the internals. That is the honest use case for Kamelets: an internal catalog of house-standard connectors, especially where Camel K or a low-code surface is involved.

The upstream Kamelet catalog is useful for prototyping and for Camel K pipelines. For long-lived enterprise routes we usually copy what we need into a local catalog and version it ourselves, rather than tracking someone else's release cadence for something on the critical path.

Testing them

Templates need the same test discipline as routes, plus one extra layer.

  • Test the template body once, thoroughly: instantiate it in a CamelTestSupport test with test parameters, point it at mock endpoints, and exercise the error paths. This is where AdviceWith still applies — advise the instantiated route by its generated id.
  • Test the instantiation logic separately: given this configuration table, do we get the expected set of route ids, and does a malformed row fail cleanly?
  • Add a startup assertion in the application itself — expected instance count, or every active partner has a route — so a silently skipped row shows up as a failed health check rather than a missing file nobody notices for a week.

When copy-paste still wins

We have also removed templates from estates. The failure mode is the over-parameterized template: fifteen parameters, three choice blocks keyed on a mode parameter, and a comment explaining that partner GLOBEX takes a different branch. At that point the abstraction is a configuration language with no type system, and reading any single partner's behavior requires simulating the template in your head.

Rough guidance:

  • Fewer than three or four instances? Duplication is cheaper than the indirection.
  • Instances that differ structurally, not just in values? Keep them separate routes. Templates parameterize values; branching on a parameter to change the shape of the route is the smell.
  • High churn in the number of instances, low variance between them? Template, and ideally instantiate from configuration.
  • Shared across teams or repositories? Kamelet, in a catalog you control.

The test we apply in reviews: can a new engineer answer "what happens to a file from partner X?" by reading one template and one row? If yes, the abstraction is earning its keep. If they need to trace three conditionals and a properties file, the forty copy-pasted routes were more honest.

Migrating an existing estate

Do not convert forty routes in one change. Pick the three most similar, extract a template that covers exactly those, and run the new instances in parallel with the old routes — same input teed, output compared — before deleting anything. The extraction will surface the drift: two of the three will turn out to differ in a way nobody documented, and deciding whether that difference is a feature or a four-year-old bug is the actual work. Then absorb the rest in batches, leaving genuinely odd routes as plain routes.

That is the same discipline we bring to ESB exits, applied at a smaller scale: parallel run, reconcile, cut over one at a time. Abstraction is a refactor, and refactors of production integrations deserve evidence, not confidence.