DEV Community

Shane Johnson
Shane Johnson

Posted on

Getting started with agentic workflows in Java and Quarkus

This post walks through building and running a real-world agentic workflow with Agentican and Quarkus.

Specifically, an agentic workflow to automate market research and information sharing:

  1. Identify the top vendors within a market category.
  2. Research the positioning and strengths of each vendor.
  3. Classify the findings as either standard or urgent.
  4. Draft a brief to share with others in the company.

Prerequisites

  • Quarkus
  • Java 25
  • Maven (or Gradle)
  • LLM provider API key

Step 1: Add the dependency

Create a Quarkus app, and add the Agentican Quarkus runtime module:

<dependency>
    <groupId>ai.agentican</groupId>
    <artifactId>agentican-quarkus-runtime</artifactId>
    <version>0.1.0-alpha.4</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode

Step 2: Define agents, skills and the workflow

Create an agentican-catalog.yaml file on the classpath.

This is where you describe:

  • Who does the work (agents).
  • What they need to do it (skills).
  • How they will do it (workflows).
agents:
  - id: researcher
    name: researcher
    role: |
      Expert at finding accurate, sourced information about 
      companies and markets. Quotes sources. Distinguishes 
      opinion from fact.

  - id: writer
    name: writer
    role: |
      Synthesizes research into structured, concise briefs. 
      Avoids hedging language. Cites concrete evidence.

skills:
  - id: web-search
    name: web-search
    instructions: |
      When a question requires external information, call the 
      search tool first. Quote sources in your answer.
Enter fullscreen mode Exit fullscreen mode

Update the agentican-catalog.yaml file to define the workflow.

workflows:
  - id: market-brief
    name: market-brief
    description: Research vendors in a market and produce a 
       structured brief
    outputStep: deliver
    params:
      - name: topic
        description: Market to research
        required: true

      - name: vendor_count
        description: Number of vendors
        defaultValue: "5"
    steps:
      - name: identify
        agent: researcher
        skills: [web-search]
        instructions: |
          Identify the top {{param.vendor_count}} vendors in 
          {{param.topic}}. Return a JSON array of vendor names
          — names only, no commentary.

      - name: deep-dive
        type: loop
        over: identify
        steps:
          - name: analyze
            agent: researcher
            skills: [web-search]
            instructions: |
              Deep-dive vendor {{item}}: positioning, key 
              strengths, recent news. Quote sources.

      - name: classify
        agent: writer
        instructions: |
          Read the per-vendor deep-dives below. If any vendor has 
          launched a competitive feature in the last 30 days, 
          return the single word 'urgent'. Otherwise return 
          'standard'.

          Deep-dives: {{step.deep-dive.output}}
        dependencies: [deep-dive]

      - name: deliver
        type: branch
        from: classify
        default: standard
        branches:
          - name: urgent
            steps:
              - name: urgent-brief
                agent: writer
                instructions: |
                  Synthesize a vendor brief flagged URGENT for 
                  executive review. Lead with the recent 
                  competitive moves.

                  Topic: {{param.topic}}
                  Deep-dives: {{step.deep-dive.output}}

          - name: standard
            steps:
              - name: standard-brief
                agent: writer
                instructions: |
                  Synthesize a vendor brief.

                  Topic: {{param.topic}}
                  Deep-dives: {{step.deep-dive.output}}
Enter fullscreen mode Exit fullscreen mode

A few things worth flagging:

  • agent: researcher references the agent for an agent step.
  • outputStep designates the step whose output becomes the workflow's result.
  • {{param.X}} interpolates workflow inputs into step instructions.
  • {{step.X.output}} interpolates an upstream step's output.
  • {{item}} is the current value inside a loop iteration.
  • type: loop steps take an over reference (a step that produced a list).
  • type: loop steps run their nested steps once per item, and in parallel.
  • type: branch steps take a from reference (step used to select branch).
  • branches: mutually exclusive steps with default for unrecognized values.

The framework loads agentican-catalog.yaml from the classpath at boot, or you can define where it's loaded from:

agentican.catalog-config=/etc/agentican/agentican-catalog.yaml
Enter fullscreen mode Exit fullscreen mode

Note: Agents, skills and workflows can be defined via a fluent builder API as well.

Step 3: Configure the models

Agentican loads runtime configuration from application.properties. The minimum is one LLM:

agentican.llm[0].api-key=${ANTHROPIC_API_KEY}
Enter fullscreen mode Exit fullscreen mode

The provider defaults to anthropic, and the model defaults to claude-sonnet-4-5.

OpenAI

agentican.llm[0].provider=openai
agentican.llm[0].api-key=${OPENAI_API_KEY}
agentican.llm[0].model=gpt-4o-mini
Enter fullscreen mode Exit fullscreen mode

Multiple, named LLMs

agentican.llm[0].name=default
agentican.llm[0].api-key=${ANTHROPIC_API_KEY}

agentican.llm[1].name=efficient
agentican.llm[1].provider=openai
agentican.llm[1].api-key=${OPENAI_API_KEY}
agentican.llm[1].model=gpt-4o-mini
Enter fullscreen mode Exit fullscreen mode

Step 4: Create a typed workflow instance

Define the workflow input and output records:

public record ResearchParams(String topic, int vendorCount) {}

public record VendorBrief(String topic, List<Vendor> vendors) {

    public record Vendor(String name, 
                         String positioning, 
                         List<String> strengths) {}
}
Enter fullscreen mode Exit fullscreen mode

Then inject the typed workflow, and call it from a REST endpoint:

@Path("/market-brief")
public class VendorBriefResource {

    @Inject
    @AgenticanWorkflow(name = "market-brief")
    Workflow<ResearchParams, VendorBrief> brief;

    @POST
    @Path("/{topic}")
    public VendorBrief generate(@PathParam("topic") String topic) {

        return brief.start(new ResearchParams(topic, 5)).await();
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, test the endpoint:

curl -X POST http://localhost:8080/market-brief/databases 
Enter fullscreen mode Exit fullscreen mode

A few things worth flagging — they're what set this apart from a generic "call an LLM" library:

  • ResearchParams.vendorCount becomes the workflow parameter vendor_count via SNAKE_CASE mapping.
  • start() returns a WorkflowRun<VendorBrief>. await() parses the output step's text into a VendorBrief.
  • @AgenticanWorkflow(name = "vendor-brief") resolves the registered workflow at injection time.

The WorkflowRun itself exposes future() for a CompletableFuture<R>, and there's a ReactiveWorkflow<P, R> Mutiny variant for Vert.x stacks.

Step 5: Add tools (MCP and Composio).

Agentican ships two integrations out of the box:

MCP (Model Context Protocol)

There is one config block per server. Tools are auto-discovered:

agentican.mcp[0].slug=github
agentican.mcp[0].name=GitHub
agentican.mcp[0].url=https://mcp.github.com/sse
agentican.mcp[0].headers.Authorization=Bearer ${GITHUB_TOKEN}
Enter fullscreen mode Exit fullscreen mode

Composio

100+ SaaS toolkits — Slack, Notion, Linear, Salesforce, GitHub, Google Workspace:

agentican.composio.api-key=${COMPOSIO_API_KEY}
agentican.composio.user-id=user-123
Enter fullscreen mode Exit fullscreen mode

Tools are reference by name within agent steps:

steps:
  - name: research
    agent: researcher
    tools: [github_search_repositories]
    instructions: "Profile open-source vendors in {{param.topic}}."
Enter fullscreen mode Exit fullscreen mode

Where to go next

Structured agentic workflows for the JVM.

GitHub →

Top comments (0)