Java DSL cheatsheet
Quarkus Flow uses the CNCF Serverless Workflow Java Fluent DSL to define workflows in code. This page shows:
-
how to define a workflow class,
-
the complete set of chainable task providers from the DSL (the ones you pass directly into
workflow(…).tasks(…)), and -
how to shape data with transformations:
exportAs,inputFrom, andoutputAs.
|
Source of truth for the DSL classes:
|
Define a workflow class (required)
Workflows are discovered at build time from CDI beans that extend io.quarkiverse.flow.Flow and override descriptor().
package org.acme;
import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.set;
import jakarta.enterprise.context.ApplicationScoped;
import io.quarkiverse.flow.Flow;
import io.serverlessworkflow.api.types.Workflow;
import io.serverlessworkflow.fluent.func.FuncWorkflowBuilder;
@ApplicationScoped
public class HelloWorkflow extends Flow {
@Override
public Workflow descriptor() {
return FuncWorkflowBuilder.workflow("hello")
// jq expression to set our context to the JSON object `message`
.tasks(set("{ message: \"hello world!\" }"))
.build();
}
}
Setup (imports)
import io.serverlessworkflow.api.types.Workflow;
import io.serverlessworkflow.fluent.func.spec.FuncWorkflowBuilder;
// Static imports recommended for brevity:
import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.*;
import static io.serverlessworkflow.fluent.func.spec.FuncWorkflowBuilder.workflow;
Complete task providers (chainable)
All helpers below return a chainable provider/step (e.g., FuncTaskConfigurer, FuncCallStep, EmitStep, ListenStep, ConsumeStep) so you can pass them straight into .tasks(…):
| Method | Returns | What it does | Example |
|---|---|---|---|
|
|
Set/merge into data context using jq-style JSON expression. |
|
|
|
Set/merge from a map (useful in Java without inline JSON). |
|
|
|
Call a Java |
|
|
|
Call a Java |
|
|
|
Named variant, input inferred. |
|
|
|
Named + explicit input type. |
|
|
|
Call with workflow context |
|
|
|
Named |
|
|
|
Call with instance id |
|
|
|
Named |
|
|
|
Sugar for agent-style calls that need the instance id. |
|
|
|
Named agent call. |
|
|
|
Fire-and-forget side-effect. |
|
|
|
Named side-effect. |
|
|
|
Low-level emit using builder config. |
|
|
|
Named low-level emit. |
|
|
|
Emit event; body encoded from |
|
|
|
Named variant. |
|
|
|
Custom bytes serializer (Jackson provided). |
|
|
|
Named custom bytes emit. |
|
|
|
Emit JSON CloudEvent for POJO input. |
|
|
|
Named JSON emit. |
|
|
|
Listen using a spec (e.g., |
|
|
|
Named listen. |
|
|
|
Low-level switch (builder consumer). |
|
|
|
Named low-level switch. |
|
|
|
Switch from cases helpers. |
|
|
|
Named cases switch. |
|
|
|
Typed single-case jump. |
|
|
|
JQ expression single-case jump. |
|
|
|
Typed single-case + default task. |
|
|
|
Typed single-case + default directive ( |
|
|
|
JQ single-case + default task. |
|
|
|
JQ single-case + directive. |
|
|
|
Iterate over a computed collection. |
|
|
|
Iterate over a constant collection. |
|
|
|
Iterate over a constant list. |
|
|
|
Group multiple steps/configurers (useful inside |
|
Helpers that help you build specs for emit/listen/switch but are not task providers themselves:
event(…), eventJson(…), eventBytes(…), to().one(…), to().any(…), to().all(…), and cases helpers cases(…), caseOf(…), caseDefault(…).
|
Transformations: exportAs, inputFrom, outputAs
Every task/step can transform how it reads input, exposes its result to the next step, and writes to the workflow data. Internally, Quarkus Flow keeps your workflow context as JSON (via Jackson) and seamlessly (de)serializes your domain objects.
You can use either jq expressions (string-based) or Java lambdas to transform data.
inputFrom(…) — where the task reads its input
Apply to a task step (call/agent/consume/emit/listen):
-
jq:
function(pricing::quote, QuoteRequest.class) .inputFrom("$.cart.quoteRequest") // slice of the workflow data -
Java:
function(pricing::quote, QuoteRequest.class) .inputFrom(ctx -> ctx.get("cart", QuoteEnvelope.class).quoteRequest())
exportAs(…) — what the task exposes to the next step
This defines the ephemeral output visible to the next step in the chain (before any outputAs writes to the global data). Useful to pipe values across steps without polluting global state.
-
jq:
function(ai::answer, Prompt.class) .exportAs("$.choices[0].text") // only the LLM's first text is exposed -
Java:
function(ai::answer, Prompt.class) .exportAs(result -> result.choices().get(0).text())
outputAs(…) — how the task writes into the workflow data
Persist (part of) the step result into the workflow data context.
-
jq:
function(nlp::classify, Text.class) .outputAs("{ sentiment: ., reviewed: false }") // writes a JSON object with the classification result under the current scope -
Java (typed):
function(pricing::quote, QuoteRequest.class) .outputAs(q -> Map.of("quote", q, "ts", System.currentTimeMillis())) -
Listen multi-event adapter:
listen("waitHuman", to().one(event("org.acme.review.done"))) .outputAs((java.util.Collection<Object> c) -> c.iterator().next());
You can combine the three:
1) inputFrom(…) selects what the step consumes,
2) the step runs,
3) exportAs(…) narrows what the next step receives, and
4) outputAs(…) commits data into the workflow context.
|
End-to-end example (agentic + HITL + transforms)
Workflow w = workflow("intelligent-newsletter")
.tasks(
// 1) Draft with input selection and export the 'draft' only
agent("draftAgent", drafterAgent::draft, String.class)
.inputFrom("$.seedPrompt")
.exportAs("$.draft"),
// 2) Critique receives only the 'draft' (exported) and writes a composite result
agent("criticAgent", criticAgent::critique, String.class)
.outputAs(r -> Map.of("review", r, "status", r.needsRevision() ? "REVISION" : "OK")),
// 3) Emit review request
emitJson("org.acme.email.review.required", CriticAgentReview.class),
// 4) Wait human review; adapt multi-event collection
listen("waitHumanReview", to().one(event("org.acme.newsletter.review.done")))
.outputAs((java.util.Collection<Object> c) -> c.iterator().next()),
// 5) Branch: revision loop or final send
switchWhenOrElse(
(HumanReview h) -> ReviewStatus.NEEDS_REVISION.equals(h.status()),
"draftAgent",
"sendNewsletter",
HumanReview.class
),
// 6) Final side-effect
consume("sendNewsletter",
(HumanReview reviewedDraft) ->
mailService.send("subscribers@acme.finance.org", "Weekly Newsletter", reviewedDraft.draft()),
HumanReview.class
)
)
.build();
Tips
-
Prefer static imports:
workflow,set,function,agent,emitJson,listen,switchWhenOrElse,consume,to,event. -
Name tasks you branch to: stable task names make
switchWhen*targets explicit. -
Keep transformations close to the step that needs them (
inputFrom,exportAs,outputAs) for readability. -
Remember: the workflow data is JSON; your domain objects are (de)serialized via Jackson, so you can keep typed payloads in your steps while still using jq for quick projections.