Getting Started With Agentic Workflows in Java and Quarkus
A step-by-step tutorial on how to add agentic workflows to Quarkus applications with the Agentican framework via YAML and annotations.
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:
- Identify the top vendors within a market category.
- Research the positioning and strengths of each vendor.
- Classify the findings as either standard or urgent.
- 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.3</version>
</dependency>
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.
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}}
A few things worth flagging:
agent: researcherreferences the agent for a step, skills referenced by name, too.outputStepdesignates the step whose output becomes the workflow's typed 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: loopsteps take anoverreference (a step that produced a list, or a list-typed param).type: loopsteps run their nestedstepsonce per item, in parallel, and on virtual threads.type: branchsteps take afromreference (a step whose output is used to select a branch).branches:mutually exclusive steps (or sets of steps) withdefaultfor unrecognized values.
The framework loads agentican-catalog.yaml from the classpath, or you can define where it's loaded from:
agentican.catalog-config=/etc/agentican/agentican-catalog.yaml
Note: Agents, skills, and workflows can be defined via a fluent builder API as well.
Step 3: Configure the Models
Agentican reads the engine configuration from `application.properties`. The minimum is one LLM:
agentican.llm[0].api-key=${ANTHROPIC_API_KEY}
The provider defaults to `anthropic`, and the model defaults to `claude-sonnet-4-5`.
Want OpenAI instead?
agentican.llm[0].provider=openai
agentican.llm[0].api-key=${OPENAI_API_KEY}
agentican.llm[0].model=gpt-4o-mini
Want to mix and match? Configure `name`s and reference them per-agent in the YAML catalog:
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
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) {}
}
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();
}
}
Now, test the endpoint:
curl -X POST http://localhost:8080/market-brief/data%20observability%20platforms
A few things worth flagging — they're what set this apart from a generic "call an LLM" library:
ResearchParams.vendorCountbecomes the workflow parametervendor_countvia SNAKE_CASE mapping.start()returns aWorkflowRun<VendorBrief>andawait()parses the output step's text into aVendorBrief.@AgenticanWorkflow(name = "vendor-brief")resolves the registered workflow at injection time.
Note: WorkflowRun itself exposes future() for a CompletableFuture<R>, and there's a ReactiveWorkflow<P, R> Mutiny variant for Vert.x stacks.
Step 5: Add Agent Tools
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}
Composio
100+ SaaS toolkits — Slack, Notion, Linear, Salesforce, GitHub, Google Workspace:
agentican.composio.api-key=${COMPOSIO_API_KEY}
agentican.composio.user-id=user-123
Tools are referenced by name within agent steps:
steps:
- name: research
agent: researcher
tools: [github_search_repositories]
instructions: "Profile open-source vendors in {{param.topic}}."
Structured agentic workflows for the JVM.
Where to Go Next
- Getting Started — install, configure, and run workflows
- Core Concepts — architecture, terminology, and data flow
- Workflows & Steps — CDI surface, beans, qualifiers, override patterns.
- Agents — defining agents, skills, and roles
- Getting Started (Quarkus) — dependency setup, config, first task
- CDI Integration — injection, qualifiers, lifecycle events, bean overrides
- REST API — endpoints, SSE streaming, WebSocket, error codes
- Observability — Micrometer metrics, OTel tracing, Prometheus queries
Opinions expressed by DZone contributors are their own.