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.
Join the DZone community and get the full member experience.
Join For FreeThis 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.
Comments