Skip to content
JavaAgentic

Type at least two characters. Try “RAG”, “pgvector” or “tool calling”.

LangChain4j Chains & Composition

Compose multi-step LLM workflows in LangChain4j: sequential chains, routing by classification, and building custom chains from AI Services — when to chain and when a single call suffices.

Intermediate4 min readUpdated
On this page

A chain is a workflow with a fixed shape: classify, then route, then format. Modern LangChain4j lets you build these by composing AI Services and ordinary Java, which keeps the control flow explicit — you can read it, test it, and step through it in a debugger.

Key Takeaways

  • A chain is a fixed sequence you define; an agent decides its own steps. Prefer chains when the path is knowable.
  • Build chains by composing AI Services with plain Java, not a magic Chain class.
  • Mix models across steps — a cheap one to route, a strong one to reason.
  • Chain only when there are genuinely distinct stages; a single call is cheaper and simpler.

A sequential chain

Each stage is an AI Service; plain Java wires them:

A summarise-then-translate chain
interface Summariser {
    @UserMessage("Summarise the following in three sentences:\n\n{{it}}")
    String summarise(String text);
}
 
interface Translator {
    @UserMessage("Translate to {{lang}}:\n\n{{text}}")
    String translate(@V("text") String text, @V("lang") String language);
}
 
public class SummariseAndTranslate {
 
    private final Summariser summariser;
    private final Translator translator;
 
    public SummariseAndTranslate(ChatModel model) {
        this.summariser = AiServices.create(Summariser.class, model);
        this.translator = AiServices.create(Translator.class, model);
    }
 
    public String run(String document, String language) {
        // The chain, as plain Java. Readable, testable, debuggable.
        String summary = summariser.summarise(document);
        return translator.translate(summary, language);
    }
}

The control flow is ordinary code. You can log between steps, add a cache, or short-circuit — none of which is possible when the sequence is hidden inside a framework abstraction.

A routing chain

Classify the input, then dispatch to a specialised handler. This is the most useful chain pattern in practice:

RoutingAssistant.java
enum Category { BILLING, TECHNICAL, GENERAL }
 
interface Router {
    @UserMessage("Classify this request as BILLING, TECHNICAL or GENERAL:\n{{it}}")
    Category classify(String request);
}
 
public class RoutingAssistant {
 
    private final Router router;
    private final BillingAssistant billing;
    private final TechnicalAssistant technical;
    private final GeneralAssistant general;
 
    public String handle(String request) {
        // Cheap model classifies; specialised services (possibly on different
        // models) handle. This is a chain and, importantly, not an agent —
        // the routing is deterministic.
        return switch (router.classify(request)) {
            case BILLING -> billing.answer(request);
            case TECHNICAL -> technical.answer(request);
            case GENERAL -> general.answer(request);
        };
    }
}

Adding deterministic steps

The strength of building chains in plain Java is that non-model steps slot in naturally:

public Report analyse(String document) {
    // Step 1: model extracts entities.
    Entities entities = extractor.extract(document);
 
    // Step 2: deterministic enrichment from your database — no model needed.
    List<EnrichedEntity> enriched = entities.list().stream()
            .map(e -> database.lookup(e))
            .toList();
 
    // Step 3: model composes a report from the enriched data.
    return writer.compose(enriched);
}

Steps 1 and 3 are model calls; step 2 is a database lookup. Interleaving deterministic logic between model calls is where chains earn their keep — the reliable parts stay reliable.

Chain vs single call vs agent

SituationUse
One model call does the jobA single call
Fixed sequence of distinct stagesA chain
Steps depend on runtime results in unknowable waysAn agent

The decision tree: if a single prompt suffices, do that. If you can write the steps down in advance, chain them. Only when you genuinely cannot predict the sequence should you reach for an agent, which trades predictability for flexibility.

Error handling across a chain

A chain fails at whichever step errors. Decide per step whether to fail the whole chain, retry, or degrade:

public String run(String document, String language) {
    String summary;
    try {
        summary = summariser.summarise(document);
    } catch (Exception e) {
        log.warn("summarisation failed, using truncated original", e);
        summary = truncate(document, 500);   // degrade rather than fail
    }
    return translator.translate(summary, language);
}

Next

Frequently Asked Questions

What is a chain in LangChain4j?
A chain is a sequence of steps where the output of one feeds the next — for example classify a request, then route it to a specialised handler, then format the result. In modern LangChain4j you usually build chains by composing AI Services and plain Java rather than a dedicated Chain class, which keeps the control flow explicit and debuggable.
Should I use a chain or a single prompt?
Use a single prompt when one model call can do the job — it is cheaper, faster and easier to debug. Use a chain when the task genuinely has distinct stages that benefit from separate prompts, separate models, or deterministic logic between them. Chaining for its own sake adds latency and cost.
How is a chain different from an agent?
A chain has a fixed sequence of steps that you define. An agent decides its own steps at runtime. If you can write the steps down in advance, build a chain — it is more predictable, cheaper and easier to test. Reserve agents for when the path genuinely cannot be known ahead of time.
Can different steps in a chain use different models?
Yes, and it is often the point. Use a small cheap model to classify or route, and a stronger model only for the step that needs deep reasoning. Because each AI Service takes its own ChatModel, mixing models across a chain is straightforward and a good cost optimisation.

Related tutorials