Skip to main content
1.0.0-M1 Milestone 1 is available on Maven Central. It is a non-final, draft-status release published for early community feedback, and has not undergone the release review required to ratify a final specification.

About

Artificial Intelligence agents are one of the most prominent developments in enterprise and cloud native computing in decades. They detect events, gather data, generate self-correcting plans, execute actions, process results, and evolve subsequent decisions.

Jakarta Agentic AI aims to do for developing AI agents what Jakarta Servlet did for HTTP processing, Jakarta REST did for RESTful web services, or perhaps most appropriately, Jakarta Batch did for batch processing.

Why would I want this?

It is certainly possible to write script-like agent code that invokes LLMs directly. The problem is that for any reasonably complex agent, you will probably end up with code that is hard to maintain, especially for someone who did not write it. This API gives your agents a long-term, predictable structure that fits the general patterns of agentic systems.

The API shines when agents must adapt their behaviour at runtime. Keep the building blocks of your domain logic simple, and change how they are composed dynamically, possibly in response to LLM interactions. Think of those blocks as the elements of a flow chart that can update itself.

Is this an LLM API like Spring AI or LangChain4j?

No. It is an API that helps you write better AI agents using Jakarta EE. In your agent code you very likely will be using LLMs, so we provide a very simple facade. Implementations will likely use Spring AI or LangChain4j under the hood, and the facade also lets you reach those directly when you need to.

The agent workflow

An agent is a CDI bean. Its workflow is a sequence of annotated methods, and domain objects flow between them through the workflow context.

  1. @TriggerA CDI event starts the workflow
  2. @DecisionDecide whether and how to proceed
  3. @ActionCarry out a step
  4. @OutcomeMark the end of the workflow

@HandleException handles failures arising in any phase.

TypePurpose
@AgentDeclares an agent class. Scope defaults to the workflow.
@TriggerWorkflow entry point, invoked by CDI events.
@DecisionDecides whether and how the workflow proceeds.
@ActionA step within the workflow.
@OutcomeTerminal step; marks the end of the workflow.
@HandleExceptionException handler within the workflow.
@WorkflowScopedCDI normal scope, one context per workflow execution.
LargeLanguageModelInjectable LLM facade with automatic type conversion.
ResultBuilt-in record returning a boolean plus detail from a decision.

An agent, annotated

A fraud detection agent. It does not block a transaction — it marks it suspect and sends notifications.

/*
 * Simple agent for bank fraud detection.
 * Doesn't actually block a transaction but marks it suspect and sends notifications.
 */
// Infers agent type and name by default.
// Default scope is agent workflow, but agents can have application scope.
// Just a CDI bean annotated with @Agent.
@Agent
public class FraudDetectionAgent {

    // Injects default LLM in the implementation, but can be configured to inject specific ones.
    @Inject private LargeLanguageModel model;
    // Regular CDI features just work.
    @Inject private EntityManager entityManager;

    // Initiates the agent workflow. For this initial release, the workflow can only be triggered by
    // CDI events.
    // In the future, there could be many other types of triggers such as Jakarta Messaging,
    // REST POST, or direct invocation from a programmatic life cycle API.
    @Trigger
    // Return type can be void or a domain object stored in the workflow and accessible in
    // the context.
    // Parameters are automatically added to the workflow context.
    private void handleTransaction(@Valid BankTransaction transaction) {
        // Simple check to see if this is a type of transaction that makes sense to check for
        // fraud detection.
        // Could add a bit more data, likely looked up from a database, and return an enhanced
        // version of the transaction or return another domain object entirely.
    }

    // Can return boolean, a built-in Result record type, or any domain object.
    // In this initial release, workflows will automatically end with a negative result.
    // In subsequent releases, more robust decision flows should be possible, either with
    // annotations/EL and/or the programmatic workflow API.
    @Decision
    private Result checkFraud(BankTransaction transaction) {
        /*
         * One of the value propositions of the LLM facade is automatic type conversion in Java,
         * both for parameters and return types.
         *
         * If nothing is specified, it's all strings.
         * Probably only JSON and string are supported initially for conversion.
         * Queries can be parameterized similar to Jakarta Persistence.
         */
        String output = model.query(
            "Is this a fraudulent transaction? If so, how serious is it?", transaction);

        boolean fraud = isFraud(output); // Does some simple custom text parsing.
        Fraud details = null;

        if (fraud) {
            details = getFraudDetails(output); // Does some simple custom text parsing,
                                               // possibly involving database queries.
        }

        return new Result(fraud, details);
    }

    // Only one action here, but there could be multiple actions and/or decisions in sequence.
    // In the initial version, it's just one linear flow.
    // In subsequent releases, the workflow API can define complex flows, including
    // pre-conditions for actions defined via annotation/EL.
    @Action
    // Notice that we are automatically injecting domain objects from the workflow context.
    private void handleFraud(Fraud fraud, BankTransaction transaction) {
        /*
         * IMPORTANT FUNDAMENTAL CONCEPT:
         * This is an example of hard-coded logic, which would still be possible if desired.
         *
         * The power of a programmatic/structured workflow, instead, is that this could change
         * entirely at runtime, driven by further LLM queries.
         * Even for simple, static workflows, the API helps developers think through how agents
         * operate fundamentally - introducing a common vocabulary/patterns.
         *
         * Dynamically altered workflows could possibly be serialized into persistent storage.
         */
        if (fraud.isSerious()) {
            alertBankSecurity(fraud);
        }

        Customer customer = getCustomer(transaction);
        alertCustomer(fraud, transaction, customer);
    }

    // In this initial release, outcomes are essentially the same as actions, but specifically
    // mark the end of the workflow.
    // In subsequent releases, outcomes can do more powerful things such as pass a domain
    // object to a subsequent workflow or agent.
    // This is probably also where it best makes sense to dynamically alter a workflow using
    // a programmatic API.
    @Outcome
    private void markTransaction(BankTransaction transaction) {
        // Mark a transaction suspect, probably in the database.
    }
}

Get it

<dependency>
    <groupId>jakarta.agentic-ai</groupId>
    <artifactId>jakarta.agentic-ai-api</artifactId>
    <version>1.0.0-M1</version>
    <scope>provided</scope>
</dependency>

Requirements

Get involved

The project aims for the broadest industry consensus possible, engaging subject matter experts and API consumers from within the Java and Jakarta EE ecosystem as well as outside it. Version 1.0 is intentionally minimal, and we aim to iterate quickly based on evolving industry knowledge and user feedback.

Standalone specification

The project does not initially seek inclusion into the Jakarta EE Platform or any profile. Rather, it provides a usable standalone API under the Jakarta EE umbrella that vendors may choose to adopt. In the future it may make sense to define a Jakarta EE profile for AI in general, to which this project could be added.