What is an AI Agent? Detailed Explanation with Java Examples

This article has a short video:YouTubeTikTok

1. Overview

“AI Agent” is a buzzword that appears everywhere, but definitions are often vague: some call every chatbot an agent, others only consider complex multi-step systems as true agents. This article clarifies what an AI Agent is, what core components it must have, and the looping mechanism behind it. After reading, you’ll be able to build a minimal agent in vanilla Java to understand how it works “under the hood,” then scale up to a version using real LLMs with Spring AI.

2. What is an AI Agent?

Simple definition: An AI Agent is a system that uses an LLM as its brain to autonomously decide on actions to achieve a goal, rather than simply answering a single question and stopping.

Difference from a typical chatbot:

Feature Typical Chatbot AI Agent
Number of processing rounds 1 round: ask → answer Multiple steps: observe → reason → act → repeat
Calls tools/APIs? Usually not Yes, autonomously decides which tools to invoke
Long-term goal? No Yes, the agent breaks it down into smaller tasks to achieve it
Example “What’s the weather in Hanoi?” → returns a description “Book the cheapest flight Hanoi – Ho Chi Minh City for tomorrow” → finds tickets, compares prices, books, confirms

In other words, an LLM is just a “language brain” that can generate text. An agent transforms that brain into an entity capable of taking actions in the real world (calling APIs, reading files, querying databases) and repeatedly iterating through the reasoning process until the task is complete.

3. Core Components of an AI Agent

A minimal agent needs 4 components. Without any of them, the system is just a chatbot “wearing an agent’s outfit.”

3.1. LLM – The Reasoning Brain

The LLM receives a prompt (containing the goal, history, and available tools list) and generates the next decision: whether to answer directly or which tool to call with which parameters.

3.2. Tool (Function Calling) – The Agent’s Hands

A tool is a specific function the agent can call: look up an order, call a weather API, query a database. This is what transforms the agent from “knowing how to talk” to “knowing how to act.”

public interface Tool {
    String name();
    boolean canHandle(String input);
    String execute(String input);
}

canHandle checks whether this tool can handle the current input; execute performs the action and returns the result (observation).

3.3. Memory – The Agent’s Memory

The agent needs to recall what it did in previous steps (conversation history, tool results) to avoid repeating useless actions. Memory can be as simple as a List<String> storing step logs, or more complex like a vector store to retrieve relevant information.

3.4. Control Loop – The Engine

This is the “engine” that runs the entire process: looping through Thought → Action → Observation steps until the goal is reached or the step limit is exhausted. The next section will dive deeper into this mechanism.

4. The ReAct Loop: How an Agent “Thinks Then Acts”

ReAct (Reasoning + Acting) is the most common looping pattern for agents:

  1. Thought: The LLM reasons “what should I do next?”
  2. Action: selects a tool and calls it with appropriate parameters.
  3. Observation: receives the result from the tool and feeds it back into the context.
  4. Repeats from step 1 until the LLM decides it has enough information to provide a final answer, or reaches maxSteps.

To understand this mechanism without calling a real LLM (which costs API keys), we’ll build a mini agent in vanilla Java, using if-else logic instead of a real LLM.

public class SimpleAgent {

    private final List<Tool> tools;
    private final int maxSteps;

    public SimpleAgent(List<Tool> tools, int maxSteps) {
        this.tools = tools;
        this.maxSteps = maxSteps;
    }

    public String run(String goal) {
        String observation = goal;
        for (int step = 1; step <= maxSteps; step++) {
            Tool chosen = tools.stream()
                    .filter(t -> t.canHandle(observation))
                    .findFirst()
                    .orElse(null);

            if (chosen == null) {
                return "No tool can handle: " + observation;
            }

            System.out.printf("Step %d - Thought: need to use tool '%s'%n", step, chosen.name());
            String result = chosen.execute(observation);
            System.out.printf("Step %d - Observation: %s%n", step, result);

            return result; // simple example: done after 1 step
        }
        return "Reached step limit (" + maxSteps + ") without completing the task.";
    }
}

maxSteps is the “budget” of loop iterations — essential to prevent the agent from running indefinitely if it never reaches a goal. The for loop simulates Thought (selecting a tool), Action (execute), and Observation (printing the result to console).

Three sample tools:

public class WeatherTool implements Tool {
    @Override
    public String name() {
        return "WeatherTool";
    }

    @Override
    public boolean canHandle(String input) {
        return input.toLowerCase().contains("weather");
    }

    @Override
    public String execute(String input) {
        return "Hanoi today is 28°C, cloudy.";
    }
}
public class OrderStatusTool implements Tool {
    @Override
    public String name() {
        return "OrderStatusTool";
    }

    @Override
    public boolean canHandle(String input) {
        return input.toLowerCase().contains("order");
    }

    @Override
    public String execute(String input) {
        return "Order #1023 is in transit, expected delivery within 2 days.";
    }
}
public class CalculatorTool implements Tool {
    @Override
    public String name() {
        return "CalculatorTool";
    }

    @Override
    public boolean canHandle(String input) {
        return input.matches(".*\\d+\\s*\\+\\s*\\d+.*");
    }

    @Override
    public String execute(String input) {
        String digits = input.replaceAll("[^0-9+]", "");
        String[] parts = digits.split("\\+");
        int sum = Integer.parseInt(parts[0]) + Integer.parseInt(parts[1]);
        return "Result: " + sum;
    }
}

Try it:

SimpleAgent agent = new SimpleAgent(
        List.of(new CalculatorTool(), new WeatherTool(), new OrderStatusTool()),
        3
);

String result = agent.run("Tell me the weather in Hanoi today");
System.out.println(result);

Output:

Step 1 - Thought: need to use tool 'WeatherTool'
Step 1 - Observation: Hanoi today is 28°C, cloudy.
Hanoi today is 28°C, cloudy.

Write tests to verify correct tool selection and error handling when no tool matches:

import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;

class SimpleAgentTest {

    @Test
    void shouldPickWeatherToolWhenGoalMentionsWeather() {
        SimpleAgent agent = new SimpleAgent(
                List.of(new CalculatorTool(), new WeatherTool(), new OrderStatusTool()),
                3
        );

        String result = agent.run("Tell me the weather in Hanoi today");

        assertThat(result).contains("Hanoi");
    }

    @Test
    void shouldReturnErrorWhenNoToolMatches() {
        SimpleAgent agent = new SimpleAgent(List.of(new CalculatorTool()), 3);

        String result = agent.run("Sing me a song");

        assertThat(result).startsWith("No tool can handle");
    }
}

This example uses if-else instead of an LLM, so “reasoning” is very rigid. A real agent differs in this: instead of hard-coded canHandle, the LLM itself reads the tool descriptions and decides which tool to call, with parameters that the LLM extracts from the natural language question.

5. Building a Real Agent with Spring AI and Tool Calling

To see an LLM actually making decisions, we’ll use Spring AI — a framework that integrates AI models into Spring Boot with built-in tool calling support.

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-openai</artifactId>
    <version>1.0.0</version>
</dependency>

Configure the API key and model:

spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      chat:
        options:
          model: gpt-4o-mini

Declare a tool using the @Tool annotation, similar to the role of OrderStatusTool in the earlier example, but this time a real LLM will invoke it:

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Component;

@Component
public class OrderTools {

    @Tool(description = "Look up order status by order ID")
    public String getOrderStatus(String orderId) {
        // in practice, this would call OrderService / database here
        return "Order " + orderId + " is in transit, expected delivery within 2 days.";
    }
}

The description is crucial: the LLM uses it to decide whether to invoke this tool and what parameters to pass.

Service that wires the tool into the agent:

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Service;

@Service
public class OrderAssistantService {

    private final ChatClient chatClient;

    public OrderAssistantService(ChatClient.Builder builder, OrderTools orderTools) {
        this.chatClient = builder
                .defaultTools(orderTools)
                .build();
    }

    public String ask(String question) {
        return chatClient.prompt(question).call().content();
    }
}

Calling ask("Where is my order ORD-1023?"), under the hood Spring AI will:

  1. Send the question along with the getOrderStatus tool description to the model.
  2. The model returns the decision “call getOrderStatus with orderId="ORD-1023"” instead of answering directly.
  3. Spring AI automatically invokes the corresponding Java method and retrieves the result.
  4. Sends that result back to the model so it can write the final natural language response.
Your order ORD-1023 is currently in transit and should arrive within 2 more days.

All 4 steps above form a complete ReAct loop, except that Spring AI hides the for loop—you don’t need to write the control loop yourself like in the vanilla Java example in section 4.

Note: The spring-ai-starter-openai version and artifact name may change over time; always check the BOM that matches your Spring Boot version before adding the dependency.

6. Common Agent Types

  • Single-agent ReAct: one agent looping Thought-Action-Observation. Suitable for medium-complexity tasks, like the two examples above.
  • Plan-and-Execute: the agent first creates a detailed plan (step list), then executes each step without continuously re-reasoning like ReAct. Suitable for structured multi-step tasks (e.g., “create a report with 5 sections”).
  • Multi-agent: multiple specialized agents collaborate, typically with one “Supervisor” agent orchestrating “Worker” agents (e.g., search agent, writing agent, data-checking agent), with results aggregated.

Which type to choose depends on task complexity: simple tasks use single-agent, tasks spanning multiple domains (search + writing + verification) should be split into multi-agent for easier control and debugging.

7. Common Pitfalls & When Not to Use Agents

  • Infinite loops, burning tokens: without setting maxSteps or a token budget, the agent may loop forever because the LLM thinks the task isn’t done. Always set a clear step limit like in SimpleAgent from section 4.
  • Vague tool descriptions: if a tool’s description is unclear, the LLM may call the wrong tool or pass parameters of the wrong type. Write specific descriptions, clearly stating the expected parameter format.
  • Missing guardrails for dangerous actions: allowing an agent to call tools that delete data, transfer money, etc. without a confirmation step is a major risk. Always add a verification layer (human-in-the-loop or rule-based checks) before executing irreversible actions.
  • Overkill for simple tasks: if a question only needs one LLM call to get an answer (e.g., translating a sentence), don’t build an entire agent loop with tool calling. It adds extra latency and wastes money calling the model multiple times unnecessarily.

Note: If your system doesn’t need to call external actions (APIs, databases) and only needs to generate text once, that’s a job for a regular LLM — not an agent. Don’t add unnecessary complexity.

8. Conclusion

An AI Agent differs from a typical chatbot in its ability to autonomously decide actions and repeatedly iterate through the reasoning process until a goal is achieved, based on four components: LLM, tools, memory, and control loop. The ReAct loop (Thought-Action-Observation) is the mechanism behind most agents today, and you can simulate it yourself in vanilla Java to understand its essence before moving to production frameworks like Spring AI. Most importantly: always set a step limit, write clear tool descriptions, and add guardrails for irreversible actions — agents are powerful but can easily “overshoot” without proper controls.

AI AgentLLMSpring AIJavaFunction Calling