AI Weather Assistant with Spring AI

Build a Spring Boot REST API that uses AI tool calling to answer weather questions.

Introduction

30 Second Summary

Imagine asking your phone "Do I need an umbrella today?" and getting a helpful answer that actually checked the latest weather. Behind the scenes, an AI model decided it needed weather data, fetched it, and combined it with its own knowledge to give you a recommendation.

In this project, you will build a Spring Boot REST API that integrates with OpenAI through Spring AI. The AI model will autonomously call your Java methods to fetch weather data, then use that data to generate helpful recommendations.

What You'll Build

A Spring Boot REST API that acts as an intelligent weather assistant, using Spring AI's ChatClient to connect OpenAI with your Java application logic through tool calling and structured output.

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

  • A Spring AI application integrated with Spring Boot using auto-configuration and the ChatClient fluent API.
  • A tool calling system where the AI model autonomously invokes your @Tool-annotated Java methods to fetch live weather data.
  • A structured output endpoint that maps AI responses directly to Java records, returning clean JSON to your API consumers.
  • Secret Mission: Add conversation memory so your assistant remembers previous questions in a session.

Are there any prerequisites?

You should have basic familiarity with Java and Spring Boot. Understanding REST controllers, dependency injection, and application.properties is helpful. No prior AI or machine learning experience is needed.

Before We Start

To build an AI-powered weather assistant, you need a few tools working together. Spring Boot handles the web server, Spring AI connects your app to OpenAI, and Maven manages the project dependencies.

In this step, you will install the JDK and Maven, create an OpenAI account, and generate the starter project from Spring Initializr.

In this step, get ready to:
  • Install JDK 17+ and Maven on your machine.
  • Create an OpenAI account and generate an API key.
  • Generate a Spring Boot project with Spring AI dependencies.
Install JDK 17+ and Maven

Spring Boot 3.5.14 requires Java 17 or higher to run. The JDK (Java Development Kit) includes the compiler and runtime you need.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal and press Enter to open it.
  • Check whether Java is installed by running this command:
java -version

✔️ I see version 17 or higher

You are all set. Your output shows something like openjdk version "17.x.x" or higher. Move on to the Maven check below.

ⓧ I see an older version

Your system has an older Java version installed. You need version 17 or higher for this project.

  • Go to adoptium.net/temurin/releases.
  • Select JDK 17 - LTS from the version dropdown.
  • Download the .pkg (macOS) or .msi (Windows) installer.
  • Run the installer and accept the defaults.
  • On the Custom Setup screen (Windows), check the box to update the JAVA_HOME environment variable.
  • Close and reopen your terminal, then run java -version again to confirm the new version shows 17 or higher.

macOS may still resolve to an older version

If macOS still shows an older version after installing, run /usr/libexec/java_home -V to list all installed JDKs. Set the correct one with export JAVA_HOME=$(/usr/libexec/java_home -v 17) in your terminal.

ⓧ Command not found

Java is not installed on your machine. You will install it from Adoptium, the official open-source JDK distribution.

  • Go to adoptium.net/temurin/releases.
  • Select JDK 17 - LTS from the version dropdown.
  • Download the .pkg (macOS) or .msi (Windows) installer.
  • Run the installer and accept the defaults.
  • On the Custom Setup screen (Windows), check the box to update the JAVA_HOME environment variable.
  • Close and reopen your terminal, then run java -version to confirm it shows version 17 or higher.
  • Check whether Maven is installed by running this command:
mvn -version

✔️ I see a version number

Maven is ready. You should see output showing Apache Maven 3.x.x along with your Java version. Move on to the next substep.

ⓧ Command not found

Maven is not installed. Install it using a package manager for your operating system.

macOS

  • Install Maven with Homebrew by running:
brew install maven
  • After the install completes, run mvn -version to confirm Maven is available.

Windows

  • Install Maven with Chocolatey by running:
choco install maven
  • Close and reopen your terminal, then run mvn -version to confirm Maven is available.

What is Maven?

Maven is a build tool for Java projects. It reads a pom.xml file to know which libraries your project needs, downloads them automatically, and compiles your code.

Finally, make sure you have an IDE ready. You can use IntelliJ IDEA (Community Edition is free) or VS Code with the Extension Pack for Java.

Create an OpenAI account and generate an API key

Spring AI sends your prompts to the OpenAI API. To authenticate those requests, you need an API key from your OpenAI account.

  • Open your browser and go to platform.openai.com.
  • Sign up for an account (or sign in if you already have one).
  • Once signed in, click the gear icon or navigate to API Keys in the left sidebar.
  • Click Create new secret key.
  • Give your key a name like weather-assistant and click Create secret key.
  • Copy the key immediately and save it somewhere safe (a password manager or a temporary text file). You will not be able to see it again after closing the dialog.

Keep your API key secret

Never commit your API key to version control or share it publicly. You will paste it into a local configuration file in the next step. That file should never leave your machine.

Generate the Spring Boot project from Spring Initializr

Spring Initializr is a web tool that generates a starter project with the dependencies you select. It saves you from creating the folder structure and pom.xml by hand.

  • Open your browser and go to start.spring.io.
  • Set Project to Maven.
  • Set Language to Java.
  • Set Spring Boot version to 3.5.14.
  • Under Project Metadata, set Group to com.example.
  • Set Artifact to weather-assistant.
  • Set Packaging to Jar.
  • Set Java to 17.
  • Click Add Dependencies.
  • Search for Spring Web and add it.
  • Search for OpenAI (under the AI section) and add it.
  • Click Generate to download the project as a zip file.

What did Spring Initializr create?

The downloaded zip contains a complete Maven project with a pom.xml (listing your dependencies), a main application class, and the standard src/main/java folder structure.

The OpenAI dependency pulls in spring-ai-starter-model-openai, which auto-configures a connection to OpenAI when you provide an API key.

  • Extract the zip file to your Desktop.
  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type the name of your IDE (IntelliJ IDEA or Visual Studio Code) and press Enter to open it.
  • In IntelliJ, click Open and select the weather-assistant folder on your Desktop.
  • In VS Code, click File then Open Folder and select the weather-assistant folder on your Desktop.
  • Wait for your IDE to import the Maven project. IntelliJ may show a progress bar as it downloads dependencies.
  • Open pom.xml in the file explorer and confirm you see spring-ai-starter-model-openai listed under dependencies.

IDE not recognizing the project?

If IntelliJ does not detect the Maven project automatically, right-click pom.xml in the file tree and select Add as Maven Project. In VS Code, make sure you have the Extension Pack for Java installed from the Extensions marketplace.

Help me troubleshoot my IDE not recognizing my Spring Boot project.

✔️ Awesome, I've got everything!

Great. Double check you can see pom.xml and the src/main/java/com/example/weatherassistant folder in your IDE's file tree.

ⓧ I'd like to double check the full code

Here is what your pom.xml should look like:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.5.14</version>
		<relativePath/>
	</parent>
	<groupId>com.example</groupId>
	<artifactId>weather-assistant</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>weather-assistant</name>
	<description>AI-Powered Weather Assistant with Spring AI</description>
	<properties>
		<java.version>17</java.version>
		<spring-ai.version>1.1.7</spring-ai.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.ai</groupId>
			<artifactId>spring-ai-starter-model-openai</artifactId>
		</dependency>
	</dependencies>
	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.ai</groupId>
				<artifactId>spring-ai-bom</artifactId>
				<version>${spring-ai.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

And here is your main application class:

package com.example.weatherassistant;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class WeatherAssistantApplication {

	public static void main(String[] args) {
		SpringApplication.run(WeatherAssistantApplication.class, args);
	}
}

Your development environment is ready. Next up, you will configure your API key and test your first AI-powered chat response.

Configure Spring AI and Test Basic Chat

Your project skeleton is ready with all the right dependencies. Now it is time to connect your application to OpenAI and see it respond to a real request.

Spring AI uses Spring Boot's auto-configuration to wire up an AI model client from just a few properties. You will set those properties, create a REST controller, and confirm the AI is reachable.

In this step, get ready to:
  • Configure your OpenAI connection properties.
  • Create a ChatController with a system prompt.
  • Run the application and verify the AI responds.
Add your OpenAI configuration to application.properties

Spring AI reads your API credentials and model preferences from application.properties. This file tells Spring Boot which provider to use, which model to call, and how creative the responses should be.

  • In your IDE, open src/main/resources/application.properties.
  • Replace the contents with the following configuration:
spring.ai.openai.api-key=your-api-key-here
spring.ai.openai.chat.options.model=gpt-4o
spring.ai.openai.chat.options.temperature=0.7
spring.application.name=weather-assistant
  • Replace your-api-key-here with the OpenAI API key you generated in Step 1.

What do these properties do?

  • spring.ai.openai.api-key authenticates your application with OpenAI's API.
  • spring.ai.openai.chat.options.model=gpt-4o selects the GPT-4o model for chat completions.
  • spring.ai.openai.chat.options.temperature=0.7 controls creativity. Lower values give more predictable answers. Higher values give more varied responses.
  • spring.application.name sets the app name that appears in log output.

Keep your API key safe

Never commit your API key to version control. If you plan to share this project on GitHub, add application.properties to your .gitignore file.

  • Save application.properties.
Create the ChatController

Spring AI provides a ChatClient fluent API that handles the back-and-forth with the AI model. Spring Boot auto-configures a ChatClient.Builder bean that you can inject directly into your controller.

  • Create a new file called WeatherTools.java in src/main/java/com/example/weatherassistant/.
  • Add the following placeholder code to the file:
package com.example.weatherassistant;

public class WeatherTools {
    // You will add tool methods here in the next step
}

Why create an empty class?

The ChatController you are about to write references WeatherTools so the AI knows where to look for callable tools. For now the class is empty, which means no tools are available yet. You will add the actual tool logic in Step 3.

  • Save WeatherTools.java.
  • Create another new file called ChatController.java in the same src/main/java/com/example/weatherassistant/ folder.
  • Add the following code to ChatController.java:
package com.example.weatherassistant;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ChatController {

    private final ChatClient chatClient;

    public ChatController(ChatClient.Builder chatClientBuilder) {
        // Build a ChatClient with a system prompt that defines the assistant's personality
        this.chatClient = chatClientBuilder
                .defaultSystem("You are a helpful weather assistant. When users ask about weather or what to wear, use the available tools to get current weather data. Provide friendly, concise recommendations.")
                .build();
    }

    @GetMapping("/chat")
    public String chat(@RequestParam(value = "message", defaultValue = "Hello") String message) {
        // Send the user's message to the AI model and return the text response
        return this.chatClient.prompt()
                .user(message)
                .tools(new WeatherTools())
                .call()
                .content();
    }
}

What does this code do?

  • @RestController marks this class as a Spring MVC controller that returns data directly instead of a view.
  • The constructor receives a ChatClient.Builder that Spring Boot auto-configures using your application.properties settings.
  • defaultSystem() sets a system prompt that shapes how the AI responds to every request.
  • The /chat endpoint takes a message query parameter, sends it to the AI, and returns the plain text response.
  • .prompt().user(message).tools(new WeatherTools()).call().content() is the ChatClient fluent API chain. The model is actually invoked when .content() is called.
  • Save ChatController.java.

Seeing red squiggles on the imports?

If your IDE shows unresolved import errors, it may not have downloaded the Spring AI dependencies yet. In IntelliJ, right-click pom.xml and select Maven then Reload Project.

Help me fix import errors in my Spring AI project.

Run the application and test with curl

Time to see your AI assistant respond. You will start the Spring Boot application using Maven, then send it a request from a separate terminal window.

  • Open a terminal in your project's root folder (the weather-assistant directory that contains pom.xml).
  • Start the application by running:
mvn spring-boot:run

What should I see?

After Maven downloads dependencies and compiles your code, you will see the Spring Boot banner followed by log lines. Look for a message containing Started WeatherAssistantApplication and another containing Tomcat started on port 8080. These confirm your app is running and ready for requests.

  • Open a second terminal window (keep the first one running).
  • Send a test request by running:
curl http://localhost:8080/chat?message=Hello

You should see a friendly text response from the AI printed in your terminal. The exact wording varies because the model generates a fresh answer each time.

Getting a connection refused error?

Make sure the first terminal still shows the application running. If you see an error during startup, check that your API key in application.properties is correct and has no extra spaces or quotes around it.

If you see a 401 Unauthorized response, your API key may be invalid or expired. Generate a new one at platform.openai.com.

Help me debug why my Spring AI application is not connecting to OpenAI.

✔️ Awesome, I've got everything!

Great. Double check you have saved application.properties, WeatherTools.java, and ChatController.java.

ⓧ I'd like to double check the full code

Here is the full application.properties file:

spring.ai.openai.api-key=your-api-key-here
spring.ai.openai.chat.options.model=gpt-4o
spring.ai.openai.chat.options.temperature=0.7
spring.application.name=weather-assistant

Here is the full ChatController.java file:

package com.example.weatherassistant;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ChatController {

    private final ChatClient chatClient;

    public ChatController(ChatClient.Builder chatClientBuilder) {
        this.chatClient = chatClientBuilder
                .defaultSystem("You are a helpful weather assistant. When users ask about weather or what to wear, use the available tools to get current weather data. Provide friendly, concise recommendations.")
                .build();
    }

    @GetMapping("/chat")
    public String chat(@RequestParam(value = "message", defaultValue = "Hello") String message) {
        return this.chatClient.prompt()
                .user(message)
                .tools(new WeatherTools())
                .call()
                .content();
    }
}

Your AI assistant is alive and responding. Next up, you will give it a superpower: the ability to call your own Java methods to fetch real weather data before answering.

Implement Tool Calling with @Tool

In the previous step, you got your Spring AI app talking to OpenAI and returning basic chat responses. The AI can respond to questions, but it has no access to real-world data.

In this step, you'll give the AI model the ability to call your own Java methods. When a user asks about the weather, the model will recognize it needs data, autonomously invoke your getWeather method, and use the result to form a helpful answer.

In this step, get ready to:
  • Create a WeatherTools class with a @Tool-annotated method.
  • Understand how the tool is registered with your ChatClient.
  • Test that the AI autonomously calls your Java method when asked about weather.
Create the WeatherTools class

Your ChatController already references a WeatherTools class in the .tools(new WeatherTools()) call. The class doesn't exist yet, so the application won't compile until you create it.

  • In your IDE, create a new Java file called WeatherTools.java in the same package as your other classes (src/main/java/com/example/weatherassistant/).
  • Add the following code to WeatherTools.java:
package com.example.weatherassistant;

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;

public class WeatherTools {

    @Tool(description = "Get the current weather for a given city. Returns temperature in Celsius, weather conditions, and humidity percentage.")
    public String getWeather(
            @ToolParam(description = "The city name to get weather for, e.g. London, Tokyo, New York") String city) {
        // Simulated weather data - in production, call a real weather API
        return switch (city.toLowerCase()) {
            case "london" -> "Temperature: 12°C, Conditions: Cloudy with light rain, Humidity: 78%";
            case "tokyo" -> "Temperature: 24°C, Conditions: Sunny with some clouds, Humidity: 55%";
            case "new york" -> "Temperature: 18°C, Conditions: Clear skies, Humidity: 42%";
            case "paris" -> "Temperature: 15°C, Conditions: Overcast, Humidity: 65%";
            case "sydney" -> "Temperature: 20°C, Conditions: Partly cloudy, Humidity: 60%";
            default -> "Temperature: 20°C, Conditions: Fair, Humidity: 50%";
        };
    }
}

What does this code do?

The @Tool annotation marks getWeather as a method the AI model can invoke. The description attribute tells the model what this tool does and when to use it.

The @ToolParam annotation describes the city parameter so the model knows what value to pass. Spring AI automatically generates a JSON schema from these annotations and sends it to OpenAI.

The method body uses a Java switch expression to return simulated weather data. In a production app, you'd call a real weather API here instead.

  • Save WeatherTools.java.
  • Confirm your IDE shows no compilation errors in the file.

Seeing a compilation error?

  • Make sure the package declaration at the top matches com.example.weatherassistant exactly.
  • Verify both imports (org.springframework.ai.tool.annotation.Tool and org.springframework.ai.tool.annotation.ToolParam) are present. If your IDE can't resolve them, try refreshing your Maven dependencies.
  • The switch expression with -> syntax requires Java 17 or higher. Confirm your project is targeting Java 17.

Help me fix a compilation error in my WeatherTools class

Understand how tool registration connects

Your ChatController already wires the tool to the AI model. Open ChatController.java and look at the /chat endpoint:

return this.chatClient.prompt()
        .user(message)
        .tools(new WeatherTools())
        .call()
        .content();

How does tool calling work behind the scenes?

The .tools(new WeatherTools()) call registers your class with the AI model for this request. Spring AI scans the class for @Tool-annotated methods and sends their descriptions and parameter schemas to OpenAI.

When the model receives a weather-related question, it decides it needs weather data. It responds with a tool call request instead of a text answer. Spring AI then executes your getWeather method, sends the result back to the model, and the model uses that data to compose its final answer.

The model decides autonomously when to call the tool. You don't write any if/else logic to detect weather questions.

Test the tool calling endpoint

Now you'll run the application and ask the AI about weather. The model should autonomously invoke your getWeather method and use the returned data in its response.

  • Stop the application if it's still running from the previous step (press Ctrl+C in the terminal).
  • Start the application again by running:
./mvnw spring-boot:run
  • Wait until you see the Started WeatherAssistantApplication message in your terminal.
  • Open a new terminal window and test the endpoint by running:
curl "http://localhost:8080/chat?message=What's the weather in Tokyo?"
  • You should receive a response that mentions Tokyo's weather conditions, including a temperature around 24°C, sunny with some clouds, and humidity around 55%.

What should I see?

The AI's response will be a natural language answer that incorporates the data from your getWeather method. It won't be the raw string you returned. Instead, the model weaves the weather data into a friendly recommendation.

In the terminal where your application is running, you'll also see log activity showing the request being processed. This confirms the tool was called during the interaction.

Getting an empty or generic response?

  • Make sure you restarted the application after creating WeatherTools.java. Spring Boot needs a restart to pick up the new class.
  • Verify the @Tool annotation's description is present. Without a clear description, the model may not realize it should call the tool.
  • Check that your API key is valid and you have available credits on your OpenAI account.

Help me troubleshoot why my Spring AI tool calling isn't working

✔️ Awesome, I've got everything!

Great. Double check you've saved WeatherTools.java and confirmed the AI responds with Tokyo weather data.

ⓧ I'd like to double check the full code

Here's the complete WeatherTools.java file:

package com.example.weatherassistant;

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;

public class WeatherTools {

    @Tool(description = "Get the current weather for a given city. Returns temperature in Celsius, weather conditions, and humidity percentage.")
    public String getWeather(
            @ToolParam(description = "The city name to get weather for, e.g. London, Tokyo, New York") String city) {
        // Simulated weather data - in production, call a real weather API
        return switch (city.toLowerCase()) {
            case "london" -> "Temperature: 12°C, Conditions: Cloudy with light rain, Humidity: 78%";
            case "tokyo" -> "Temperature: 24°C, Conditions: Sunny with some clouds, Humidity: 55%";
            case "new york" -> "Temperature: 18°C, Conditions: Clear skies, Humidity: 42%";
            case "paris" -> "Temperature: 15°C, Conditions: Overcast, Humidity: 65%";
            case "sydney" -> "Temperature: 20°C, Conditions: Partly cloudy, Humidity: 60%";
            default -> "Temperature: 20°C, Conditions: Fair, Humidity: 50%";
        };
    }
}

And here's ChatController.java (unchanged from the previous step):

package com.example.weatherassistant;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ChatController {

    private final ChatClient chatClient;

    public ChatController(ChatClient.Builder chatClientBuilder) {
        this.chatClient = chatClientBuilder
                .defaultSystem("You are a helpful weather assistant. When users ask about weather or what to wear, use the available tools to get current weather data. Provide friendly, concise recommendations.")
                .build();
    }

    @GetMapping("/chat")
    public String chat(@RequestParam(value = "message", defaultValue = "Hello") String message) {
        return this.chatClient.prompt()
                .user(message)
                .tools(new WeatherTools())
                .call()
                .content();
    }
}

Your AI assistant can now access live data through your Java methods. Next up, you'll use structured output to map the AI's response directly to a Java record, giving you type-safe data instead of raw strings.

Add Structured Output with Java Records

Your weather assistant can now call Java methods autonomously. The AI decides when it needs weather data, invokes your getWeather tool, and returns a helpful response.

The problem is that the response comes back as a plain String. In real applications, you need structured data that your frontend or downstream services can process programmatically. Spring AI solves this with structured output, which maps AI responses directly to Java types.

In this step, get ready to:
  • Create a Java record to define the shape of AI responses.
  • Add a new endpoint that maps AI output directly to your record.
  • Test and verify structured JSON responses.
Create the WeatherRecommendation record

A Java record is a compact way to define a data class. You declare the fields, and Java automatically generates the constructor, getters, equals(), hashCode(), and toString(). Spring AI uses this record as a schema to instruct the AI model what shape its response should take.

  • Create a new file called WeatherRecommendation.java in the same package as your other files (src/main/java/com/example/weatherassistant/).
  • Add the following code to define the record:
package com.example.weatherassistant;

public record WeatherRecommendation(
        String city,
        String temperature,
        String conditions,
        String recommendation,
        String clothingSuggestion
) {}

What does this code do?

This record defines five fields that the AI must populate in its response:

  • city and temperature capture the location and current reading.
  • conditions describes the sky and weather state.
  • recommendation and clothingSuggestion hold the AI's personalized advice.

Spring AI generates a JSON schema from this record and sends it to the model, so the model knows exactly what structure to return.

  • Save WeatherRecommendation.java.
Add the /recommend endpoint

Now you need an endpoint that tells Spring AI to map the model's response to your new record. The key method is .entity(WeatherRecommendation.class), which replaces .content() and returns a typed Java object instead of a raw String.

  • In ChatController.java, find the closing brace of the chat method. Add the following new method below it:
    @GetMapping("/recommend")
    public WeatherRecommendation recommend(
            @RequestParam(value = "city", defaultValue = "London") String city) {
        return this.chatClient.prompt()
                .user("Give me a weather recommendation for " + city + ". Include what to wear.")
                .tools(new WeatherTools())
                .call()
                .entity(WeatherRecommendation.class);
    }

What does this code do?

  • @GetMapping("/recommend") maps HTTP GET requests to this method.
  • The method accepts a city request parameter that defaults to London if none is provided.
  • The prompt tells the AI to include clothing advice, giving it clear direction on what to fill in.
  • .entity(WeatherRecommendation.class) replaces .content(). Spring AI generates a JSON schema from your record and instructs the model to respond in that exact shape.
  • Save ChatController.java.

Seeing a compile error?

  • Make sure WeatherRecommendation.java is in the same package (com.example.weatherassistant) as ChatController.java. If the file is in a different folder, the import won't resolve.
  • Check that the method is placed inside the ChatController class braces, not outside them.

Help me fix a compile error in my ChatController

✔️ 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 ChatController.java with both endpoints:

package com.example.weatherassistant;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ChatController {

    private final ChatClient chatClient;

    public ChatController(ChatClient.Builder chatClientBuilder) {
        this.chatClient = chatClientBuilder
                .defaultSystem("You are a helpful weather assistant. When users ask about weather or what to wear, use the available tools to get current weather data. Provide friendly, concise recommendations.")
                .build();
    }

    @GetMapping("/chat")
    public String chat(@RequestParam(value = "message", defaultValue = "Hello") String message) {
        return this.chatClient.prompt()
                .user(message)
                .tools(new WeatherTools())
                .call()
                .content();
    }

    @GetMapping("/recommend")
    public WeatherRecommendation recommend(
            @RequestParam(value = "city", defaultValue = "London") String city) {
        return this.chatClient.prompt()
                .user("Give me a weather recommendation for " + city + ". Include what to wear.")
                .tools(new WeatherTools())
                .call()
                .entity(WeatherRecommendation.class);
    }
}

And the full WeatherRecommendation.java:

package com.example.weatherassistant;

public record WeatherRecommendation(
        String city,
        String temperature,
        String conditions,
        String recommendation,
        String clothingSuggestion
) {}
Test the structured output endpoint

Time to verify that the AI returns a fully-populated JSON object instead of a plain text string.

  • Stop your running application (press Ctrl+C in the terminal), then restart it by running:
./mvnw spring-boot:run
  • Once the application starts, test the new endpoint by running:
curl http://localhost:8080/recommend?city=Tokyo
  • You should see a JSON response with all five fields populated. It will look something like this:
{
  "city": "Tokyo",
  "temperature": "24°C",
  "conditions": "Sunny with some clouds",
  "recommendation": "Great day to be outside! Stay hydrated.",
  "clothingSuggestion": "Light layers, sunglasses, and comfortable shoes."
}

Why does this matter?

Without structured output, you would get a free-form text response and have to parse it yourself. With .entity(), Spring AI handles the schema generation, model instruction, and JSON deserialization for you.

Your frontend can now consume this endpoint and render each field independently. The AI's creativity is channeled into a predictable structure.

Getting a 500 error or empty fields?

  • Check your terminal logs for the full error message. A common cause is the AI returning text that doesn't match the expected JSON schema. Restart and try again.
  • Verify the record field names in WeatherRecommendation.java match exactly: city, temperature, conditions, recommendation, clothingSuggestion.
  • Make sure you restarted the application after saving both files. Changes require a restart to take effect.

Help me debug my /recommend endpoint

Your weather assistant now returns structured, typed data. A frontend can consume this JSON directly without any parsing logic. Next up, you'll tackle the Secret Mission and add conversation memory so the assistant remembers previous questions.

Secret mission

Add Conversation Memory

Your weather assistant currently treats every request as a brand new conversation. What if it could remember what you just asked? Add conversation memory so the assistant maintains context across multiple questions in a session.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs locally and uses the OpenAI API, so the only ongoing cost is API usage if you continue making requests.

Cost warning

Your OpenAI API key remains active even after you stop the application. If you have usage-based billing enabled, any future requests using this key will incur charges. Consider deleting the key if you do not plan to use it again.

Resources you used:

  • The weather-assistant project folder on your Desktop.
  • The running Spring Boot application on localhost:8080.
  • Your OpenAI API key (stored in application.properties and active on platform.openai.com).

Keep everything running

No action needed. Choose this if you are still actively building on the weather assistant or experimenting with Spring AI features.

  • Your application continues running on localhost:8080. You can keep sending requests to the /chat and /recommend endpoints.
  • Your OpenAI API key stays active. Each request costs a small amount depending on the model and prompt length.
  • JDK, Maven, and your IDE remain installed on your machine.

Pause - I'll come back to this later

Stop the running application to free up your terminal and port, but keep your project files intact for later.

  • Switch to the terminal where your Spring Boot application is running.
  • Press Ctrl+C to stop the application.
  • Your project files stay on your Desktop. You can restart anytime by navigating to the weather-assistant folder and running mvn spring-boot:run.
  • Your OpenAI API key stays active but will not incur charges while the app is stopped.

Delete - I don't want to use this again

Remove all project files and revoke your API key to fully clean up.

  • Switch to the terminal where your Spring Boot application is running and press Ctrl+C to stop it.
  • Delete the project folder on macOS/Linux by running:
rm -rf ~/Desktop/weather-assistant

On Windows, run:

Remove-Item -Recurse -Force ~\Desktop\weather-assistant
  • Delete your OpenAI API key to prevent future charges.
  • Go to platform.openai.com and sign in.
  • Navigate to the API keys page.
  • Find the key you created for this project and click the trashcan icon next to it to delete it.

Nice Work!

Nice Work!

You just built an AI-powered weather assistant that uses Spring AI to bridge the gap between OpenAI and your Java application.

Your assistant doesn't just chat. It autonomously decides when to call your Java methods, fetches weather data, and returns structured recommendations as typed Java records.

You've learned how to:

  • Integrated Spring AI with Spring Boot using auto-configuration and the ChatClient fluent API.
  • Implemented tool calling with @Tool so the AI model autonomously invokes your Java methods.
  • Used structured output to map AI responses directly to Java records.
  • Added conversation memory so your assistant maintains context across requests.

Ready to quiz yourself?