eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Course – LJB – NPI EA (cat = Core Java)
announcement - icon

Code your way through and build up a solid, practical foundation of Java:

>> Learn Java Basics

Partner – LambdaTest – NPI EA (cat= Testing)
announcement - icon

Distributed systems often come with complex challenges such as service-to-service communication, state management, asynchronous messaging, security, and more.

Dapr (Distributed Application Runtime) provides a set of APIs and building blocks to address these challenges, abstracting away infrastructure so we can focus on business logic.

In this tutorial, we'll focus on Dapr's pub/sub API for message brokering. Using its Spring Boot integration, we'll simplify the creation of a loosely coupled, portable, and easily testable pub/sub messaging system:

>> Flexible Pub/Sub Messaging With Spring Boot and Dapr

1. Overview

In this tutorial, we’ll discuss the concepts of Spring AI that can help create an AI assistant using LLMs like ChatGPT, Ollama, Mistreal, etc.

Enterprises are increasingly adopting AI assistants to enhance the user experience across a wide range of existing business functionalities:

  • Answering user queries
  • Performing transactions based on user input
  • Summarizing lengthy sentences and documents

While these are just a few fundamental capabilities of LLMs, their potential extends far beyond these functionalities.

2. Spring AI Features

The Spring AI framework offers a range of exciting features to deliver AI-driven functionalities:

  • interfaces that can integrate seamlessly with the underlying LLM services and Vector DBs
  • context-aware response generation and action execution using RAG and the Function Calling APIs
  • structured output converters to transform LLM responses into POJOs or machine-readable formats like JSON
  • enrich prompts and apply guardrails through interceptors provided by the Advisor API
  • enhanced user engagement by maintaining conversation states

We can visualize it as well:

AI Assistant building Blocks

To illustrate some of these features, we’re going to build a chatbot in a legacy Order Management System (OMS):

Typical functionalities of an OMS consists of:

  • Create order
  • Get user orders

3. Prerequisites

First, we’ll need an OpenAI subscription to use its LLM service. Then in the Spring Boot application, we’ll add the Maven dependencies for Spring AI libraries. We’ve already covered the prerequisites in our other articles in great detail, therefore we’ll not elaborate further on this topic.

Furthermore, we’ll use the in-memory HSQLDB database to get started quickly. Let’s create the requisite tables and insert some data in it:

CREATE TABLE User_Order (
    order_id BIGINT NOT NULL PRIMARY KEY,
    user_id VARCHAR(20) NOT NULL,
    quantity INT
);

INSERT INTO User_Order (order_id, user_id, quantity) VALUES (1, 'Jenny', 2);
INSERT INTO User_Order (order_id, user_id, quantity) VALUES (2, 'Mary', 5);
INSERT INTO User_Order (order_id, user_id, quantity) VALUES (3, 'Alex', 1);
INSERT INTO User_Order (order_id, user_id, quantity) VALUES (4, 'John', 3);
INSERT INTO User_Order (order_id, user_id, quantity) VALUES (5, 'Sophia', 4);
--and so on..

We’ll use a few standard configuration properties related to initializing the HSQLDB and OpenAI client in the Spring application.properties file:

spring.datasource.driver-class-name=org.hsqldb.jdbc.JDBCDriver
spring.datasource.url=jdbc:hsqldb:mem:testdb;DB_CLOSE_DELAY=-1
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=none

spring.ai.openai.chat.options.model=gpt-4o-mini
spring.ai.openai.api-key=xxxxxxx

Selecting the right model fit for a use case is a complex iterative process involving a lot of trial and error. Nevertheless, for a simple demo application for this article, the cost-effective GPT-4o mini model will suffice.

4. Function Calling API

This feature is one of the pillars of the popular agentic concept in LLM-based applications. It enables applications to perform a complex collection of precise tasks and make decisions independently.

For instance, it can help immensely to build a chatbot in the legacy Order Management application.  The chatbot can help users raise order requests, retrieve order history, and do more through natural language.

These are specialized skills powered by one or more application functions. We define the algorithm in a prompt sent to the LLM with the supporting function schema. The LLM receives this schema and identifies the correct function to perform the requested skill. Later, it sends its decision to the application.

Finally, the application executes the function and sends back more information to the LLM:

Function Calling sequence

First, let’s look at the main components of the legacy application:

Class Diagram

The OrderManagementService class has two important functions: creating orders and fetching user order history. Both functions use the OrderRepository bean to integrate with the DB:

@Service
public class OrderManagementService {
    @Autowired
    private OrderRepository orderRepository;

    public Long createOrder(OrderInfo orderInfo) {
        return orderRepository.save(orderInfo).getOrderID();
    }

    public Optional<List<OrderInfo>> getAllUserOrders(String userID) {
       return orderRepository.findByUserID(userID);
    }
}

Now, let’s see how Spring AI can help implement a chatbot in the legacy application:

AI Assistant Class Diagram

In the class diagram, the OmAiAssistantConfiguration class is a Spring configuration bean. It registers the function callback beans, createOrderFn and getUserOrderFn:

@Configuration
public class OmAiAssistantConfiguration {
    @Bean
    @Description("Create an order. The Order ID is identified with orderID. "
      + The order quantity is identified by orderQuantity."
      + "The user is identified by userID. "
      + "The order quantity should be a positive whole number."
      + "If any of the parameters like user id and the order quantity is missing"
      + "then ask the user to provide the missing information.")
    public Function<CreateOrderRequest, Long> createOrderFn(OrderManagementService orderManagementService) {
        return createOrderRequest -> orderManagementService.createOrder(createOrderRequest.orderInfo());
    }
    @Bean
    @Description("get all the orders of an user. The user ID is identified with userID.")
    public Function<GetOrderRequest, List<OrderInfo>> getUserOrdersFn(OrderManagementService orderManagementService) {
        return getOrderRequest -> orderManagementService.getAllUserOrders(getOrderRequest.userID()).get();
    }
}

The @Description annotation helps generate the function schema. Subsequently, the application sends this schema to the LLM as part of the prompt. Since the functions use the existing methods of the OrderManagementService class, it promotes reusability.

Additionally, CreateOrderRequest and GetOrderRequests are Record classes, that help Spring AI generate the POJOs for the downstream service calls:

record GetOrderRequest(String userID) {}

record CreateOrderRequest(OrderInfo orderInfo) {}

Finally, let’s take a look at the new OrderManagementAIAssistant class that will send the user queries to the LLM service:

@Service
public class OrderManagementAIAssistant {
    @Autowired
    private ChatModel chatClient;

    public ChatResponse callChatClient(Set<String> functionNames, String promptString) {
        Prompt prompt  = new Prompt(promptString, OpenAiChatOptions
          .builder()
          .withFunctions(functionNames)
          .build()
        );
        return chatClient.call(prompt);
    }
}

The callChatClient() method helps register the functions in the Prompt object. Later, it invokes the ChatModel#call() method to get the response from the LLM service.

5. Function Calling Scenarios

For a user query or instruction given to an AI assistant, we’ll cover a few basic scenarios:

  • LLM decides and identifies one or more functions to execute
  • LLM complains about incomplete information for executing the function
  • LLM executes statements conditionally

We’ve discussed the concepts so far; therefore, moving ahead, we’ll use this feature to build the chatbot.

5.1. Execute a Callback Function Once or More Times

Now, let’s study the behavior of the LLM when we invoke it with a prompt consisting of the user query and the function schema.

We’ll begin with an example that creates an order:

void whenOrderInfoProvided_thenSaveInDB(String promptString) {
    ChatResponse response = this.orderManagementAIAssistant
      .callChatClient(Set.of("createOrderFn"), promptString);
    String resultContent = response.getResult().getOutput().getText();
    logger.info("The response from the LLM service: {}", resultContent);
}

Surprisingly, with natural language, we get the desired results:

Prompt LLM Response Observation
Create an order with quantity 20 for user id Jenny, and
randomly generate a positive whole number for the order ID
The order has been successfully created with the following details:
– **Order ID:** 123456
– **User ID:** Jenny
– **Order Quantity:** 20
The program creates the order with the information provided in the prompt.
Create two orders. The first order is for user id Sophia with quantity 30. The second order is for user id Mary with quantity 40. Randomly generate positive whole numbers for the order IDs. The orders have been successfully created:

1. **Order for Sophia**: Order ID 1 with a quantity of 30.
2. **Order for Mary**: Order ID 2 with a quantity of 40.

If you need anything else, feel free to ask!

The program creates two orders. The LLM was smart to request to execute the function twice.

Moving on, let’s see if the LLM can understand the prompt that requests fetching order details of a user:

void whenUserIDProvided_thenFetchUserOrders(String promptString) {
    ChatResponse response = this.orderManagementAIAssistant
      .callChatClient(Set.of("getUserOrdersFn"), promptString);
    String resultContent = response.getResult().getOutput().getText();
    logger.info("The response from the LLM service: {}", resultContent);
}

The results are similar to the previous case. The LLM successfully identifies the registered function to execute. Later, the Spring Boot application invokes the requested function and returns the order details of one or more users.

5.2. Conditionally Execute Callback Functions

Let’s consider the program that would create order only when certain criteria are met:

void whenUserIDProvided_thenCreateOrderIfUserHasLessThanTwoOrders(String promptString) {
    ChatResponse response = this.orderManagementAIAssistant
      .callChatClient(Set.of("getUserOrdersFn", "createOrderFn"), promptString);
    String resultContent = response.getResult()
      .getOutput()
      .getText();
    logger.info("The response from the LLM service: {}", resultContent);
}

We’ve registered the getUserOrderFn and CreateOrderFn functions in the ChatModel bean. Further, the LLM must determine which function will produce the best results for the requested action.

Just like before, the results are encouraging:

Prompt Output Observation
Create an order for user id Alex with quantity 25. Don’t create any order if the user has more than 2 orders. While creating the order, randomly generate positive whole numbers for the order ID User Alex already has more than 2 orders (specifically, 4 orders). Therefore, I will not create a new order. The LLM can conditionally invoke downstream functions based on the output of the previous functions.
Create an order for user id David with quantity 25. Don’t create any order if the user has more than 2 orders. While creating the order, randomly generate positive whole numbers for the order ID An order has been successfully created for user ID “David” with an order quantity of 25, and the order ID is 42. This time getUserOrderFn returned less than 2 orders for David. Hence, the LLM decided to request executing createOrderFn.
Create an order with a quantity of 20 Please provide your user ID to create the order. The LLM identified at the beginning that the user ID was missing and aborted further processing.

6. Spring AI Advisors API

In the previous sections, we discussed the application’s functional aspects. However, there are a few common concerns across all the functionalities, such as:

  • Prevent users from entering sensitive information
  • Logging and auditing of the user queries
  • Maintain conversational states
  • Enriching prompts

Fortunately, the Advisors APIs can help consistently address these concerns. We’ve already explained this in detail in one of our articles.

7. Spring AI Structured Output API and Spring AI RAG

The LLMs mostly generate response messages to user queries in natural languages. However, the downstream services mostly understand the messages in machine-readable formats like POJOs, JSON, etc. This is where the Spring AI’s capability to generate structured output plays an important role.

Like Advisors APIs, we’ve explained this in great detail in one of our articles. Hence, we’ll not discuss more on this and move to the next topic.

Sometimes, the applications have to query a Vector DB to perform a semantic search on the stored data to retrieve additional information. Later, the fetched result from the Vector DB is used in the prompt to provide contextualized information to the LLM. This is called the RAG technique, which can also be implemented using Spring AI.

8. Conclusion

In this article, we explored the key Spring AI features that can help create an AI assistant. Spring AI is evolving with lots of out-of-the-box features. However, the right selection of the underlying LLM service and Vector DB is crucial, irrespective of the programming framework. Additionally, achieving the optimum configuration of these services can be tricky and demands a lot of effort. Nevertheless, it’s important for the application’s wide adoption.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

eBook Jackson – NPI EA – 3 (cat = Jackson)