Quarkus Flow is a lightweight, low-dependency, production-grade workflow engine for Quarkus, built on the CNCF Serverless Workflow specification.
Use it to model classic workflows and Agentic AI orchestrations, with first-class CDI/Quarkus ergonomics.
- π§© CNCF-compliant workflows via a fluent Java DSL
- β‘ Fast start & low footprint (Quarkus/native-friendly)
- π CDI-first: build-time discovery β CDI injection, no registries to wire
- π§ͺ Great DX: inject your workflow class or the compiled
WorkflowDefinition
- π€ Agentic AI ready: seamless LangChain4j integration
Nothing beats a tiny example.
<dependency>
<groupId>io.quarkiverse.flow</groupId>
<artifactId>quarkus-flow</artifactId>
<version>999-SNAPSHOT</version>
</dependency>
If youβre returning POJOs from JAX-RS, also add
quarkus-rest-jackson
for JSON.
package org.acme;
import jakarta.enterprise.context.ApplicationScoped;
import io.quarkiverse.flow.Flow;
import io.serverlessworkflow.api.types.Workflow;
import io.serverlessworkflow.fluent.spec.WorkflowBuilder;
@ApplicationScoped
public class HelloWorkflow extends Flow {
@Override
public Workflow descriptor() {
return WorkflowBuilder.workflow("hello")
.tasks(t -> t.set("{ message: \"hello world!\" }"))
.build();
}
}
- You extend
Flow
and implement a single method:descriptor()
, using the fluent DSL. - CDI is available in your workflow class β feel free to
@Inject
collaborators and use them while building the descriptor.
package org.acme;
import java.util.Map;
import java.util.concurrent.CompletionStage;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import org.jboss.resteasy.reactive.ResponseStatus;
@Path("/hello")
@ApplicationScoped
public class HelloResource {
@Inject
HelloWorkflow hello; // inject the Flow subclass
@GET
@ResponseStatus(200)
public CompletionStage<Message> hello() {
return hello
.startInstance(Map.of()) // convenience on Flow
.thenApply(w -> w.as(Message.class).orElseThrow());
}
}
Every discovered workflow produces a CDI bean:
import io.smallrye.common.annotation.Identifier;
import io.serverlessworkflow.impl.WorkflowDefinition;
@Inject
@Identifier("org.acme.HelloWorkflow") // FQCN of the workflow class
WorkflowDefinition helloDef;
Tip: pick option A unless you specifically need the raw
WorkflowDefinition
.
- At build time, Quarkus Flow finds every non-abstract subclass of
io.quarkiverse.flow.Flow
. - For each, it compiles a
WorkflowDefinition
and publishes it as a CDI bean qualified with@Identifier("<workflow FQCN>")
. Example: classorg.acme.HelloWorkflow
β qualifier@Identifier("org.acme.HelloWorkflow")
.
Quarkus Flow integrates with Quarkus LangChain4j so you can orchestrate AI agents as first-class tasks.
<dependency>
<groupId>io.quarkiverse.langchain4j</groupId>
<artifactId>quarkus-langchain4j-ollama</artifactId>
<version>YOUR_VERSION</version>
</dependency>
(Use OpenAI/Vertex/etc. if you prefer.)
package org.acme.agentic;
import jakarta.enterprise.context.ApplicationScoped;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
import io.quarkiverse.langchain4j.RegisterAiService;
@RegisterAiService
@ApplicationScoped
@SystemMessage("""
You are a minimal echo agent.
- If the input is empty/blank/null β reply 'Your message is empty'
- Otherwise reply with the same text
""")
public interface EchoAgent {
@UserMessage("{{message}}")
String echo(@V("message") String message);
}
package org.acme.agentic;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import io.quarkiverse.flow.Flow;
import io.serverlessworkflow.api.types.Workflow;
import io.serverlessworkflow.fluent.agentic.AgentWorkflowBuilder;
@ApplicationScoped
public class HelloAgenticWorkflow extends Flow {
@Inject EchoAgent agent;
@Override
public Workflow descriptor() {
return AgentWorkflowBuilder.workflow("hello-agentic")
.tasks(t -> t.callFn(f -> f.function(agent::echo))) // call the agent
.build();
}
}
package org.acme.agentic;
import java.util.concurrent.CompletionStage;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
@Path("/echo")
@ApplicationScoped
public class HelloAgenticResource {
@Inject HelloAgenticWorkflow wf;
@POST
public CompletionStage<String> echo(String message) {
return wf.startInstance(message)
.thenApply(m -> m.asText().orElseThrow());
}
}
Can my workflow class use CDI?
Absolutely. descriptor()
runs on a CDI bean; @Inject
whatever you need.
How do I inject the compiled definition?
Use the workflow class FQCN as the qualifier:
@Inject @Identifier("org.acme.HelloWorkflow") WorkflowDefinition def;
Dev mode & native? Fully supported. Definitions are built at startup, designed for Quarkus native friendliness.
- Richer DSL coverage (events, retries, compensation, etc.)
- Dev-UI visualizations & tracers
- Prebuilt AI orchestration patterns
- Native build optimizations
- Deeper LangChain4j glue
Apache 2.0