Enterprise Java

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:

  1. Fetch the article content from the given URL.
  2. 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:

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.

Download
You can download the full source code of this example here: Java embabel agent framework

Omozegie Aziegbe

Omos Aziegbe is a technical writer and web/application developer with a BSc in Computer Science and Software Engineering from the University of Bedfordshire. Specializing in Java enterprise applications with the Jakarta EE framework, Omos also works with HTML5, CSS, and JavaScript for web development. As a freelance web developer, Omos combines technical expertise with research and writing on topics such as software engineering, programming, web application development, computer science, and technology.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Back to top button