Skip to content
JavaAgentic

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

Agent Architecture Patterns

The core agent architecture patterns explained with Java: ReAct, plan-and-execute, reflection, orchestrator-worker and routing — when to use each, and why simpler is usually better.

Intermediate4 min readUpdated
On this page

There are a handful of agent architecture patterns worth knowing, and most production agents use the simplest one. This tutorial covers each pattern, the problem shape it fits, and the honest guidance that you should reach for the more elaborate ones far less often than the literature suggests.

Key Takeaways

  • ReAct (reason-act-observe) is the default and covers most cases. Start here.
  • Plan-and-execute suits long tasks whose shape is knowable — a visible plan you can inspect.
  • Reflection raises quality on tasks with a clear "better", at extra cost.
  • Orchestrator-worker and routing handle genuinely heterogeneous sub-tasks — use sparingly.

ReAct: the default

Reason, act, observe, repeat. The agent thinks, calls a tool, sees the result, and continues until done. Spring AI and LangChain4j run this loop for you.

ReAct: the reason-act-observe loop that underlies most tool-using agents.
// A ReAct agent is just tools plus a bounded loop — the library handles it.
Agent agent = AiServices.builder(Agent.class)
        .chatModel(model)
        .tools(new InvestigationTools())
        .maxSequentialToolsInvocations(8)
        .build();

Its strength is flexibility: it adapts step by step to what each observation reveals. Its weakness is that on long tasks it can meander, because it never commits to an overall plan.

Plan-and-execute

For longer tasks, make a plan first, then execute it. The plan is inspectable and the execution is more directed.

Plan then execute
public Result solve(String task) {
    // 1. Plan: the model produces an ordered list of steps.
    Plan plan = planner.plan(task);   // structured output: List<Step>
 
    // 2. Execute each step, feeding results forward.
    List<StepResult> results = new ArrayList<>();
    for (Step step : plan.steps()) {
        StepResult result = executor.execute(step, results);
        results.add(result);
        // Optional: re-plan if a step reveals the plan was wrong.
        if (result.requiresReplan()) {
            plan = planner.replan(task, results);
        }
    }
    return synthesise(results);
}

Reflection

Generate, then critique, then revise. Reflection raises quality where there is a clear notion of better:

Reflection loop
public String writeWithReflection(String task) {
    String draft = writer.write(task);
 
    for (int i = 0; i < MAX_REVISIONS; i++) {
        Critique critique = critic.review(draft);   // structured: issues + severity
        if (critique.isAcceptable()) {
            break;
        }
        draft = writer.revise(draft, critique);
    }
    return draft;
}

It works well for code (does it compile, pass tests, handle edge cases?) and writing (is it clear, complete, on-brief?). It costs extra model calls per revision, so reserve it for tasks where quality outweighs latency and cost.

Orchestrator-worker

An orchestrator decomposes a task and delegates to specialised workers, then combines results. Use it when sub-tasks need genuinely different tools or prompts:

Orchestrator-worker
public Report research(String question) {
    List<SubQuestion> subs = orchestrator.decompose(question);
 
    // Workers run concurrently — on Java 21, cheap on virtual threads.
    List<Finding> findings = subs.parallelStream()
            .map(sub -> switch (sub.type()) {
                case FILINGS -> filingsWorker.analyse(sub);
                case NEWS -> newsWorker.analyse(sub);
                case DATA -> dataWorker.analyse(sub);
            })
            .toList();
 
    return orchestrator.synthesise(question, findings);
}

The FinAgentic project is a full worked example of this pattern.

Routing

The simplest multi-path pattern: classify the input and dispatch. It is a chain, not really an agent, because the routing is deterministic — but it belongs in the same mental toolkit.

return switch (router.classify(request)) {
    case BILLING -> billingAgent.handle(request);
    case TECHNICAL -> technicalAgent.handle(request);
    case GENERAL -> generalAgent.handle(request);
};

Choosing a pattern

PatternUse whenCost
ReActDefault; exploratory, tool-using tasksLow
Plan-and-executeLong tasks with a knowable shapeMedium
ReflectionQuality matters more than latencyHigh (revisions)
Orchestrator-workerHeterogeneous sub-tasksHigh (many agents)
RoutingDistinct handlers by categoryLow

Next

Frequently Asked Questions

What is the most common agent architecture pattern?
ReAct — reason, act, observe, repeat — is the default and underlies most tool-using agents, including Spring AI and LangChain4j AI Services with tools. It is simple, effective, and the right starting point. More elaborate patterns like plan-and-execute or reflection add value only for specific problem shapes.
When should I use plan-and-execute instead of ReAct?
When a task has many steps whose overall shape is clear up front. Plan-and-execute makes a plan first, then executes it, which gives you a visible plan to inspect and reduces the meandering that pure ReAct can show on long tasks. For short, exploratory tasks, ReAct's step-by-step reasoning is simpler and usually better.
What is the reflection pattern?
Reflection has the agent critique its own output and revise it — generate, then evaluate against criteria, then improve. It raises quality on tasks with a clear notion of "better", like code or writing, at the cost of extra model calls. Use it where quality matters more than latency and cost.
What is the orchestrator-worker pattern?
An orchestrator agent decomposes a task and delegates sub-tasks to specialised worker agents, then combines their results. It suits problems where sub-tasks need genuinely different tools or prompts. It is more complex and costly than a single agent, so use it only when one agent with a good tool set genuinely cannot do the job.

Related tutorials