Home / Articles

Adding AI Agents to Spring Boot Microservices With Spring AI

Adding AI agents to Spring Boot services with Spring AI: tools as beans, circuit breakers, token metrics and async agent loops inside the code you already run.

Publisher: REACTAPP.TOP17 min read3863 words

This article is published in English.

Sources: How to Integrate AI Agents Into Spring Boot Microservices | 2026 Java AI Guide

Picture three Spring Boot microservices that already carry production traffic, six weeks of work to give them agent features, two of them shipping cleanly and the third turning into a long, expensive lesson. That scenario, which stands in for the kind of experience many Java teams describe, frames what follows. It's a practical look at wiring agentic pipelines into existing services that have latency budgets, dependent teams and real users, written for Java backend engineers who know Spring Boot and want AI capabilities without throwing away the architecture they maintain.

The pages ahead cover what an AI agent actually is in a Spring Boot setting (and isn't), why teams are adopting them, how to implement tool calling with Spring AI, three failure modes that tend to appear only after go-live, and how to treat token consumption as a first-class operational metric.

What an agent is, and what it isn't

Before any code gets written, the mental model has to be right. Most engineers who struggle with agent integration in Java aren't blocked by the framework. They're applying the wrong model to it.

An agent isn't a clever API call. It's a reasoning loop: a language model receives a task, chooses which tools to call, gets the results back, reasons over everything it now knows and either returns a final answer or runs another turn.

Four ingredients make up the loop:

  • The model is the reasoning engine, whether that's GPT-4o, Claude, Gemini or an open-weight alternative served through Ollama.
  • The tools are the functions, APIs or service calls the model is allowed to invoke.
  • The loop is the multi-turn cycle that separates an agent from a single LLM call.
  • The context is the growing pile of conversation history, tool results and instructions that feeds each reasoning step.

Inside a Spring Boot microservice, this means you aren't bolting a RestTemplate-style wrapper around an AI endpoint. You're embedding a multi-step, stateful and non-deterministic workflow into a service designed for bounded, synchronous and predictable execution. That mismatch is where most Java AI integrations get into trouble. A short way to remember it: the tool isn't the agent, the loop is, and until that sinks in you'll end up building endpoints and not agents.

The case for adopting agents in Spring Boot services

Tutorials tend to skip this part. They show how to integrate an agent and say little about why an organization should, which problem it solves or what adopting it too early costs.

Beyond fixed rules

Classic microservices automate known procedures, where input A runs through rule set B to yield output C, and that suits deterministic flows superbly. Agents stretch the idea to ambiguous, multi-step tasks in which the correct next action depends on intermediate results. Some examples that fit existing Spring Boot architectures:

  • Order processing. One agent checks inventory, evaluates pricing rules, applies customer-specific discounts and produces a fulfillment recommendation in a single request, so the frontend doesn't have to orchestrate four sequential calls.
  • Document understanding. An agent reads an uploaded PDF, extracts structured fields, validates them against business rules and either confirms or hands the case to a human reviewer, with no hardcoded extraction rules.
  • Customer support. Inside an existing support service, an agent pulls account state, order history and policy documents and drafts a contextually accurate reply, which cuts handling time without removing the human from the loop.
  • Code review and CI help. In a platform engineering pipeline, an agent analyzes failing tests, consults internal documentation and proposes targeted fixes.

In each of these the value isn't "AI" as such. It's swapping brittle, handwritten orchestration logic for a reasoning layer that adapts to what it finds instead of breaking when the input leaves the happy path.

Why Spring Boot suits the job

Python dominates AI tutorials, while Java dominates enterprise production systems, and the gap between the two is a genuine opportunity. Spring Boot offers structural advantages that lightweight Python frameworks don't give you out of the box:

  • Dependency injection keeps tool beans managed, testable and composable without extra configuration.
  • Actuator and Micrometer turn token usage, agent-turn latency and tool error rates into ordinary operational metrics.
  • Async primitives such as @Async and CompletableFuture cope well with agent loops whose running time varies.
  • Resilience4j wraps tool calls in circuit breakers and retries, blocking cascading failures.
  • Spring Security lets authentication and authorization for agent-facing tool endpoints plug into existing access control.
  • Mature operations tooling means existing stacks such as Prometheus, Grafana or Datadog can extend to agent metrics with no new infrastructure.

On top of that, Spring AI supplies LLM clients, vector store integration and @Tool annotation-based function calling inside the same ecosystem, so building a production-grade agent doesn't require leaving the Spring way of working.

When an agent is worth it

Agents earn their place when:

  • The workflow needs decisions based on information discovered during execution, not information known when the request arrived.
  • The current implementation makes the frontend or an orchestrator service coordinate several backend calls, and the agent could own that coordination on the server.
  • The input is natural language of variable structure, with classification, extraction, summarization or routing driven by content instead of a schema.
  • The present system fails in the "wrong but doesn't crash" way, and an agent could be designed to surface uncertainty rather than silently return the likeliest wrong answer.

They're a poor fit when the workflow is fully deterministic, the inputs are structured and the rule-based logic is maintainable. Agents add complexity, and that is only worth paying when the alternative is worse.

The business argument in plain terms

For engineering leads and product stakeholders, the argument rests on three levers:

  1. Faster delivery of new capability. Features that once needed weeks of backend orchestration code can often be prototyped in days when an agent handles the multi-step coordination. Teams commonly report this speedup after a first successful integration, though your numbers will vary.
  2. Less brittle rule maintenance. Each hardcoded rule in a service is debt that grows when business logic changes, and instructions written in natural language are generally easier to update than code expressing the same logic imperatively.
  3. A base for compound AI systems. One well-integrated agent is the foundation for multi-agent workflows, retrieval-augmented generation pipelines and AI-native product features.

Why the urgency, and the shift in execution model

Through 2023 and into 2024, AI in Java meant little more than posting to an LLM endpoint and handing back whatever text came out: plain HTTP behind a WebClient, a timeout, and no further thought.

That model no longer holds. Spring AI's feature set (tool and function calling, vector store integration and a ChatClient API built with agentic workflows in mind) signals that the execution model itself has changed.

  • The old shape was a single request leading to one LLM call and one response.
  • The new shape is a request that fans out into N tool calls and M reasoning steps before a single response emerges.

Plenty of Spring Boot engineers still apply the old model and treat ChatClient like a RestTemplate. That category error produces production incidents weeks after go-live, and they're seldom obvious when they arrive.

The loop is the agent

Engineers new to agent integration usually picture a service standing in for a controller: one @Service bean, one injected ChatClient, finished. That's an autocomplete endpoint, not an agent. A real agent runs a loop of receive task, select tool, execute tool, receive result, reason over the new state, then decide whether to continue or answer. The model is the reasoning engine inside the loop; the loop is the agent.

Why the usual design misjudges the cost

The common assumption is that the LLM call is the expensive, complicated part and a Spring @Service wrapper handles the rest. In reality, "the rest" is now distributed: how slow the tools are, what state the context carries from turn to turn, how errors travel along multi-step chains, and retries that must cope with half-finished work.

Consider a hypothetical order-classification service. It begins as one POST /orders/classify endpoint making one LLM call. It's then refactored into an agent that classifies the order, calls an inventory tool, calls a pricing tool depending on stock, and returns a fulfillment recommendation. Each tool call is a real service call with real latency.

Suppose the agent finishes in under two seconds for 80% of requests, while for the remaining 20% a slow upstream inventory service stretches responses to 8 to 14 seconds. That isn't an AI problem. It's a distributed-systems problem that the agent loop exposed.

The trade-off that gets overlooked

An agent loop isn't stateless. Each tool result is appended to the conversation and sent to the model again on the next turn. At high throughput, what each request costs in memory grows with how many turns the agent takes, and the raw request count matters less. A service handling 1,000 requests a minute at four average turns is effectively processing 4,000 LLM calls, with context sizes compounding.

Before writing any agent code, then, list every tool call, estimate each one's p95 latency and work out the worst-case context size at the maximum number of turns. Budget the context before designing the loop. Most agent failures in Java backends turn out to be timeout failures wearing a model-failure costume.

Spring AI gives you plumbing, not a plan

Spring AI is a serious, production-minded abstraction layer. It offers ChatClient, ToolCallback, VectorStore and an agent execution API that works with dependency injection, model switching through application.properties and reactive support through Flux. As a starting point for LLM integration in Spring Boot, it's the right one.

What it doesn't give you is a strategy for agentic pipelines, and that gap separates prototypes from production services. The framework manages individual tool calls and model interactions. It doesn't handle:

  • Contracts at the service boundary between the agent and downstream services.
  • Persistence of agent state across requests, meaning session memory.
  • Circuit-breaker logic for failures that span several steps.
  • Workflow-level observability, such as understanding why the agent picked a particular sequence of tools.

Write a tool failure matrix

Before go-live, define for every @Tool method what happens in three situations:

  • The tool returns the expected output. The agent proceeds to the next reasoning step.
  • The tool returns an error. The agent receives the error signal and decides whether to retry, use a fallback or tell the user.
  • The tool times out. A circuit breaker fires, a fallback value comes back and the agent is told the data is unavailable.

If the second and third cases are blank for any tool, the integration isn't done. The happy path isn't the service. Spring AI handles the how; the why, the when and the what-if-it-fails remain yours.

Context windows behave like unwritten contracts

In an ordinary Spring Boot service, breaking a contract throws an exception. In an agent, overrunning the context window produces no exception, no alert and no error log. The model quietly drops the oldest content in the conversation, and that's frequently the system prompt carrying your business rules. A REST API answers with a 400 when you break its contract; a context window just starts forgetting, which is a much harder bug to find.

An illustrative incident

Imagine a document-processing service whose system prompt runs about 800 tokens. In testing, tool responses average roughly 1,600 tokens per request, well within the model's limit. In production, one particular workflow triggers all three tool calls with big payloads, among them a nested order-history response of around 1,800 tokens by itself. Total context passes 6,000 tokens, the system prompt gets silently truncated and the agent returns a plausible answer that omits a critical business rule.

Nothing throws and nothing alerts. The bug surfaces in a weekly audit three days later.

The remedy: token metrics from day one

Instrument context consumption before shipping. With Micrometer, that means reading the prompt and generation token counts from the response metadata's usage object and publishing each as a gauge, for example under names like agent.context.input_tokens and agent.context.output_tokens. Exact accessor names differ between Spring AI versions, so check the usage API of the release you run. Then alert at about 80% of the model's context limit. That's roughly two beans and a configuration property, and it belongs in place before go-live. Shipping an agent with no token metrics is like running a database with no query-time monitoring, and it ends at 2 AM.

Building it step by step

Prerequisites: Spring Boot 3.2 or newer, Spring AI 1.0 or newer with the starter for your model provider (an OpenAI starter in this example), a working grasp of @Async and CompletableFuture, an internal service to expose as a tool, and Micrometer wired into your observability stack. Starter artifact names have changed across Spring AI releases, so confirm the current one in the reference docs.

Step 1: add the dependencies

In the pom.xml, add the Spring AI starter for your provider, plus the resilience4j-spring-boot3 artifact if you want circuit-breaker support on tool calls. Model credentials go in application.properties. Read the API key from an environment variable such as OPENAI_API_KEY through the spring.ai.openai.api-key property, pick the chat model (the example uses gpt-4o) and keep the temperature low, around 0.2, so tool use stays predictable.

Step 2: define the tools before configuring the agent

Order matters because tool design drives agent behavior. A system prompt should refer to concrete tool capabilities, and writing it before the tools exist produces vague instructions that confuse the model.

In the example, an OrderTools component receives an InventoryService and a PricingService through its constructor and exposes two annotated methods. checkInventory(String sku) returns the available quantity, or -1 when the SKU is unknown. getPrice(String sku, String customerTier) returns a price in USD cents. Each method carries a @Tool annotation with a description.

The common mistake is making those descriptions vague. The model reads them to decide when to call each tool, so precise and complete descriptions mean fewer needless calls and more accurate answers. Spell out units, return values and the not-found case, as the examples above do.

Step 3: give the ChatClient a short system prompt

An AgentConfig class defines a ChatClient bean from the injected builder and the OrderTools. The default system prompt is short and imperative: act as an order-processing assistant, always check inventory before confirming availability, always fetch pricing before quoting a price, return a JSON object with an available boolean, an integer price in USD cents and a recommendation string, and never invent stock levels or prices. The tools are registered with defaultTools.

Discipline with tokens pays here. Every token in the system prompt shrinks the room left for tool results, so aim for under 300 tokens and edit without mercy.

Step 4: run the agent asynchronously and log tokens

An OrderAgentService holds the ChatClient and a MeterRegistry. Its processOrder method returns a CompletableFuture<String> and runs the agent call inside supplyAsync: send the user input, call the model, take the chat response, read the input and output token counts and record them as gauges, then return the text of the result.

Why async? An agent's running time scales with the number of tool calls, not with the apparent complexity of the request. A synchronous controller thread blocked for 8 to 14 seconds on a busy service will exhaust the thread pool. Treat agent execution as a background job from the very beginning.

Step 5: put a circuit breaker on every tool

Annotate each tool method with Resilience4j's @CircuitBreaker, naming the breaker (say inventoryTool) and a fallback method. The fallback for the inventory tool logs a warning with the SKU and returns -1, which tells the agent that inventory data is unavailable. Thresholds belong in application.properties: a sliding window of 10 calls, a 50% failure-rate threshold and a 10-second wait in the open state.

If breakers never seem to fire, remember that Spring AOP only works when calls pass through the Spring proxy. Inject tool components properly and don't instantiate them directly.

Step 6: expose an endpoint with an async response pattern

An OrderAgentController mapped to /agent accepts a POST to /orders, generates a request ID, starts the agent and stores the future in a concurrent map, then answers 202 Accepted with the ID. A GET on /orders/{requestId}/result looks up the future. It returns 404 when the ID is unknown, 202 with a "Processing" message while the future is unfinished, 200 with the result when done and 500 with an error message if execution failed. For anything beyond a demo, replace the in-memory map with a store that survives restarts and expires old entries.

A decision fork applies to the tools themselves. For internal service calls with low latency, keep tools synchronous and handle async at the service layer. For external APIs with unpredictable latency, make the tool async with a dedicated thread pool and a hard timeout, so one slow dependency never leaks uncapped latency into the agent's turn budget.

Tools worth knowing

  • Spring AI is the core agent framework for Spring Boot.
  • LangChain4j is a Java-native alternative, often cited as stronger on multi-agent coordination.
  • Resilience4j provides circuit breakers and retries for tool calls.
  • Micrometer publishes token usage and agent metrics.
  • Ollama runs open-weight models locally for development and testing.

Three mistakes that burn time and credibility

The beginner trap: testing only with happy-path inputs. Tutorials use clean, cooperative test data, but real users submit inputs that run long, stay ambiguous or set off unusual tool behavior seen only in production data. Build a harness that replays anonymized production request samples, not synthetic ones, against the agent in staging before each release, and test at minimum with adversarial inputs that exceed the expected context budget.

The expert blind spot: over-engineering before shipping. Engineers steeped in multi-agent literature sometimes arrive with orchestrators, sub-agents, memory layers and a RAG pipeline before a single agent turn has reached a real user. It's the compound-AI version of building a service mesh before validating the monolith. Ship one agent with one set of tools in one service, measure it and add complexity only where the data asks for it. The teams that reach production fastest resisted architecting a platform before they had a product.

The systemic failure: nobody owns the agent's behavior. A conventional failure leaves a log line, a metric and an alert. A wrong-but-plausible agent answer leaves none of them: no exception, no alert, just a reply that passes schema validation and fails business logic. Name an owner for what the agent says, not merely whether it responded, and review a sampled set of responses every week instead of looking only at error rates. That's an organizational decision more than a technical one.

What the getting-started guides leave out

By most accounts, the hardest part of Spring Boot agent integration isn't the AI. Model behavior, prompt engineering and the tool-calling interface are learnable in about a week. The hard part is Java's synchronous heritage colliding with the asynchronous, multi-turn nature of agent execution.

Spring Boot's request-response model assumes bounded execution time. An agent doesn't. It makes as many tool calls as the task needs and runs as many reasoning turns as necessary, with no awareness of your 30-second gateway timeout. Agentic pipelines don't fit the request-response shape; they fit an event-driven one, and the sooner you stop resisting that the faster the integration ships.

Teams that struggle most try to keep synchronous handling all the way through to the agent's reply. Those who ship fastest run the agent as a background job, start it on request, deliver the result by polling or WebSocket and design the service contract around that. The reframe is missing from the Spring AI getting-started material, and arguably shouldn't be.

The mental model to carry forward

Everything above condenses into one sentence: an AI agent is a distributed system with a non-deterministic execution engine inside it.

The distributed-systems principles a Java engineer already knows (isolating faults, observing behavior, bounding execution time, spelling out failure contracts) all carry over, with more force, since failures here are quieter and take longer to show up. The encouraging part is that Spring Boot suits the work well. Dependency injection, Actuator-based observability, async primitives and a mature operational ecosystem are real advantages, and Spring AI is a serious framework. The gap lies in the mental model, not in the tooling.

The practical rules:

  • Treat an agent as a reasoning loop and design the service contract around the loop, not the model call.
  • Adopt agents when the workflow depends on information found mid-execution, or when existing orchestration is brittle and hard to maintain. Skip them when the workflow is deterministic and the rule-based version works.
  • Spring AI supplies the framework; you supply the strategy for tool failures, context budgets and observability.
  • Treat context windows as contracts and instrument token consumption before go-live, not after the first incident.
  • Run agent execution asynchronously from day one, since an event-driven design cooperates where request-response fights you.

Common questions

Which framework is best for agents in Spring Boot? Spring AI is the most idiomatic option for Spring Boot applications. LangChain4j is a strong alternative with broader support for multi-agent coordination, and both are actively maintained.

Must existing microservices be rewritten? No. The approach here extends a service through Spring AI's @Tool annotation, a ChatClient bean and @Async execution without touching existing service logic.

How should agent failures be handled in production? Wrap every @Tool method in a Resilience4j circuit breaker, publish token consumption through Micrometer, define a tool failure matrix before go-live and give someone ownership of reviewing response quality, not only error rates.

How does an agent differ from a plain LLM call? A plain call is one request and one response. An agent is a loop in which the model can call tools, read the results, reason about them and decide to continue or answer across several turns.

Is Spring Boot ready for production agent workloads? Yes, given the right architectural choices. Async execution, circuit breakers, token metrics and explicit context-window management are all achievable with existing, stable libraries.

Where does RAG fit? RAG is one tool pattern among others: the agent retrieves relevant documents from a vector store to enrich its context before reasoning. It helps when an agent needs a large knowledge base, isn't required for every integration and adds complexity best introduced only once a context-window limit is causing real problems.