How Do I Build My First AI Application with Spring AI?

Building AI Applications with Spring AI, Part 1

In this example, you will build a Spring Boot API that answers questions about Java. Send a question to POST /api/ask, and Spring AI’s ChatClient will pass it to an OpenAI chat model. The endpoint returns the model’s answer as JSON.

Set up the project

Use JDK 25, Maven 3.9 or later, and an OpenAI API key with access to gpt-4o-mini. Copy the complete Maven POM at the end of this article into an empty project directory. The example uses Spring Boot 4.1.2 and Spring AI 2.0.2.

Create an OpenAI API key

  1. Sign in to the OpenAI Platform API Keys page.
  2. Select your project, then click Create new secret key. You can name it kodejava-spring-ai.
  3. Copy the secret key and store it securely. The platform displays the full key only when you create it.

API usage is billed separately from ChatGPT Plus. To enable paid API usage, add a payment method in API Billing.

Configure the chat model

Create src/main/resources/application.properties:

server.address=127.0.0.1
spring.ai.openai.api-key=${OPENAI_API_KEY}
spring.ai.openai.chat.model=gpt-4o-mini

Spring Boot reads the API key from the environment. Keep it out of source control. The loopback address makes the endpoint accessible on your own machine.

Create the application

Save the following class in src/main/java/org/kodejava/springai/JavaAssistantApplication.java:

package org.kodejava.springai;

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

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

Create the REST controller

Save the controller in src/main/java/org/kodejava/springai/JavaAssistantController.java:

package org.kodejava.springai;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;

@RestController
public class JavaAssistantController {
    private final ChatClient chatClient;

    public JavaAssistantController(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }

    @PostMapping("/api/ask")
    public AskResponse ask(@RequestBody AskRequest request) {
        if (request.question() == null || request.question().isBlank()) {
            throw new ResponseStatusException(
                    HttpStatus.BAD_REQUEST, "Question must not be blank");
        }

        String answer = chatClient.prompt()
                .system("You are a Java tutor. Explain concepts in plain English.")
                .user(request.question())
                .call()
                .content();

        return new AskResponse(answer);
    }

    public record AskRequest(String question) {}

    public record AskResponse(String answer) {}
}

Spring AI supplies the ChatClient.Builder, and the controller uses it to create a client once. The system message describes how the model should answer. The user message contains the question. The call().content() chain gets the model’s text synchronously, and Spring MVC serializes the response record as JSON.

A missing or blank question field returns HTTP 400 before the controller calls the model. Each request is independent, so include the topic when asking a follow-up question.

Run the application

On macOS or Linux:

export OPENAI_API_KEY='your-api-key'
mvn spring-boot:run

Run Maven in the same terminal where you set the key. Then open a second terminal to send a request:

curl http://127.0.0.1:8080/api/ask \
  -H 'Content-Type: application/json' \
  -d '{"question":"What is the difference between List and Set in Java?"}'

A possible response:

{
  "answer": "A List is ordered and allows duplicate elements. A Set contains unique elements. Its iteration order depends on the implementation."
}

The exact wording can vary. Try sending {"question":" "} to check the HTTP 400 response.

In Part 2, you will use a prompt template to adapt the explanation to the reader’s skill level.

References

Maven artifacts

The Spring Boot parent manages the Web MVC starter and Maven plugin versions. The Spring AI BOM manages the OpenAI starter version.

<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>4.1.2</version>
        <relativePath/>
    </parent>

    <groupId>org.kodejava</groupId>
    <artifactId>spring-ai-java-assistant</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <java.version>25</java.version>
        <spring-ai.version>2.0.2</spring-ai.version>
    </properties>

    <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>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webmvc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-model-openai</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>