Skip to content
-
Subscribe to our newsletter & never miss our best posts. Subscribe Now!
Cracking My Interview - Your Interview Partner Cracking My Interview - Your Interview Partner

Cracking My Interview - Your Interview Partner

Cracking My Interview - Your Interview Partner Cracking My Interview - Your Interview Partner

Cracking My Interview - Your Interview Partner

  • Java
  • Designs
  • Data Structure
  • Micro Services
  • Spring Boot
  • AI & ML
  • Big Data
  • Basics
  • Computer Basics
  • Cyber Security
  • Java
  • Designs
  • Data Structure
  • Micro Services
  • Spring Boot
  • AI & ML
  • Big Data
  • Basics
  • Computer Basics
  • Cyber Security
Designs

Build a Multi-Agent AI Planner Using Spring Boot, Java & Spring AI

By SND
August 6, 2026 9 Min Read
0

Learn How to Build an LLM-Based Planner Agent That Orchestrates Multiple AI Agents

Artificial Intelligence is rapidly evolving from simple chatbots to Agentic AI systems capable of reasoning, planning, and executing complex tasks.

Instead of asking a single Large Language Model (LLM) to perform everything, modern AI applications divide work among multiple specialized agents. A Planner Agent analyzes the user’s request, generates an execution plan, delegates tasks to specialized agents, and finally consolidates the results into a meaningful response.

In this article, we’ll build the planning layer of a Multi-Agent AI application using Spring Boot, Java, and Spring AI.

By the end of this article, you’ll understand how to:

  • Build a Planner Agent using Spring AI
  • Convert natural language into an execution plan
  • Design a clean Multi-Agent architecture
  • Create reusable DTOs and domain models
  • Use Spring AI’s ChatClient to communicate with OpenAI

In Part 2, we’ll implement the Orchestrator Service and specialized agents such as Flight, Hotel, Weather, and Summary agents.


What is Agentic AI?

Traditional AI applications work like this:

User
   │
   ▼
LLM
   │
   ▼
Response

The LLM is responsible for everything.

Although this works well for simple conversations, it becomes difficult when applications require multiple independent tasks.

Imagine the following request:

Plan a 5-day trip from London to Paris with a budget of $2500.

The AI needs to:

  • Find flights
  • Recommend hotels
  • Check weather
  • Estimate budget
  • Create an itinerary

Asking one prompt to perform all these tasks often leads to inconsistent or hallucinated responses.


Multi-Agent Architecture

Instead of using one intelligent model for everything, we divide responsibilities among multiple specialized agents.

                     Planner Agent
                           │
       ┌──────────┬─────────┴───────────┬──────────┐
       ▼          ▼                     ▼          ▼
 Flight Agent  Hotel Agent      Weather Agent  Budget Agent
       │          │                     │          │
       └──────────┴──────────────┬──────┴──────────┘
                                 ▼
                           Summary Agent
                                 │
                                 ▼
                              User

Each agent has a single responsibility.

AgentResponsibility
Planner AgentUnderstands user request
Flight AgentFlight recommendations
Hotel AgentHotel recommendations
Weather AgentWeather forecast
Budget AgentCost estimation
Summary AgentFinal itinerary generation

This follows the Single Responsibility Principle and makes the application easier to maintain and extend.


High-Level Architecture

Our application consists of two major layers.

Planning Layer

The LLM analyzes the user’s request.

User
   │
   ▼
Planner Controller
   │
   ▼
Planner Service
   │
   ▼
LLM Planner Service
   │
   ▼
OpenAI
   │
   ▼
Execution Plan

The Planner Service does not invoke Flight or Hotel agents directly.

Instead, it asks the LLM:

“Given this user request, what tasks should be executed?”

The LLM returns a structured execution plan.


Execution Layer

The execution layer will be implemented in Part 2.

Execution Plan
      │
      ▼
Orchestrator
      │
 ┌────┼─────┐
 ▼    ▼     ▼
Flight Hotel Weather
      │
      ▼
Summary Agent
      │
      ▼
User

Project Structure

planner-agent
│
├── config
│      OpenAIConfig.java
│
├── controller
│
├── service
│      PlannerService.java
│      LLMPlannerService.java
│
├── dto
│      UserRequest.java
│      ExecutionPlan.java
│      AgentRequest.java
│      AgentResponse.java
│      SummaryRequest.java
│
├── model
│      Goal.java
│      Context.java
│      Task.java
│      MissingInformation.java
│
└── PlannerApplication.java

The execution-related classes will be added in Part 2.


Maven Configuration

Our project uses:

  • Spring Boot 3.5
  • Java 21
  • Spring AI
  • OpenAI
  • Lombok
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Spring AI provides a clean abstraction over OpenAI, Anthropic, Azure OpenAI, Ollama, Gemini, and many other models.


application.yml

spring:

  ai:

    openai:

      api-key: ${OPENAI_API_KEY}

      chat:

        options:

          model: gpt-4.1-mini

          temperature: 0

Keeping the temperature at 0 ensures deterministic execution plans.


OpenAI Configuration

Spring AI makes configuration extremely simple.

@Configuration
public class OpenAIConfig {

    @Bean
    ChatClient chatClient(OpenAiChatModel model){

        return ChatClient.create(model);

    }

}

Every service can now inject ChatClient.


Designing the Domain Model

Before calling the LLM, we need classes that represent an execution plan.

Instead of returning free-form text, we’ll ask the model to return structured JSON.

ExecutionPlan

Goal

Context

Tasks

MissingInformation

Let’s look at each model.


Goal

Represents the user’s primary objective.

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Goal {

    private String description;

}

Example:

Plan a travel itinerary

Context

Stores extracted information.

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Context {

    private Map<String,Object> attributes;

}

Example:

source → London

destination → Paris

budget → 2500

Task

Represents work assigned to a specialized agent.

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Task {

    private int order;

    private String agent;

    private String action;

}

Example

Task 1

FlightAgent

Search Flights

Missing Information

Sometimes users don’t provide enough information.

@Data
@NoArgsConstructor
@AllArgsConstructor
public class MissingInformation {

    private String field;

    private String reason;

}

Example

Missing Field

Travel Date

Reason

Required for searching flights.

This prevents the LLM from hallucinating missing values.


Data Transfer Objects (DTOs)

DTOs define how information flows through our application.


UserRequest

@Data
public class UserRequest {

    @NotBlank
    private String prompt;

}

Example request:

{
  "prompt":"Plan a trip from London to Paris with a budget of $2500."
}

AgentRequest

Represents requests sent to specialized agents.

@Data
@NoArgsConstructor
@AllArgsConstructor
public class AgentRequest {

    private String agentName;

    private String instruction;

    private Map<String,Object> context;

}

AgentResponse

Represents the response from any specialized agent.

@Data
@NoArgsConstructor
@AllArgsConstructor
public class AgentResponse {

    private String agentName;

    private String response;

}

SummaryRequest

Passed to the Summary Agent.

@Data
@NoArgsConstructor
@AllArgsConstructor
public class SummaryRequest {

    private String originalPrompt;

    private List<AgentResponse> responses;

}

ExecutionPlan

This is the most important object in the application.

@Data
public class ExecutionPlan {

    private Goal goal;

    private Context context;

    private List<Task> tasks;

    private List<MissingInformation> missingInformation;

}

The Planner Agent generates this object before any execution begins.


Building the LLM Planner Service

Now comes the most interesting part.

Instead of manually parsing the user’s prompt, we’ll let the LLM create the execution plan.

@Service
@RequiredArgsConstructor
public class LLMPlannerService {

    private final ChatClient chatClient;

    public ExecutionPlan createExecutionPlan(String userPrompt) {

        String prompt = buildPrompt(userPrompt);

        return chatClient.prompt()
                .user(prompt)
                .call()
                .entity(ExecutionPlan.class);

    }

    private String buildPrompt(String userPrompt) {

        return """
                You are an AI Planner Agent.

                Analyze the user's request.

                Return ONLY valid JSON.

                {
                    "goal":{},
                    "context":{},
                    "tasks":[],
                    "missingInformation":[]
                }

                User Request:

                %s
                """.formatted(userPrompt);

    }

}

Notice how simple this is.

Spring AI automatically converts the JSON response into our Java object.

No manual parsing.

No Jackson mapping.

No custom deserializers.


Example Execution Plan

User Prompt

Plan a 5-day trip from London to Paris with a budget of $2500.

Generated by the LLM

{
  "goal": {
    "description": "Plan a travel itinerary"
  },
  "context": {
    "attributes": {
      "source": "London",
      "destination": "Paris",
      "budget":2500
    }
  },
  "tasks": [
    {
      "order":1,
      "agent":"FlightAgent",
      "action":"Search Flights"
    },
    {
      "order":2,
      "agent":"HotelAgent",
      "action":"Search Hotels"
    },
    {
      "order":3,
      "agent":"WeatherAgent",
      "action":"Check Weather"
    },
    {
      "order":4,
      "agent":"SummaryAgent",
      "action":"Generate Summary"
    }
  ],
  "missingInformation":[]
}

This plan becomes the input for the execution layer.


Benefits of This Architecture

This design offers several advantages:

  • Clear separation of planning and execution
  • Modular, reusable agents
  • Easy to add new agents
  • Supports parallel execution
  • Simple integration with external APIs
  • Cleaner prompts for specialized tasks
  • Scalable architecture for enterprise AI applications

Instead of writing hundreds of lines of conditional logic, we allow the LLM to decide what needs to be done, while our application focuses on executing the plan.

We learned how to:

  • Accept a natural language request
  • Use Spring AI’s ChatClient
  • Ask the LLM to generate an ExecutionPlan
  • Map the JSON response directly into Java objects

Now it’s time to execute that plan.

In this article, we’ll build the Execution Layer, where the Planner delegates work to multiple specialized AI agents and combines their outputs into a single travel itinerary.

By the end of this article, you’ll have a complete end-to-end Multi-Agent AI application using Spring Boot, Java, and Spring AI.


Execution Layer Architecture

Once the Planner Agent generates the execution plan, the application moves to the execution phase.

                  User
                    │
                    ▼
           PlannerController
                    │
                    ▼
            PlannerService
                    │
                    ▼
         LLMPlannerService
                    │
                    ▼
             ExecutionPlan
                    │
                    ▼
          OrchestratorService
        ┌─────────┼─────────┐
        ▼         ▼         ▼
 FlightAgent  HotelAgent WeatherAgent
        │         │         │
        └─────────┼─────────┘
                  ▼
          SummaryAgent
                  │
                  ▼
          Final Travel Plan

The Orchestrator becomes the central execution engine.


Implementing the Orchestrator Service

The Orchestrator is responsible for:

  • Reading the execution plan
  • Invoking specialized agents
  • Collecting their responses
  • Calling the Summary Agent
@Service
@RequiredArgsConstructor
public class OrchestratorService {

    private final FlightAgentClient flightAgentClient;
    private final HotelAgentClient hotelAgentClient;
    private final WeatherAgentClient weatherAgentClient;
    private final SummaryAgentClient summaryAgentClient;

    public String executePlan(ExecutionPlan plan,
                              String originalPrompt) {

        List<AgentResponse> responses = new ArrayList<>();

        plan.getTasks()
                .stream()
                .sorted(Comparator.comparing(Task::getOrder))
                .forEach(task -> {

                    if ("SummaryAgent".equalsIgnoreCase(task.getAgent())) {
                        return;
                    }

                    AgentRequest request =
                            new AgentRequest(
                                    task.getAgent(),
                                    task.getAction(),
                                    plan.getContext().getAttributes());

                    responses.add(invokeAgent(request));

                });

        SummaryRequest summaryRequest =
                new SummaryRequest(originalPrompt, responses);

        return summaryAgentClient.generateSummary(summaryRequest);

    }

    private AgentResponse invokeAgent(AgentRequest request){

        return switch (request.getAgentName()){

            case "FlightAgent" ->
                    flightAgentClient.execute(request);

            case "HotelAgent" ->
                    hotelAgentClient.execute(request);

            case "WeatherAgent" ->
                    weatherAgentClient.execute(request);

            default ->
                    throw new IllegalArgumentException("Unknown Agent");

        };

    }

}

Why Use an Orchestrator?

Without an orchestrator, the Planner Service would contain all the execution logic.

That quickly becomes difficult to maintain.

The Orchestrator gives us:

  • Single Responsibility
  • Dynamic execution
  • Easy agent addition
  • Better testing
  • Cleaner architecture

Flight Agent

Every specialized agent has one responsibility.

The Flight Agent recommends flights.

@Component
@RequiredArgsConstructor
public class FlightAgentClient {

    private final ChatClient chatClient;

    public AgentResponse execute(AgentRequest request){

        String response = chatClient.prompt()

                .system("""
                        You are a Flight Expert.

                        Recommend the best flight.

                        Include:

                        Airline
                        Flight Number
                        Departure
                        Arrival
                        Estimated Price
                        """)

                .user(request.getContext().toString())

                .call()

                .content();

        return new AgentResponse(
                "FlightAgent",
                response);

    }

}

Example Response

British Airways

Flight BA304

Departure : 09:30 AM

Arrival : 11:45 AM

Price : $620

Hotel Agent

The Hotel Agent specializes in accommodation.

@Component
@RequiredArgsConstructor
public class HotelAgentClient {

    private final ChatClient chatClient;

    public AgentResponse execute(AgentRequest request){

        String response = chatClient.prompt()

                .system("""
                        You are a Hotel Expert.

                        Recommend hotels.

                        Include

                        Hotel Name

                        Rating

                        Price Per Night

                        Location
                        """)

                .user(request.getContext().toString())

                .call()

                .content();

        return new AgentResponse(
                "HotelAgent",
                response);

    }

}

Example Response

Hilton Paris

★★★★☆

$210/Night

Central Paris

Weather Agent

The Weather Agent provides travel advice.

@Component
@RequiredArgsConstructor
public class WeatherAgentClient {

    private final ChatClient chatClient;

    public AgentResponse execute(AgentRequest request){

        String response = chatClient.prompt()

                .system("""
                        You are a Weather Expert.

                        Provide

                        Temperature

                        Weather

                        Travel Advice
                        """)

                .user(request.getContext().toString())

                .call()

                .content();

        return new AgentResponse(
                "WeatherAgent",
                response);

    }

}

Example Response

Temperature : 23°C

Weather : Sunny

Advice

Carry light clothing.

Summary Agent

Instead of returning multiple independent responses, we ask another LLM call to consolidate everything.

@Component
@RequiredArgsConstructor
public class SummaryAgentClient {

    private final ChatClient chatClient;

    public String generateSummary(
            SummaryRequest request){

        StringBuilder builder =
                new StringBuilder();

        builder.append("User Request\n");

        builder.append(request.getOriginalPrompt());

        builder.append("\n\n");

        builder.append("Agent Responses\n");

        for(AgentResponse response :
                request.getResponses()){

            builder.append(response.getAgentName())
                    .append("\n")
                    .append(response.getResponse())
                    .append("\n\n");

        }

        return chatClient.prompt()

                .system("""
                        You are an AI Travel Planner.

                        Combine all agent outputs.

                        Produce a beautiful itinerary.
                        """)

                .user(builder.toString())

                .call()

                .content();

    }

}

Planner Service

The Planner Service coordinates the planning and execution phases.

@Service
@RequiredArgsConstructor
public class PlannerService {

    private final LLMPlannerService llmPlannerService;

    private final OrchestratorService orchestratorService;

    public String plan(UserRequest request){

        ExecutionPlan plan =
                llmPlannerService
                        .createExecutionPlan(
                                request.getPrompt());

        if(!plan.getMissingInformation().isEmpty()){

            return "Missing Information : "
                    + plan.getMissingInformation();

        }

        return orchestratorService.executePlan(
                plan,
                request.getPrompt());

    }

}

Notice how simple this service becomes.

Planning and execution are delegated to dedicated components.


REST Controller

The controller remains very lightweight.

@RestController
@RequestMapping("/planner")
@RequiredArgsConstructor
public class PlannerController {

    private final PlannerService plannerService;

    @PostMapping

    public String plan(
            @Valid
            @RequestBody
            UserRequest request){

        return plannerService.plan(request);

    }

}

Testing the API

Endpoint

POST /planner

Request

{
  "prompt":"Plan a 5 day trip from London to Paris with a budget of $2500."
}

End-to-End Flow

Let’s see everything together.

User Prompt
      │
      ▼
Planner Controller
      │
      ▼
Planner Service
      │
      ▼
LLM Planner
      │
      ▼
Execution Plan
      │
      ▼
Orchestrator
      │
      ├────────► Flight Agent
      │
      ├────────► Hotel Agent
      │
      ├────────► Weather Agent
      │
      ▼
Summary Agent
      │
      ▼
Final Response
      │
      ▼
User

Sample Response

Travel Itinerary

Destination
Paris

Flights

British Airways BA304

Departure
09:30 AM

Arrival
11:45 AM

Estimated Price
$620

Hotel

Hilton Paris

4.5★

$210 per night

Weather

Sunny

23°C

Recommendation

Carry light clothing.

Estimated Budget

Within your $2500 budget.

Enjoy your trip!

Why This Architecture Scales

Although this tutorial uses Flight, Hotel, and Weather agents, the architecture can support dozens of specialized agents.

For example:

Planner Agent

├── Flight Agent

├── Hotel Agent

├── Weather Agent

├── Budget Agent

├── Currency Agent

├── Visa Agent

├── Insurance Agent

├── Maps Agent

├── Taxi Agent

├── Restaurant Agent

├── Shopping Agent

└── Summary Agent

Adding a new agent only requires:

  • A new client
  • A new system prompt
  • One additional case in the orchestrator

The Planner itself remains unchanged.


Production Enhancements

Our implementation demonstrates the core Multi-Agent workflow, but production applications require additional capabilities.

Some important enhancements include:

  • Parallel Execution using CompletableFuture to run independent agents concurrently.
  • Retry, Timeout, and Circuit Breaker with Resilience4j to improve resilience.
  • Observability using Micrometer and OpenTelemetry for tracing agent execution.
  • Spring AI Advisors for reusable prompts, conversation memory, and cross-cutting AI concerns.
  • Tool Calling and Function Calling to invoke external systems such as weather services or booking platforms.
  • Model Context Protocol (MCP) to standardize communication with external tools and services.
  • Vector Databases and RAG to provide enterprise knowledge and reduce hallucinations.
  • Real Flight, Hotel, and Weather APIs instead of relying on LLM-generated recommendations.
  • Security with API authentication, authorization, and prompt validation.
  • Monitoring and Logging for production deployments.

These enhancements transform a simple demo into a robust, enterprise-ready AI application.


Conclusion

In this two-part series, we built a complete Multi-Agent AI Planner using Spring Boot, Java, and Spring AI.

We started by designing a Planner Agent that converts natural language into a structured execution plan. We then implemented an Orchestrator Service that dynamically invokes specialized agents and consolidates their outputs into a polished response.

This architecture clearly separates planning from execution, making the application modular, maintainable, and extensible. As your AI applications grow, you can add new agents, integrate real-world APIs, or adopt advanced capabilities such as parallel execution, MCP, RAG, and Tool Calling without redesigning the overall architecture.

This pattern serves as a solid foundation for building modern Agentic AI applications with Spring Boot and Java.

Source code : https://github.com/beladasharrow-netizen/multi-agent

Author

SND

Technology leader with 24 years of experience designing and delivering large-scale enterprise applications across multiple industries. Expertise in Java, Spring ecosystem, cloud-native architectures, and distributed systems. Strong background in Big Data, machine learning, and building scalable, high-performance platforms. Extensive experience with open-source technologies, databases, microservices, and modern application modernization initiatives. Proven track record of leading architecture, engineering, and digital transformation programs from concept to production.

Follow Me
Other Articles
Previous

Chapter 3: Extend Your AI Agent with the Model Context Protocol (MCP)

No Comment! Be the first one.

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Recent Posts

  • Build a Multi-Agent AI Planner Using Spring Boot, Java & Spring AI
  • Chapter 3: Extend Your AI Agent with the Model Context Protocol (MCP)
  • Chapter 2- Giving Your AI Agent a Personality and Memory with Spring AI
  • Chapter 1- Build Production-Ready AI Agents with Spring AI, and Java
  • From Physical Servers to Kubernetes: The Complete Evolution of Application Networking (Explained with real example – TravelCity)

Recent Comments

  1. Tom on Web Application Architecture in AWS (Amazon)
  2. A WordPress Commenter on DESIGN A LOG AGGREGATION SYSTEM

Archives

  • August 2026
  • July 2026
  • June 2026

Categories

  • Basics
  • Computer Basics
  • Cyber Security
  • Data Structure
  • Designs
  • Java
  • Machine Learning
  • Micro Services
  • Spring Boot
  • AI ML LLM Agents
  • Java SpringBoot REST
  • Design Problems
  • Data Structure
Contact us

contact@crackingmyinterview.com

  • YouTube
  • Facebook
Copyright 2026 — Cracking My Interview - Your Interview Partner. All rights reserved. Blogsy WordPress Theme