Skip to content
JavaAgentic

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

LangChain4j Agents & Tools

Build tool-using agents in LangChain4j: the @Tool annotation, how the agent loop works, bounding iterations, safe write tools and the ReAct pattern — with production-ready code.

Advanced5 min readUpdated
On this page

Tools turn a LangChain4j AI Service from a text generator into something that can act — look up data, call an API, run a calculation. This tutorial covers defining tools, how the agent loop runs them, and the guardrails that make a tool-using agent safe enough to deploy.

Key Takeaways

  • Annotate a method with @Tool; the description is the entire interface the model sees.
  • LangChain4j runs the ReAct loop for you — reason, act, observe, repeat.
  • Bound the loop with maxSequentialToolsInvocations; return errors as values.
  • Write tools authorize in code and require confirmation for irreversible actions.

Defining a tool

OrderTools.java
class OrderTools {
 
    private final OrderRepository orders;
 
    OrderTools(OrderRepository orders) {
        this.orders = orders;
    }
 
    // The description is the contract the model reads. Say when to use it and
    // what it returns — the model never sees the method body.
    @Tool("""
            Look up the delivery status of an order by its ID.
            Use when a customer asks where their order is or when it will arrive.
            Returns the status and expected date, or NOT_FOUND.
            """)
    String orderStatus(String orderId) {
        return orders.findById(orderId)
                .map(o -> "%s, expected %s".formatted(o.status(), o.expectedDelivery()))
                .orElse("NOT_FOUND: no order " + orderId);
    }
}

Register it on the AI Service:

interface SupportAgent {
    String handle(String message);
}
 
SupportAgent agent = AiServices.builder(SupportAgent.class)
        .chatModel(chatModel)
        .tools(new OrderTools(repository))
        // Cap the loop so a confused agent cannot spin forever.
        .maxSequentialToolsInvocations(5)
        .build();

How the loop runs

LangChain4j runs the ReAct loop: the model requests tools, the library executes them, until a final answer.

You wrote no routing, no intent parsing, and no loop. The model decides when to call the tool; the library executes it and feeds the result back.

Writing descriptions the model can use

The description is the whole skill. It must explain when to use the tool, not how it works:

// Poor — describes the implementation.
@Tool("Queries the orders table")
 
// Good — when to use it, what it needs, what it returns.
@Tool("""
        Look up an order's delivery status by ID. Use when a customer asks about
        the location or arrival of an existing order. Requires the order ID.
        Returns status and expected date, or NOT_FOUND.
        """)

Errors as values

A tool error is conversation, not an exception. Return a message that tells the model what to do:

@Tool("Issue a refund for an order. Returns the refund reference.")
String refund(String orderId) {
    Order order = orders.findById(orderId).orElse(null);
    if (order == null) {
        // Keeps the model in the loop so it can ask for a correct ID.
        return "ERROR: no order " + orderId + ". Ask the customer to check the ID.";
    }
    if (order.isRefunded()) {
        return "ERROR: order " + orderId + " was already refunded.";
    }
    return "REFUNDED: reference " + refunds.issue(order).reference();
}

A thrown exception aborts the whole request; a returned error lets the agent recover gracefully.

Safe write tools

Read tools are low risk. Write tools — anything that changes state — need real guardrails:

A safe write tool
@Tool("""
        Cancel the current customer's subscription at period end.
        Only call after the customer has explicitly confirmed.
        """)
String cancelSubscription(String confirmation) {
    // 1. Identity from the session, never a tool argument.
    String userId = SecurityContextHolder.getContext().getAuthentication().getName();
 
    // 2. Authorization checked in code.
    if (!subscriptions.canCancel(userId)) {
        return "ERROR: this account cannot self-cancel.";
    }
    // 3. Confirmation for the irreversible action.
    if (!"CONFIRM".equals(confirmation)) {
        return "CONFIRMATION_REQUIRED: confirm with the customer, then call again "
                + "with confirmation=CONFIRM.";
    }
    subscriptions.cancelAtPeriodEnd(userId);
    return "CANCELLED: ends on " + subscriptions.periodEnd(userId);
}

Read tools vs write tools

Start with read-only tools. They deliver most of the value at a fraction of the risk and let you learn how your model behaves before you hand it anything destructive. When you add write tools, register them per request rather than globally, so a support question does not have the refund tool in scope at all.

Combining tools with memory and RAG

An AI Service can have tools, memory and retrieval at once:

SupportAgent agent = AiServices.builder(SupportAgent.class)
        .chatModel(chatModel)
        .tools(new OrderTools(repository))
        .chatMemoryProvider(id -> MessageWindowChatMemory.withMaxMessages(20))
        .contentRetriever(retriever)   // RAG for policy questions
        .maxSequentialToolsInvocations(5)
        .build();

This is a capable support agent: it answers policy questions from documents, looks up real order data through tools, and remembers the conversation.

Next

Frequently Asked Questions

How do I create a tool in LangChain4j?
Annotate a method with @Tool and a clear description, then register the object holding it when building the AI Service with .tools(). The description is what the model reads to decide when to call the tool, so write it as a sentence explaining when to use it and what it returns, not what the code does internally.
How does the agent loop work in LangChain4j?
The AI Service sends your tool definitions with the prompt. If the model requests a tool, LangChain4j executes the matching method, appends the result to the conversation, and calls the model again. This repeats until the model produces a final answer, up to the iteration limit you set. You write no loop code — the library runs it.
How do I prevent an agent from looping forever?
Set .maxSequentialToolsInvocations() when building the AI Service to cap how many tool calls a single request can make. Also return clear terminal errors from tools rather than throwing, so a failing tool tells the model what to do differently instead of prompting endless retries.
What is the ReAct pattern?
ReAct interleaves Reasoning and Acting: the model thinks about what to do, calls a tool, observes the result, and repeats. It is the default architecture behind most tool-using agents, including LangChain4j AI Services with @Tool methods, where the reason-act-observe loop is handled for you.

Related tutorials