Building an AI Agent in Java with the Embabel Framework
Artificial Intelligence (AI) is steadily moving from research labs into production systems, and developers are seeking frameworks that facilitate easier integration. Embabel is one such emerging framework that introduces a structured, type-safe way to bring Large Language Models (LLMs) into Java applications.
Embabel enables developers to create agents that can understand goals, devise plans, and execute tasks using specific types of data. It uses a Goal Oriented Action Planning (GOAP) model to choose the best way to reach a result. Because it is built on top of Spring AI, it takes advantage of the Spring ecosystem while also adding new features to support intelligent automation.
Embabel was started by Rod Johnson, the founder of the Spring Framework, which provides the project with a clear vision. For enterprise Java developers, this makes it a trustworthy choice for integrating LLMs into real-world applications.
In this article, we’ll create a simple AI agent with Embabel. Our agent will analyse the sentiment of a news article provided via URL and summarise its tone as positive, negative, or neutral, along with a short opinion summary.
1. Core Components of Embabel
Let’s begin with an overview of Embabel’s building blocks.
- Platform: The runtime environment where agents are registered and executed. It comes with an interactive shell for sending commands to agents.
- Agent: A goal-driven entity that uses actions to achieve user objectives.
- Planner: Responsible for constructing a sequence of actions (plan) to meet a goal using GOAP.
- Action: A unit of work performed by an agent (e.g., fetching content, analyzing text). Each action consumes and produces domain objects.
- AchievesGoal: Annotation that declares what a specific action can achieve. This helps the framework map user requests to the correct agent.
- ToolGroups & MCP Servers: Allow integration with external APIs or services, often through Dockerized MCP servers.
2. Project Setup
We’ll set up our project using Spring Boot and Maven. Embabel offers convenient starter dependencies that make initialisation much easier. To get started, include the following configuration in your pom.xml:
<dependency>
<groupId>com.embabel.agent</groupId>
<artifactId>embabel-agent-starter</artifactId>
<version>0.1.1</version>
</dependency>
<dependency>
<groupId>com.embabel.agent</groupId>
<artifactId>embabel-agent-test</artifactId>
<version>0.1.1</version>
<scope>test</scope>
</dependency>
<repositories>
<repository>
<id>central</id>
<url>https://repo.maven.apache.org/maven2</url>
</repository>
<repository>
<id>embabel-releases</id>
<url>https://repo.embabel.com/artifactory/libs-release</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>embabel-snapshots</id>
<url>https://repo.embabel.com/artifactory/libs-snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
This setup ensures you can pull the Embabel dependencies and use them within a Spring Boot project.
3. Configuring the Application
We define Embabel’s configuration in application.yml.
spring:
profiles:
active: shell
ai:
ollama:
base-url: http://localhost:11434
mcp:
client:
enabled: true
name: embabel
version: 1.0.0
request-timeout: 300s
type: SYNC
embabel:
models:
default-llm: mistral:latest
llm-operations:
data-binding:
maxAttempts: 2
Here, we enable the shell profile for interactive runs, configure Ollama for local LLM execution, and connect to an MCP server for external tools. The Embabel section defines the default LLM (mistral:latest) and fine-tunes retry and ranking options.
Application Entry Point
The entry point wires everything together and enables Embabel agents with shell support.
@SpringBootApplication
@EnableAgentShell
@EnableAgents(loggingTheme = LoggingThemes.STAR_WARS,
localModels = {LocalModels.OLLAMA, LocalModels.DOCKER},
mcpServers = McpServers.DOCKER)
public class SocialMediaAnalyzerApplication {
public static void main(String[] args) {
SpringApplication.run(SocialMediaAnalyzerApplication.class, args);
}
}
The @EnableAgentShell annotation activates Embabel’s interactive shell, enabling user commands to be entered and executed. At the same time, @EnableAgents scans for defined agents, configures the models they rely on, and applies the chosen logging theme (in this case, a Star Wars style). Working together, these annotations fully initialize the Embabel runtime, ensuring that agents are registered, discoverable, and ready to process tasks.
4. The Social Media Post Agent
The agent itself is where the workflow is defined. It has two key responsibilities:
- Fetch the article content from the given URL.
- Generate and review a social media post about it.
@Agent(
name = "social-media-post-agent",
description = "Generates a social media post from a given blog post or URL"
)
class SocialMediaPostAgent {
@Action(toolGroups = CoreToolGroups.WEB)
Blog fetchArticle(UserInput userInput, OperationContext context) {
return context.ai()
.withLlm(LlmOptions
.withModel("mistral:latest")
.withTemperature(.7))
.createObject(
"Fetch the article content from the given URL: '%s'".formatted(userInput),
Blog.class
);
}
@Action
@AchievesGoal(description = "Social media post generated and reviewed")
SocialMediaPost generatePost(Blog article, OperationContext context) {
String prompt = """
Create a concise and engaging social media post about the following article.
Ensure the tone is professional but approachable, suitable for platforms like LinkedIn or Twitter.
Title: %s
Content: %s
""".formatted(article.getTitle(), article.getContent());
return context.ai()
.withLlm("mistral:latest")
.createObject(
prompt,
SocialMediaPost.class
);
}
}
The class is annotated with @Agent, which marks it as an Embabel agent. The name parameter ("social-media-post-agent") assigns a unique identifier to the agent, while the description provides metadata describing the agent’s purpose. This makes the agent discoverable and easier to document within the Embabel runtime environment.
The @Action annotation marks methods within the agent as discrete, executable steps. Each annotated method represents a capability of the agent that can be invoked as part of a plan to achieve the user’s request. For instance, one action fetches the content of a blog post from a URL, while another transforms that content into a structured social media post.
The @AchievesGoal annotation is used to indicate that a specific action fulfills the agent’s primary objective. In this case, the generatePost method is marked with @AchievesGoal, which signals to the Embabel planner that when this action runs successfully, the agent has completed its intended task. This mechanism allows the framework to know when the workflow has reached its logical conclusion and the user’s request has been satisfied.
Embabel provides built-in domain models like UserInput and Blog. UserInput captures what the user provides (such as a URL or command), while Blog represents the fetched article with structured fields like title, author, and content, making it easier for the agent to process. The OperationContext supplies utilities the agent needs at runtime, including access to AI functions via context.ai().
A prompt is then built from the blog content to instruct the model to generate a social media post. The .withLlm() method selects and configures the language model (e.g., choosing model type or parameters), and .createObject() sends the prompt to the LLM while mapping the result into a typed object such as SocialMediaPost. This ensures reliable, structured outputs instead of raw text.
Supporting Domain Object
Domain objects define the structured input and output for the agent. This is implemented as a Java record.
record SocialMediaPost(String postContent, String reviewNotes) {
}
5. Running the Agent
Once configured, you can run the application and interact via the Embabel shell. For example:
execute 'Create a social media post for this URL: https://www.yahoo.com/sports/article/three-talking-points-premier-league-201354586.html'
Example Execution Log
When executed, the agent fetches the blog content, generates a social media post, and reviews it before returning the structured result. Below is a sample output log with the Star Wars logging theme enabled:
[com.jcg.example.NewsOpinionAnalyzerApplication.main()] INFO starwars - Chosen Agent I have with confidence 1.0 based on UserInput(content=Create a social media post for this URL: https://www.yahoo.com/sports/article/three-talking-points-premier-league-201354586.html, timestamp=2025-09-16T16:07:43.900360Z)
17:50:05.395 [com.jcg.example.NewsOpinionAnalyzerApplication.main()] INFO starwars - You will find only what you bring in: Created agent instance:
description: Generates a social media post from a given blog post or URL
provider: com.jcg.example
version: 0.1.0-SNAPSHOT
name: social-media-post-agent
goals:
"Social media post generated and reviewed" com.jcg.example.SocialMediaPostAgent.generatePost
preconditions:
hasRun_com.jcg.example.SocialMediaPostAgent.generatePost: TRUE
it:com.embabel.agent.domain.library.Blog: TRUE
it:com.jcg.example.SocialMediaPost: TRUE
value: 0.0
actions:
name: com.jcg.example.SocialMediaPostAgent.fetchArticle
preconditions:
hasRun_com.jcg.example.SocialMediaPostAgent.fetchArticle: FALSE
it:com.embabel.agent.domain.io.UserInput: TRUE
it:com.embabel.agent.domain.library.Blog: FALSE
postconditions:
hasRun_com.jcg.example.SocialMediaPostAgent.fetchArticle: TRUE
it:com.embabel.agent.domain.library.Blog: TRUE
name: com.jcg.example.SocialMediaPostAgent.generatePost
preconditions:
hasRun_com.jcg.example.SocialMediaPostAgent.generatePost: FALSE
it:com.embabel.agent.domain.library.Blog: TRUE
it:com.jcg.example.SocialMediaPost: FALSE
postconditions:
hasRun_com.jcg.example.SocialMediaPostAgent.generatePost: TRUE
it:com.jcg.example.SocialMediaPost: TRUE
conditions:
schema types:
class: com.embabel.agent.domain.io.UserInput
class: com.embabel.agent.api.common.OperationContext
class: com.embabel.agent.domain.library.Blog
class: com.jcg.example.SocialMediaPost
17:50:58.914 [com.jcg.example.NewsOpinionAnalyzerApplication.main()] INFO starwars - Created a process I have: hopeful_murdock
17:50:59.168 [com.jcg.example.NewsOpinionAnalyzerApplication.main()] INFO starwars - [hopeful_murdock] Difficult to see. Always in motion is the future: Ready to plan from:
hasRun_com.jcg.example.SocialMediaPostAgent.fetchArticle: FALSE
hasRun_com.jcg.example.SocialMediaPostAgent.generatePost: FALSE
it:com.embabel.agent.domain.library.Blog: FALSE
it:com.jcg.example.SocialMediaPost: FALSE
it:com.embabel.agent.domain.io.UserInput: TRUE
17:50:59.314 [com.jcg.example.NewsOpinionAnalyzerApplication.main()] INFO starwars - [hopeful_murdock] Control, control, you must learn control! Formulated plan:
com.jcg.example.SocialMediaPostAgent.fetchArticle ->
com.jcg.example.SocialMediaPostAgent.generatePost
goal: com.jcg.example.SocialMediaPostAgent.generatePost
cost: 0.0
netValue: 0.0
from:
hasRun_com.jcg.example.SocialMediaPostAgent.fetchArticle: FALSE
hasRun_com.jcg.example.SocialMediaPostAgent.generatePost: FALSE
it:com.embabel.agent.domain.library.Blog: FALSE
it:com.jcg.example.SocialMediaPost: FALSE
it:com.embabel.agent.domain.io.UserInput: TRUE
From the execution trace, we see that the agent correctly fetched the article content and parsed it into a structured Blog object. The structured output is returned as JSON, making it easy to integrate into downstream systems.
InMemoryBlackboard – Structured Result
You asked: UserInput(content=Create a social media post for this URL: https://www.yahoo.com/sports/article/three-talking-points-premier-league-201354586.html, timestamp=2025-09-16T16:07:43.900360Z)
{
"postContent" : "Catch up on the latest buzz from the Premier League! Here are our top 3 talking points: 📝⚽️ \n1. Team A's impressive comeback win sets a new club record 🏆 \n2. Player B secures the 'Goal of the Month' award for his stunning strike 🎉 \n3. Manager C reflects on the season's challenges and future plans ahead 🎯 Stay tuned for more insights! 🙌 #PremierLeague #FootballNews",
"reviewNotes" : "Post highlights key points from Premier League article, maintains a professional yet engaging tone suitable for LinkedIn or Twitter. Uses bullet points to make the post easily digestible and includes relevant hashtags."
}
LLMs used: [mistral:latest] across 2 calls
Prompt tokens: 647,
Completion tokens: 279
Cost: $0.0000
Additionally, the logs confirm the use of the mistral:latest model across two LLM calls, including details about tokens processed and cost (in this case, zero since it is running locally).
6. Conclusion
In this article, we built a Java-based AI agent using Embabel framework that fetches content from a URL, generates a social media post, and returns the structured output. By combining Spring Boot, Embabel’s annotations, and LLM-powered workflows, we created a streamlined agent that automates part of content creation pipelines.
For more background on using Embabel, see:
- You can build better AI agents in Java than Python
- Ground your AI transformation on what works today
- Building AI Agents in Java with Embabel (YouTube)
There are also example agents under the embabel-agent-starters subdirectory of the official repository, which provides further inspiration for extending your own projects.
7. Download the Source Code
This article explored the Embabel agent framework for Java.
You can download the full source code of this example here: Java embabel agent framework




