Skip to content
JavaAgentic

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

Multi-Agent Systems (MAS)

Building multi-agent systems in Java: orchestrator-worker coordination, agent handoffs, communication protocols and conflict resolution — and the honest case for when one agent is better.

Advanced4 min readUpdated
On this page

Multi-agent systems are where the field's ambition outruns its judgement most often. They are genuinely useful for a specific shape of problem — and a needless complication for most others. This tutorial covers how to build them well and, just as importantly, when not to.

Key Takeaways

  • Use multiple agents only when sub-tasks need different prompts, tools or models.
  • Coordinate through an orchestrator; prefer structured messages over free text.
  • Handoffs pass control plus context to a better-suited agent.
  • Surface conflicts between agents — do not average them away.

The honest starting point

A single agent with a well-chosen tool set beats a multi-agent system on cost, latency and debuggability for the large majority of problems. Before building one, ask: do my sub-tasks genuinely need different system prompts, tools or models? If not, one agent is the answer.

Multiple agents earn their keep when the jobs are truly different:

AgentPrompt shapeToolsModel
Filings analystCareful, literal, citation-obsessedDocument searchStrong
News scannerFast, high-volume, scepticalNews searchCheap
CalculatorNever estimates, always uses a toolDeterministic mathsCheap

Those are three incompatible system prompts. Merging them produces one prompt that contradicts itself.

Orchestrator-worker coordination

The most robust MAS topology: an orchestrator decomposes, delegates, and combines. Workers do not talk to each other directly.

Orchestrator-worker: the orchestrator decomposes and reconciles; workers stay independent.
Orchestration
public Report handle(String task) {
    List<SubTask> subs = orchestrator.decompose(task);
 
    // Independent workers run concurrently — cheap on Java 21 virtual threads.
    List<WorkerResult> results = subs.parallelStream()
            .map(this::dispatchToWorker)
            .toList();
 
    // Reconciliation, not just concatenation — see below.
    return orchestrator.reconcile(task, results);
}

Handoffs

Sometimes control should pass to a specialist along with the context gathered so far:

Handoff with context
public String handle(String request) {
    TriageResult triage = triageAgent.assess(request);
 
    // Hand off to the right specialist, carrying the context forward so the
    // specialist does not start from scratch.
    return switch (triage.category()) {
        case TECHNICAL -> technicalAgent.handle(request, triage.context());
        case BILLING -> billingAgent.handle(request, triage.context());
        case ESCALATE -> humanQueue.enqueue(request, triage.context());
    };
}

Communication: prefer structured messages

Agents that pass free text to each other accumulate ambiguity. Structured results are more reliable:

// Poor: free text the next agent must re-interpret.
record LooseResult(String text) {}
 
// Better: typed fields the orchestrator can route on and reconcile.
record Finding(String claim, String source, Confidence confidence, List<String> caveats) {}

Conflict resolution

When agents disagree, the disagreement is signal. Detect and surface it:

Conflict detection
public Reconciliation reconcile(List<Finding> findings) {
    List<Conflict> conflicts = ConflictDetector.detect(findings);
 
    if (!conflicts.isEmpty()) {
        // Do NOT average or silently pick one. Report both positions with their
        // evidence so a human can adjudicate.
        return Reconciliation.withConflicts(findings, conflicts);
    }
    return Reconciliation.agreed(findings);
}

Cost and failure modes

A multi-agent system multiplies everything: more model calls (higher cost and latency), more failure points, and a harder debugging story because a wrong answer could come from any agent or the reconciliation. Budget and trace accordingly:

  • Per-run budget across all agents, not per agent — see productionizing agentic systems.
  • Trajectory logging for every agent's every step.
  • Timeouts on each worker so one slow agent does not stall the whole task.

When one agent wins

Reach for a single agent when: the sub-tasks share a prompt and tool set, latency matters, cost matters, or you are early and learning what the problem even is. You can always split later; splitting prematurely commits you to the complexity before you know you need it.

Next

Frequently Asked Questions

When should I use multiple agents instead of one?
When sub-tasks need genuinely different system prompts, tool sets or models — a careful filings analyst and a fast news scanner are different jobs that a single prompt cannot serve well. If one agent with a good tool set can do the work, use one; multi-agent systems cost more in latency, tokens and complexity, and are harder to debug.
What is agent handoff?
Passing control of a task from one agent to another better suited to the next step — for example a triage agent handing a technical question to a specialist agent, along with the context gathered so far. Handoffs let specialised agents each do what they are best at, coordinated by a routing decision.
How do agents communicate in a multi-agent system?
Usually through a shared orchestrator that passes structured messages and results between them, rather than agents calling each other directly. Structured messages — typed results with clear fields — are more reliable than free text. Keeping communication mediated by an orchestrator also keeps the control flow inspectable.
What happens when two agents disagree?
Surface the disagreement rather than averaging it away. If a filings analyst and a news analyst reach conflicting conclusions, that conflict is exactly what a human needs to see. Design the orchestrator to detect conflicts and report both positions with their evidence, not to silently pick one or blend them into a mushy middle.

Related tutorials