Your backend already exists. It runs on the JVM, it has services, configuration and tests, and increasingly it needs to do AI things — call a model, expose some tools, run an agent loop. Sprout lets you add those capabilities right where your code already lives, as ordinary beans that drop into the dependency injection, configuration and tests you already use.
Sprout is a foundation for building AI tooling in Java — a simple, modular, Spring-style and Spring-compatible IoC container with a provider-agnostic model layer, automatic tool/JSON-schema plumbing and Model Context Protocol support, all behind one open extension SPI. A cohesive base like this is still uncommon in the Java ecosystem, where most options are thin API clients. Sprout is meant to be the cornerstone you keep building AI tooling on — and, with the Spring Boot starter, to fit straight into the backend you already maintain.
It's 1.1.0 on Maven Central and Apache-2.0 on GitHub. Here's the tour.
An agent is just an annotated class
The flagship capability today is agents. An agent is an annotated class that extends AgentExecutor — declare a model, write @Tool methods, and Sprout runs the model/tool loop for you:
@Agent(model = AnthropicModelExecutor.class, systemPrompt = "You are a helpful assistant.")
public class WeatherAgent extends AgentExecutor {
@Tool(description = "Look up the weather forecast for a city")
public String lookup(String city) {
return "Sunny, 25°C in " + city;
}
}
SproutContainer container = SproutApplication.run(MyApp.class);
AgentExecutor agent = container.getSingleton("weatherAgentExecutor");
System.out.println(agent.execute("session-1", "What's the weather in Madrid?").response());
Notice the symmetry: a @Model class extends ModelExecutor, and a @Agent class extends AgentExecutor — the agent is its own executor. The executor loops: it sends the conversation to the model, dispatches any tool calls, feeds the results back, and repeats until the model produces a final answer or maxIterations is reached.
The @Tool parameter JSON-Schema is generated automatically from the method signature (compile with -parameters) — primitives, enums and collections map across, and every parameter is required by default. Add @ToolParam to describe a parameter or mark it optional; a good description noticeably improves how reliably the model fills the argument in. Tools are ordinary methods backed by the services you already have.
Conversation history is persisted through an AbstractConversationStore — a thread-safe in-memory one by default; swap in your own to persist it. And to stream a run instead of blocking, call executeStream(conversationId, prompt, listener): assistant tokens, tool calls and the final response arrive through a StreamListener as they happen.
A real IoC container, not a bag of clients
Everything above sits on a Spring-style container. You annotate classes and let it discover, instantiate and wire them:
-
@Component/@Servicemark managed singletons. -
@Autowiredinjects by type into fields or through a constructor;@Qualifierselects a specific bean by name. -
@PostConstructruns initialisation once dependencies are injected. -
@Valueand@Configuration/@ConfigurationPropertybind configuration, with Spring-style${key:default}placeholders resolved againstsprout.properties, system properties and environment variables.
The richer stereotypes — @Service, @Model, @Agent, @ConversationStore, @Mcp — are all meta-annotated with @Component. So a model and an agent are ordinary managed singletons too: the container builds and wires them, and you inject them anywhere exactly like any other bean. They are not a separate kind of object.
Provider-agnostic models
A model is a ModelExecutor subclass annotated with @Model. sprout-anthropic and sprout-openai ship implementations; you can add your own — including offline, deterministic stubs for tests — by extending ModelExecutor and implementing chat(ModelRequest). Swapping providers doesn't touch your agent code: the agent depends on a ModelExecutor, not on a vendor SDK.
MCP, both sides
With sprout-mcp on the classpath, an @Mcp bean's @Tool methods are published over the Model Context Protocol — no agent required. And an @Agent can connect to remote MCP servers with @UseMcp and use their tools as if they were its own. The same @Tool methods that power an agent can be exposed as a server.
Multi-agent orchestration
sprout-orchestration adds three patterns that compose around plain AgentExecutor beans.
Concurrent runs. An AgentOrchestrator wraps an agent to run several prompts at once. Each execute is scheduled on a worker thread and returns immediately, so calls fan out concurrently; a failing run is isolated and never tears down the others:
try (AgentOrchestrator orchestrator = AgentOrchestrator.of(agent)) {
orchestrator.execute("Tell me about Mars", "mars", "mars-session")
.execute("Tell me about Venus", "venus", "venus-session")
.waitForExecutions();
System.out.println(orchestrator.getResult("mars").block().response());
}
Delegation. One agent delegates to specialists. An AgentDelegation exposes a set of specialist agents to a supervisor as tools — one per specialist — through the very same ToolProvider SPI the agent already uses for its @Tool methods and MCP servers, so the executor needs no special-casing:
AgentDelegation.builder()
.specialist("math", "Solves arithmetic and number problems.", mathAgent)
.specialist("history", "Answers history and general-knowledge questions.", historyAgent)
.attachTo(supervisor);
System.out.println(supervisor.execute("session", "What is 6 times 7?").response());
Hand-off. Instead of calling a specialist as an isolated sub-task, an agent transfers control. An AgentHandoff gives each team member a handoff_to_<member> tool; the conversation passes to that agent, which continues the same shared transcript and produces the final answer — while still applying its own system prompt to its turns:
AgentHandoff team = AgentHandoff.builder()
.member("triage", "First point of contact; routes the user.", triageAgent)
.member("billing", "Handles invoices, payments and refunds.", billingAgent)
.member("tech", "Handles login, passwords and technical errors.", techAgent)
.build();
AgentHandoff.HandoffResult result = team.run("I have a question about my invoice.");
System.out.println(result.path()); // [triage, billing]
System.out.println(result.response()); // the billing agent's answer
The three compose: because delegation happens inside an agent's own run, a delegating supervisor is just an AgentExecutor — so you can orchestrate it concurrently and make it the target of a hand-off, all around one hub.
Fully Spring-compatible — in both directions
Add sprout-spring-boot-starter and the container is bootstrapped during context startup, with no glue code:
-
Sprout → Spring: every Sprout bean (agents' executors, models, services...) is registered in the
ApplicationContext, so you can@Autowiredit into any@Controller/@Service. -
Spring → Sprout: a Sprout component can depend on a Spring bean; it is resolved from the
BeanFactoryas a lazy proxy, preserving AOP/@Transactional.
So an agent can be backed by an existing Spring @Service and called straight from a controller:
@Agent(model = AnthropicModelExecutor.class, systemPrompt = "You are a weather assistant.")
public class WeatherAgent extends AgentExecutor {
private final WeatherService weather; // an existing Spring @Service
@Autowired
public WeatherAgent(WeatherService weather) { // Spring bean injected into the agent
this.weather = weather;
}
@Tool(description = "Look up the forecast for a city")
public String forecast(String city) {
return weather.forecast(city);
}
}
The runnable Spring example pushes this all the way: a /weather/batch endpoint is a plain @RestController that fans a forecast-per-city out concurrently with AgentOrchestrator, over a Spring-managed @Agent whose tool is a Spring @Service and whose conversations persist to a database via JPA. One HTTP call drives concurrent multi-agent work with Spring DI and JPA persistence — all inside the same application, using the framework and the beans you already run.
Customizable to the core
Here's the part I'm most proud of: the processing pipeline is fully open, and it is the same mechanism the built-in features use — there is no privileged core. To teach Sprout a new annotation, subclass ComponentProcessor and register it with @Processor(MyAnnotation.class); it's discovered automatically and applied to every component carrying that annotation. A whole custom stereotype is only a few lines:
@Retention(RUNTIME) @Target(TYPE) @Component // a custom stereotype, discovered by scanning
public @interface Plugin {}
@Processor(Plugin.class) // applied to every @Plugin component
public class PluginProcessor extends ComponentProcessor {
public PluginProcessor(Class<?> component, SproutContainer container) {
super(component, container);
}
@Override
public Set<String> beanNames() { // standard wiring still runs; here we add an alias
Set<String> names = new HashSet<>(super.beanNames());
names.add("plugin:" + component.getSimpleName());
return names;
}
}
This is exactly how @Agent, @Model, @Configuration and @Mcp are implemented — each lives in its own module and plugs in without the core knowing about it. The same door is open to your code and to third-party modules, so the framework can grow a module for anything: a new model provider, a transport, a persistence-backed conversation store, custom stereotypes.
The modules
| Module | What it provides |
|---|---|
sprout-core |
IoC container, component scanning, DI, configuration, and the agent/model/tool abstractions. |
sprout-anthropic |
ModelExecutor for Anthropic's Messages API (@Model("anthropic")). |
sprout-openai |
ModelExecutor for OpenAI's Chat Completions API (@Model("openai")). |
sprout-mcp |
Expose @Tool methods as an MCP server, and consume remote MCP servers from an agent. |
sprout-orchestration |
Concurrent runs, supervisor delegation and conversation hand-off. |
sprout-spring-boot-starter |
Runs Sprout inside Spring Boot, bridging beans and configuration both ways. |
Requirements: Java 21, Maven 3.9+.
Where it's headed
This is an early release, so the surface is deliberately focused — expect gaps and rough edges. On the roadmap: nested-object tool parameters, built-in JDBC/Redis conversation stores, real SSE provider streaming, more providers (Gemini, Azure OpenAI, Ollama), memory & RAG, observability/token accounting, structured output, human-in-the-loop guardrails, and GraalVM native image.
Try it
<dependency>
<groupId>io.github.ivannavas</groupId>
<artifactId>sprout-core</artifactId>
<version>1.1.0</version>
</dependency>
- GitHub: https://github.com/ivannavas/sprout-ai-framework
- Maven Central: https://central.sonatype.com/namespace/io.github.ivannavas
If you build AI features on the JVM — or wish you could without leaving it — I'd love your feedback, issues and stars. What would you want a Java-native agent framework to do next?













