Agentic AI with Java and Gemini

Build a Java AI agent that autonomously chains tools to answer complex questions.

Introduction

30 Second Summary

Think about how you ask a colleague a tricky question and they just know to pull up a calculator, check a database, or search the web. They reason about which tool to use without you spelling it out.

In this project, you will build a Java AI agent that autonomously decides which tools to call and chains multiple reasoning steps together to answer complex questions. You will use LangChain4j and Google Gemini to create the core pattern behind every modern AI agent system.

What You'll Build

You type a question like "What's the population of Tokyo in scientific notation?" and watch your agent decide on its own to call a city lookup tool, then a math formatter, then compose the final answer in real-time.

By the end of this project, you'll have:

  • An AI agent that autonomously picks the right tool for any question you throw at it, from math to city lookups to string formatting.
  • Multi-step reasoning where the agent chains tools together in sequence to solve problems no single tool can answer.
  • An interactive conversation loop with memory, so the agent remembers what you discussed and builds on previous answers across turns.
  • Secret Mission: Add a web search tool that fetches real data from an API, giving your agent access to live information.

Are there any prerequisites?

This project assumes you have Java development experience and a working setup with IntelliJ IDEA Community Edition, JDK 17+, and Maven installed. You do not need a credit card. The Google Gemini API is free to use.

Before We Start

Before you dive in, take a moment to think about what you are building and why it matters. You are about to create an AI agent that makes its own decisions about which tools to call and how to chain reasoning steps together.

Set Up Your Project and Gemini API Key

Before your agent can reason, act, or call tools, it needs a foundation. You need a Java project with the right libraries to talk to an LLM, and a working API key so Gemini actually responds.

In this step, you will create a Maven project in IntelliJ IDEA, pull in the LangChain4j framework, and prove that your code can reach Google Gemini by sending it a simple message.

In this step, get ready to:
  • Create a Maven project with LangChain4j dependencies.
  • Obtain a free Gemini API key and configure it in IntelliJ.
  • Send your first message to Gemini and see it respond.
Create a new Maven project in IntelliJ IDEA
  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type IntelliJ IDEA and press Enter to open it.
  • If the Welcome screen appears, click New Project. If you already have a project open, go to File then New then Project.
  • In the Name field, type ai-agent.
  • Select Java on the left if it is not already selected.
  • Set Build system to Maven.
  • From the JDK dropdown, select your JDK 17 or higher. If it is not listed, click Add JDK from Disk and point to your JDK installation directory.
  • Expand Advanced Settings at the bottom of the dialog.
  • Set GroupId to com.example and ArtifactId to ai-agent.
  • Click Create.

IntelliJ generates a project with a pom.xml file and the standard src/main/java directory structure. You should see the project tree in the Project tool window on the left.

Don't see the project tree?

Make sure IntelliJ has finished loading. Look for a progress bar at the bottom of the window. Once it completes, the project tree appears.

If your JDK is not recognised, go to File then Project Structure then Project and set the SDK to your JDK 17+ installation.

help me set up a Maven project in IntelliJ IDEA with JDK 17

Add LangChain4j dependencies to your project

Your pom.xml currently has just the project coordinates and compiler settings. You need to add LangChain4j libraries so your code can talk to Gemini.

  • Open pom.xml in the editor by double-clicking it in the project tree.
  • Find the closing </properties> tag. Add the following dependency management and dependencies blocks below it, before the closing </project> tag:
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>dev.langchain4j</groupId>
                <artifactId>langchain4j-bom</artifactId>
                <version>1.18.0</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-google-ai-gemini</artifactId>
        </dependency>
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j</artifactId>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>2.0.18</version>
        </dependency>
    </dependencies>

What do these dependencies do?

  • langchain4j-bom is a Bill of Materials that locks all LangChain4j modules to version 1.18.0 so you do not need to repeat version numbers.
  • langchain4j-google-ai-gemini provides the GoogleAiGeminiChatModel class that connects to the Gemini API.
  • langchain4j is the core framework with AI Services, tool annotations, and memory.
  • slf4j-simple gives LangChain4j a simple logger so you can see what is happening under the hood.
  • Save pom.xml with Cmd+S (macOS) or Ctrl+S (Windows).
  • IntelliJ shows a small Maven sync notification at the top of the editor. Click Load Maven Changes (or the refresh icon) to download the dependencies.
  • Wait for the progress bar at the bottom to finish. Once it completes, open the External Libraries node in the project tree. You should see entries like langchain4j-google-ai-gemini and langchain4j listed there.

Dependencies not downloading?

Make sure you are connected to the internet. If IntelliJ shows red underlines in pom.xml, right-click the file and select Maven then Reload Project.

help me fix Maven dependency resolution errors in IntelliJ IDEA

✔️ Awesome, I've got everything!

Great. Double check you have saved pom.xml and that Maven sync completed successfully.

ⓧ I'd like to double check the full code

Your complete pom.xml should look like this. Scroll up and confirm three sections are present in order: the <properties> block, the <dependencyManagement> block with the BOM, and the <dependencies> block with three entries (langchain4j-google-ai-gemini, langchain4j, and slf4j-simple).

Get your free Gemini API key

Your project needs an API key so it can authenticate with Google's Gemini model. The free tier gives you 1500 requests per day with no credit card required.

  • Open your browser and navigate to https://ai.google.dev.
  • Sign in with your Google account.
  • Click Get API key in the top navigation or left sidebar.
  • Click Create API key.
  • Copy the key that appears. Save it somewhere safe (a text file or password manager). You will not be able to view it again after closing the dialog.

Why is this free?

Google Gemini's free tier allows 1500 requests per day on flash models at no cost and does not require a credit card. This project stays well within that limit.

Now you need to make the key available to your Java code. You will configure it as an environment variable in IntelliJ's Run Configuration so it is never hardcoded in your source files.

  • In IntelliJ, go to Run in the top menu bar, then click Edit Configurations.
  • Click the + button in the top-left of the dialog, then select Application.
  • In the Name field, type Main.
  • In the Main class field, type com.example.Main.
  • In the Environment variables field, type GEMINI_AI_KEY= followed by your API key (paste it directly after the equals sign with no spaces).
  • Click Apply, then OK.

Keep your key out of code

By storing the key in the Run Configuration (not in your source code), you avoid accidentally committing it to version control. The code reads it at runtime with System.getenv("GEMINI_AI_KEY").

Create Main.java and verify the connection

Time to write your first line of AI code. You will create a class that connects to Gemini and sends a test message.

  • In the project tree, expand src then main then java.
  • Right-click the java folder, select New then Package, and type com.example. Press Enter.
  • Right-click the new com.example package, select New then Java Class, and type Main. Press Enter.
  • Replace the contents of Main.java with the following code:
package com.example;

import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.googleai.GoogleAiGeminiChatModel;

public class Main {

    public static void main(String[] args) {
        // Build a chat model pointed at Gemini 2.5 Flash
        ChatModel gemini = GoogleAiGeminiChatModel.builder()
                .apiKey(System.getenv("GEMINI_AI_KEY"))
                .modelName("gemini-2.5-flash")
                .temperature(0.0)
                .build();

        // Send a test message and print the response
        String response = gemini.chat("Hello!");
        System.out.println(response);
    }
}

What does this code do?

  • GoogleAiGeminiChatModel.builder() creates a connection to the Gemini API using your key and the gemini-2.5-flash model.
  • System.getenv("GEMINI_AI_KEY") reads the API key from the environment variable you just configured.
  • temperature(0.0) makes responses deterministic. The model picks the most likely answer every time.
  • gemini.chat("Hello!") sends the message and returns Gemini's reply as a string.
  • Save Main.java with Cmd+S (macOS) or Ctrl+S (Windows).

Before you run this, what do you think Gemini will reply to a simple "Hello!" message? Make a prediction.

  • Click the green play triangle in the gutter next to public static void main, then select Run 'Main.main()'.

What should I see?

The Run panel opens at the bottom of IntelliJ. After a moment, Gemini's response prints in the console. You should see a friendly greeting like "Hello! How can I help you today?" or similar.

Seeing an error instead of a greeting?

  • If you see a NullPointerException or a message about a missing API key, open Run then Edit Configurations and confirm the Environment variables field contains GEMINI_AI_KEY= followed by your key with no spaces around the equals sign.
  • If IntelliJ used a different run configuration (not the Main one you created), select it from the Run widget dropdown at the top and re-run.
  • If you see a compilation error about missing imports, make sure Maven sync completed successfully. Right-click pom.xml and select Maven then Reload Project.

help me fix my Gemini API connection in a LangChain4j Java project

✔️ Awesome, I've got everything!

Great. Confirm you saved Main.java and that Gemini's response printed in the console.

ⓧ I'd like to double check the full code

Your Main.java should look exactly like this:

package com.example;

import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.googleai.GoogleAiGeminiChatModel;

public class Main {

    public static void main(String[] args) {
        ChatModel gemini = GoogleAiGeminiChatModel.builder()
                .apiKey(System.getenv("GEMINI_AI_KEY"))
                .modelName("gemini-2.5-flash")
                .temperature(0.0)
                .build();

        String response = gemini.chat("Hello!");
        System.out.println(response);
    }
}

Your project is wired up and Gemini is talking back. Next up, you will turn this simple one-shot call into a proper AI Service that can hold a conversation.

Build a Simple Chatbot (No Tools)

Your Gemini connection works. You can send a message and get a response back. But can the LLM handle real tasks like precise math or factual lookup?

Before adding tools, you need to see exactly where the LLM falls short on its own. In this step, you will ask it questions it cannot answer correctly and witness the failure firsthand.

In this step, get ready to:
  • Create an AI Service interface for cleaner LLM interactions.
  • Ask the LLM questions it cannot answer correctly on its own.
Create the Assistant interface

LangChain4j provides an AI Services API that lets you define a plain Java interface and LangChain4j handles wiring it to the LLM behind the scenes. You declare the method signature and the framework does the rest.

  • In IntelliJ, right-click the com.example package in the src/main/java folder.
  • Select New, then Java Class.
  • Name it Assistant and change the kind from Class to Interface.
  • Replace the file contents with this code:
package com.example;

public interface Assistant {
    String chat(String message);
}

What does this interface do?

You are declaring a contract: any class that implements Assistant must have a chat method that takes a String and returns a String. LangChain4j's AI Services API will generate the implementation at runtime, routing every call to your Gemini model.

  • Save Assistant.java.
Wire the Assistant to Gemini and ask it hard questions

Now you will connect the Assistant interface to your Gemini model and ask it two questions that LLMs typically get wrong: a precise math calculation and a factual data lookup.

  • Open Main.java in the editor.
  • Add this import below the existing imports at the top of the file:
import dev.langchain4j.service.AiServices;
  • Find these two lines at the bottom of main():
String response = gemini.chat("Hello!");
System.out.println(response);
  • Replace those two lines with the following code:
// Build an AI Service that wires Assistant to the Gemini model
Assistant assistant = AiServices.builder(Assistant.class)
        .chatModel(gemini)
        .build();

// Ask a precise math question
System.out.println(assistant.chat("What is the square root of 475695037565?"));
// Ask a factual question about current data
System.out.println(assistant.chat("What is the current population of Tokyo?"));

What does this code do?

  • AiServices.builder(Assistant.class) tells LangChain4j to generate an implementation of your Assistant interface at runtime.
  • .chatModel(gemini) connects the interface to your Gemini model so every call to chat() routes through the LLM.
  • The two println calls ask the LLM a math question and a factual question to test its raw ability.
  • Save Main.java.

Before you run this, take a moment to predict: do you think the LLM will get both answers exactly right?

  • Run the Main class by clicking the green play button next to public static void main.

Look at the console output. The square root answer is approximate or wrong. The actual answer is 689706.486532, but the LLM likely gives a rounded or incorrect value. For the Tokyo population, it either hallucinates a number or hedges with uncertainty.

This is exactly the result you should see. The LLM alone is not enough for precise calculations or factual data retrieval.

Why does the LLM get these wrong?

LLMs generate text by predicting the most likely next token. They do not actually compute math or look up live data. For the square root, the model guesses plausible-looking digits. For Tokyo's population, it recalls training data that may be outdated or imprecise.

This gap is exactly what tools fix. In the next step, you will give the agent access to a real calculator and a data lookup method.

Seeing an error instead of an answer?

  • If you see a compilation error mentioning Assistant, make sure Assistant.java is saved in the same com.example package as Main.java.
  • If you see an API key error, confirm your GEMINI_AI_KEY environment variable is still set in your run configuration.
  • If you see a missing import error for AiServices, check that the import dev.langchain4j.service.AiServices; line is at the top of Main.java.

Help me debug my AI Services setup in LangChain4j

✔️ Awesome, I've got everything!

Great. Double check you have both Assistant.java and Main.java saved.

ⓧ I'd like to double check the full code

Here is the full Assistant.java:

package com.example;

public interface Assistant {
    String chat(String message);
}

Here is the full Main.java:

package com.example;

import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.googleai.GoogleAiGeminiChatModel;
import dev.langchain4j.service.AiServices;

public class Main {

    public static void main(String[] args) {
        ChatModel gemini = GoogleAiGeminiChatModel.builder()
                .apiKey(System.getenv("GEMINI_AI_KEY"))
                .modelName("gemini-2.5-flash")
                .temperature(0.0)
                .build();

        Assistant assistant = AiServices.builder(Assistant.class)
                .chatModel(gemini)
                .build();

        System.out.println(assistant.chat("What is the square root of 475695037565?"));
        System.out.println(assistant.chat("What is the current population of Tokyo?"));
    }
}

You have proven the gap. The LLM alone cannot do precise math or retrieve factual data reliably. Next up, you will fix this by giving your agent tools it can call on its own.

Give Your Agent Tools

In the last step, your assistant gave a wrong answer for the square root and an uncertain answer about Tokyo's population.

The LLM on its own cannot do precise math or look up factual data. Now you fix that by giving it tools. In LangChain4j, a tool is a regular Java method annotated with @Tool. The LLM reads the tool descriptions, decides which one to call, and uses the result to answer your question.

In this step, get ready to:
  • Create a Calculator tool class with annotated math methods.
  • Create a CityInformationTool class with hardcoded city data.
  • Wire both tools into the AI Service and watch the agent answer correctly.
Create the Calculator tool class

Each tool method gets two annotations. @Tool describes what the method does so the LLM knows when to use it. @P describes each parameter so the LLM knows what values to pass.

  • In the src/main/java/com/example package, create a new Java class called Calculator.
  • Right-click the com.example package in the Project panel, then select New and Java Class.
  • Name it Calculator and press Enter.
  • Replace the generated content with the basic arithmetic methods by pasting this code:
package com.example;

import dev.langchain4j.agent.tool.P;
import dev.langchain4j.agent.tool.Tool;

public class Calculator {

    @Tool("Adds two numbers together")
    public double add(@P("first number") double a, @P("second number") double b) {
        return a + b;
    }

    @Tool("Subtracts the second number from the first")
    public double subtract(@P("first number") double a, @P("second number") double b) {
        return a - b;
    }

    @Tool("Multiplies two numbers together")
    public double multiply(@P("first number") double a, @P("second number") double b) {
        return a * b;
    }
}

What does this code do?

Each method is a simple arithmetic operation that returns a double. The @Tool string tells the LLM what the method does in plain English.

The @P annotations on each parameter give the LLM a description of what to pass in. Without these, the LLM would only see generic names like a and b.

  • Now add two more methods before the closing } of the class. Place them directly below the multiply method:
    @Tool("Divides the first number by the second")
    public double divide(@P("first number") double a, @P("second number") double b) {
        if (b == 0) throw new IllegalArgumentException("Cannot divide by zero");
        return a / b;
    }

    @Tool("Returns the square root of a given number")
    public double squareRoot(@P("the number to find the square root of") double x) {
        return Math.sqrt(x);
    }

What does this code do?

The divide method includes a zero-division guard so the agent never causes a crash. The squareRoot method uses Math.sqrt to return a precise answer for the exact question your assistant got wrong.

  • Save Calculator.java.
  • Verify it compiles by clicking Build in the top menu bar, then Build Project. You should see no errors in the Build output panel at the bottom.

Seeing a compilation error?

  • Make sure the imports at the top match exactly: dev.langchain4j.agent.tool.P and dev.langchain4j.agent.tool.Tool.
  • Confirm that your pom.xml has both the langchain4j-google-ai-gemini and langchain4j dependencies. The @Tool annotation lives in the core langchain4j artifact.
  • Check that the divide and squareRoot methods are inside the class braces, not after the closing }.

Help me fix my Calculator.java compilation error

✔️ Awesome, I've got everything!

Great. Make sure you have saved Calculator.java before continuing.

ⓧ I'd like to double check the full code

Your Calculator.java is built from the two code chunks above. Scroll up and confirm you have:

  • The package declaration, imports, and add/subtract/multiply methods from chunk 1.
  • The divide and squareRoot methods from chunk 2, placed before the final closing }.
Create the city information tool

Your agent also failed the population question. This tool gives it access to hardcoded city data so it can return real facts instead of guessing.

  • Create a new Java class in the com.example package called CityInformationTool.
  • Replace the generated content with this code:
package com.example;

import dev.langchain4j.agent.tool.P;
import dev.langchain4j.agent.tool.Tool;

public class CityInformationTool {

    @Tool("Returns information about a city including population, country, and timezone")
    public String getCityInfo(@P("name of the city") String city) {
        return switch (city.toLowerCase()) {
            case "tokyo" -> "Tokyo: population 13,960,000, country Japan, timezone Asia/Tokyo";
            case "paris" -> "Paris: population 2,161,000, country France, timezone Europe/Paris";
            case "london" -> "London: population 8,982,000, country United Kingdom, timezone Europe/London";
            case "new york" -> "New York: population 8,336,000, country United States, timezone America/New_York";
            default -> "Sorry, I don't have information about " + city;
        };
    }
}

What does this code do?

This single method uses a switch expression to return population, country, and timezone data for known cities. The @Tool description tells the LLM this is a city lookup tool.

When the agent receives a question about Tokyo's population, it reads this description, decides to call getCityInfo, passes "Tokyo" as the argument, and uses the returned string to compose its answer.

  • Save CityInformationTool.java.

✔️ Awesome, I've got everything!

Great. Make sure CityInformationTool.java is saved.

ⓧ I'd like to double check the full code

package com.example;

import dev.langchain4j.agent.tool.P;
import dev.langchain4j.agent.tool.Tool;

public class CityInformationTool {

    @Tool("Returns information about a city including population, country, and timezone")
    public String getCityInfo(@P("name of the city") String city) {
        return switch (city.toLowerCase()) {
            case "tokyo" -> "Tokyo: population 13,960,000, country Japan, timezone Asia/Tokyo";
            case "paris" -> "Paris: population 2,161,000, country France, timezone Europe/Paris";
            case "london" -> "London: population 8,982,000, country United Kingdom, timezone Europe/London";
            case "new york" -> "New York: population 8,336,000, country United States, timezone America/New_York";
            default -> "Sorry, I don't have information about " + city;
        };
    }
}
Wire tools into your agent and test

The tools exist but your agent does not know about them yet. You need to pass them into the AI Service builder so the LLM can discover and call them.

  • In Main.java, find the AI Service builder block:
        Assistant assistant = AiServices.builder(Assistant.class)
                .chatModel(gemini)
                .build();
  • Add a .tools() call between .chatModel(gemini) and .build() so the block looks like this:
        Assistant assistant = AiServices.builder(Assistant.class)
                .chatModel(gemini)
                .tools(new Calculator(), new CityInformationTool())
                .build();

What does this line do?

The .tools() method registers tool instances with the AI Service. LangChain4j scans each instance for @Tool-annotated methods and sends their descriptions to the LLM.

The LLM then autonomously decides which tool to call based on the user's question. You never tell it which tool to use. It reads the descriptions and makes its own choice.

  • Save Main.java.

Before you run this, think about what you expect to see. The same two questions from Step 2 are being asked. Will the answers be different now that the agent has tools?

  • Run the Main class by clicking the green play button next to the main method.

The console now prints the exact square root (689706.486532) and the correct Tokyo population (13,960,000) from your tool. The agent decided on its own to call squareRoot and getCityInfo.

What is the agent loop?

What just happened is the core of every AI agent. The LLM received your question, reasoned that it needed a tool, called that tool, observed the result, then composed a final answer.

This reason-act-observe loop is what separates an agent from a simple chatbot. The chatbot from Step 2 could only guess. This agent takes action.

Still seeing wrong answers?

  • Make sure you added the .tools(new Calculator(), new CityInformationTool()) line before .build() in the AI Service builder.
  • Check that your Calculator and CityInformationTool classes are in the same package (com.example) as Main.java.
  • Confirm there are no red underlines in IntelliJ. If there are, try Build > Rebuild Project to clear stale caches.

Help me debug why my agent is not calling tools

Your chatbot just became an agent. Next up, you will push it further by asking compound questions that require chaining multiple tools together in sequence.

Multi-Step Reasoning with Chained Tool Calls

Your agent can call individual tools to answer questions it could not handle before. But real-world problems rarely map to a single tool call.

In this step, you'll add a string utility tool and ask your agent a compound question that forces it to chain two tools together in sequence. You'll also enable request/response logging so you can watch the agent's multi-step reasoning unfold in the console.

In this step, get ready to:
  • Create a StringUtilityTool with text manipulation and formatting methods.
  • Wire the new tool into your AI Service and enable request/response logging.
  • Test a compound question that requires chaining multiple tools together.
Create the StringUtilityTool class

Your agent already has a Calculator and a CityInformationTool. Now you'll give it the ability to manipulate strings and format numbers. This is the tool that will let the agent transform raw data from other tools into whatever format you ask for.

  • In IntelliJ IDEA, right-click on the com.example package in the Project panel (the same package where your other tool classes live).
  • Select New, then Java Class.
  • Name it StringUtilityTool and press Enter.
  • Replace the contents of the new file with this code:
package com.example;

import dev.langchain4j.agent.tool.P;
import dev.langchain4j.agent.tool.Tool;

public class StringUtilityTool {

    @Tool("Converts text to uppercase")
    public String toUpperCase(@P("text to convert") String text) {
        return text.toUpperCase();
    }

    @Tool("Reverses a string")
    public String reverseString(@P("text to reverse") String text) {
        return new StringBuilder(text).reverse().toString();
    }

    @Tool("Formats a number in scientific notation")
    public String formatAsScientificNotation(@P("the number to format") double number) {
        return String.format("%E", number);
    }
}

What does this code do?

Each method is annotated with @Tool and @P, just like your Calculator and CityInformationTool.

  • toUpperCase converts any text to uppercase letters.
  • reverseString flips text backwards using a StringBuilder.
  • formatAsScientificNotation takes a number and returns it in scientific notation (like 1.396E7). This is the method the agent will use to format population data.
  • Save StringUtilityTool.java.
Wire the tool and enable logging

The StringUtilityTool exists, but your agent does not know about it yet. You need to register it in the AI Service builder. You'll also turn on request/response logging so you can see exactly which tools the agent calls and in what order.

  • Open Main.java in IntelliJ IDEA.
  • Find this line in the model builder:
                .temperature(0.0)
                .build();
  • Add .logRequestsAndResponses(true) between .temperature(0.0) and .build(), so the builder looks like this:
        ChatModel gemini = GoogleAiGeminiChatModel.builder()
                .apiKey(System.getenv("GEMINI_AI_KEY"))
                .modelName("gemini-2.5-flash")
                .temperature(0.0)
                .logRequestsAndResponses(true)
                .build();

What does logging do?

The .logRequestsAndResponses(true) setting prints the full conversation between your code and Gemini to the console. You'll see every tool call the agent makes, including the arguments it passes and the results it receives back.

  • Now find the .tools() line:
                .tools(new Calculator(), new CityInformationTool())
  • Add new StringUtilityTool() to the tools list, so the line looks like this:
                .tools(new Calculator(), new CityInformationTool(), new StringUtilityTool())
  • Finally, replace the two System.out.println lines at the bottom of main() with a single compound question:
        System.out.println(assistant.chat("What is the population of Tokyo? Format that number in scientific notation."));

Why this question?

This question is deliberately designed so that no single tool can answer it alone. The agent must first call CityInformationTool to get Tokyo's population, then call StringUtilityTool's formatAsScientificNotation method to convert that number. If it chains them correctly, you'll know your agent can plan multi-step actions.

  • Save Main.java.
Run and observe chained tool calls

Before you run this, think about what you expect to happen. The agent receives a question with two parts. Do you think it will call one tool or two? In what order?

  • Run the Main class by clicking the green play button next to the main method.

In the console output, you'll see the agent make two sequential tool calls. First it calls getCityInfo with "tokyo" to get the population. Then it calls formatAsScientificNotation with the population number. The final printed answer contains the population in scientific notation (something like 1.396E7).

What is happening under the hood?

This is the agent loop in action. The LLM reasons about what it needs, acts by calling a tool, observes the result, then loops again to decide the next action. It repeats this cycle until it has enough information to compose a final answer.

The logging output shows this loop. You can see the agent sending a tool call request, receiving the response, and then deciding to make another call. It planned and executed a two-step strategy entirely on its own.

Not seeing two tool calls in the log?

  • Make sure you added .logRequestsAndResponses(true) to the GoogleAiGeminiChatModel.builder() chain, not the AiServices builder.
  • Confirm new StringUtilityTool() is inside the .tools() call. If it is missing, the agent cannot discover the formatting method.
  • If the agent answers without using tools (just approximating), double-check that your StringUtilityTool.java compiles with no errors. A compilation failure silently removes the tool from the agent's view.

Help me debug why my agent is not chaining tools together.

✔️ Awesome, I've got everything!

Great. Double check you've saved both files before moving on.

ⓧ I'd like to double check the full code

Here is the full StringUtilityTool.java:

package com.example;

import dev.langchain4j.agent.tool.P;
import dev.langchain4j.agent.tool.Tool;

public class StringUtilityTool {

    @Tool("Converts text to uppercase")
    public String toUpperCase(@P("text to convert") String text) {
        return text.toUpperCase();
    }

    @Tool("Reverses a string")
    public String reverseString(@P("text to reverse") String text) {
        return new StringBuilder(text).reverse().toString();
    }

    @Tool("Formats a number in scientific notation")
    public String formatAsScientificNotation(@P("the number to format") double number) {
        return String.format("%E", number);
    }
}

Here is the full Main.java:

package com.example;

import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.googleai.GoogleAiGeminiChatModel;
import dev.langchain4j.service.AiServices;

public class Main {

    public static void main(String[] args) {
        ChatModel gemini = GoogleAiGeminiChatModel.builder()
                .apiKey(System.getenv("GEMINI_AI_KEY"))
                .modelName("gemini-2.5-flash")
                .temperature(0.0)
                .logRequestsAndResponses(true)
                .build();

        Assistant assistant = AiServices.builder(Assistant.class)
                .chatModel(gemini)
                .tools(new Calculator(), new CityInformationTool(), new StringUtilityTool())
                .build();

        System.out.println(assistant.chat("What is the population of Tokyo? Format that number in scientific notation."));
    }
}

Your agent just proved it can plan and execute a multi-step strategy. Next up, you'll give it memory and a conversation loop so it can chain tools across multiple turns of dialogue.

Build an Interactive Agent Loop

Your agent can chain multiple tools and reason through complex queries. But right now it only handles one hardcoded question, prints the answer, and exits.

A real agent runs in a conversation loop where each question can build on previous context. In this step, you will add memory and an interactive loop that turns your one-shot demo into a working conversational agent.

In this step, get ready to:
  • Add chat memory so the agent remembers previous messages.
  • Create an interactive loop that reads your questions and prints agent responses.
  • Test multi-turn conversations that chain tools across multiple messages.
Add chat memory to your agent

Right now, every call to assistant.chat() is independent. The agent has no idea what you asked before. ChatMemory solves this by storing a rolling window of past messages that gets passed to the LLM with every new request.

  • In Main.java, find the import section at the top of the file.
  • Add this import below the existing imports:
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
  • Now find the AiServices.builder section. It currently looks like this:
        Assistant assistant = AiServices.builder(Assistant.class)
                .chatModel(gemini)
                .tools(new Calculator(), new CityInformationTool(), new StringUtilityTool())
                .build();
  • Add a .chatMemory() line between .chatModel(gemini) and .tools(...), so it looks like this:
        Assistant assistant = AiServices.builder(Assistant.class)
                .chatModel(gemini)
                .chatMemory(MessageWindowChatMemory.withMaxMessages(20))
                .tools(new Calculator(), new CityInformationTool(), new StringUtilityTool())
                .build();

What does MessageWindowChatMemory do?

MessageWindowChatMemory.withMaxMessages(20) keeps a sliding window of the last 20 messages (both yours and the agent's responses). Every time you send a new message, the full conversation history is included in the request to Gemini.

This means the LLM can reference information from earlier in the conversation. Once the window fills up, the oldest messages drop off automatically.

  • Save Main.java and run it using the green play button in IntelliJ.

You should see the same chained-tool answer from before (Tokyo's population in scientific notation). The program compiles and runs correctly with the new memory wired in.

Seeing a compilation error?

  • Make sure the import line reads exactly import dev.langchain4j.memory.chat.MessageWindowChatMemory; with no typos.
  • Check that .chatMemory() is chained between .chatModel(gemini) and .tools(...) with dots connecting each line.

Why is my agent not compiling after adding chat memory?

Create a Scanner-based conversation loop

The agent has memory now, but it still only answers one hardcoded question. You need an interactive loop that continuously reads your input, sends it to the agent, and prints the response.

  • In Main.java, add this import alongside the other imports at the top of the file:
import java.util.Scanner;
  • Now find the last line inside your main method. It currently looks like this:
        System.out.println(assistant.chat("What is the population of Tokyo? Format that number in scientific notation."));
  • Delete that entire line and replace it with the following interactive loop:
        // Create a scanner to read user input from the terminal
        Scanner scanner = new Scanner(System.in);
        System.out.println("AI Agent ready! Type your questions (type 'exit' to quit):");
        System.out.println("---");

        // Loop until the user types 'exit'
        while (true) {
            System.out.print("You: ");
            String input = scanner.nextLine();
            if (input.equalsIgnoreCase("exit")) {
                System.out.println("Goodbye!");
                break;
            }
            // Pass the user's message to the agent and print the response
            String response = assistant.chat(input);
            System.out.println("Agent: " + response);
            System.out.println("---");
        }

        scanner.close();

What does this loop do?

  • Scanner scanner = new Scanner(System.in) reads text input from your terminal.
  • The while (true) loop keeps asking for input until you type exit.
  • Each message goes through assistant.chat(input), which triggers the full agent loop: reasoning, tool selection, execution, and response.
  • Because you added ChatMemory, every new message includes the history of previous turns, so the agent can reference earlier answers.
  • Save Main.java.
  • Run the program using the green play button.

Your terminal should display AI Agent ready! Type your questions (type 'exit' to quit): and wait for your input. The program is now running interactively.

Program exits immediately without waiting for input?

  • Make sure you deleted the old System.out.println(assistant.chat(...)) line entirely. If it still exists above the loop, the program may hit an error and exit before reaching the Scanner.
  • Verify your while (true) loop is inside the main method, after the assistant builder.

Why does my Java program exit without waiting for Scanner input?

Test multi-turn conversations

The real test of an agent with memory is whether it can reference information from a previous turn and combine it with a tool call in a new turn.

  • In the running program, type the following at the You: prompt and press Enter:
What is the population of Paris?

The agent should call the getCityInfo tool and return Paris's population (2,161,000). You can see the tool call in the log output.

Now for the real test. Before you type the next message, think about what you expect the agent to do. Will it remember what city you just asked about?

  • Type this follow-up message:
Now multiply that by 3

The agent remembers that "that" refers to Paris's population from the previous turn. It calls the Calculator multiply tool with 2,161,000 and 3, returning 6,483,000. You can verify this in the request/response logs showing the agent chose the multiply tool autonomously.

What just happened?

This is the full agent loop in action. The agent received an ambiguous pronoun ("that"), resolved it using conversation history, identified the correct tool to use, and composed a final answer. No code told it what order to do things in.

This reason-act-observe pattern is the foundation of every modern AI agent framework. The LLM reasons about what to do, acts by calling a tool, observes the result, and decides whether to loop again or return a final answer.

  • Type exit to stop the program.

✔️ Awesome, I've got everything!

Great. Double check you have saved Main.java and your agent responds interactively with context across turns.

ⓧ I'd like to double check the full code

Here is the complete Main.java after all the changes in this step. Scroll up and confirm each section is present in order:

  • The MessageWindowChatMemory import and the Scanner import at the top.
  • The Gemini model builder (unchanged from before).
  • The AI Service builder with .chatMemory(MessageWindowChatMemory.withMaxMessages(20)) added.
  • The Scanner-based while (true) loop with exit handling.
  • scanner.close() at the end of the method.

You have a fully working interactive AI agent. It reasons about your questions, picks the right tools, chains them together, and remembers what you said earlier in the conversation. This is the same core pattern that powers production agent systems. Next up, stretch your skills with the Secret Mission.

Secret mission

Add a Live Web Search Tool

Your agent is powerful, but it only knows what you hardcode into it. What if it could reach out to the internet and fetch live data on its own? In this mission, you'll give your agent a real superpower: the ability to pull live weather data from the web using a free API.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally and uses only the Google Gemini free tier API. There are no ongoing costs or cloud resources to worry about.

Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • The ai-agent project folder on your Desktop.
  • The GEMINI_AI_KEY environment variable.
  • A Google AI Studio API key (free tier, no charges).

Keep everything running

No action needed. Choose this if you plan to keep experimenting with your AI agent.

  • Your Gemini API key is free and does not expire. There are no charges for keeping it active.
  • The project folder stays on your Desktop ready for you to reopen in IntelliJ IDEA at any time.
  • Maven dependencies are cached in your local .m2 folder and take up minimal disk space.

Pause - I'll come back to this later

Stop any running processes to free up memory. Your project files and API key stay intact for later.

  • If your agent is still running in the terminal, press Ctrl+C to stop it.
  • Close the project in IntelliJ IDEA to free up memory.
  • Your GEMINI_AI_KEY environment variable and project files remain in place. When you return, just reopen the project and run Main.java again.

Delete - I don't want to use this again

Remove all project files and clean up your environment completely.

  • Delete the project folder by running this command in your terminal:

macOS

rm -rf ~/Desktop/ai-agent

Windows

rmdir /s /q %USERPROFILE%\Desktop\ai-agent
  • Remove the GEMINI_AI_KEY environment variable. If you added it to your shell profile (.zshrc or .bashrc on macOS/Linux, or System Environment Variables on Windows), remove the line that sets it.
  • To delete your Gemini API key, go to https://aistudio.google.com/apikey, find your key in the list, and click the trash icon to revoke it.

Nice Work!

Nice Work!

You just built a fully autonomous AI agent in Java. Your agent reasons about problems, decides which tools to call, chains multiple actions together, and remembers what you talked about across turns.

You've learned how to:

  • Build an AI agent in Java using LangChain4j that autonomously decides which tools to call and chains them together to solve multi-step problems.
  • Create custom tools with the @Tool annotation pattern and watch the agent discover, select, and sequence them without any hardcoded logic.
  • Wire up conversational memory with MessageWindowChatMemory so the agent maintains context across multiple interactions and chains tools across turns.
  • Secret Mission: Connected your agent to a live API, giving it access to real-time weather data from the internet.

Ready to quiz yourself?