Define workflows from YAML files

Quarkus Flow recommends the Java DSL as the primary way to define workflows because it provides type safety, refactoring support, and excellent IDE integration.

However, Quarkus Flow fully supports loading CNCF Serverless Workflow 1.0.0 definitions written in YAML or JSON. This is ideal for teams that already maintain specification files or need to share definitions across different runtime environments.

This guide shows you how to load a YAML workflow into your Quarkus application and expose it as a standard Flow CDI bean that you can inject and execute.

Prerequisites

1. Create the workflow specification file

By default, Quarkus Flow scans the src/main/flow directory (relative to your Maven module root) for workflow specification files at build time.

Create the directory and add your CNCF 1.0.0 workflow:

src/main/flow/echo-name.yaml
document:
  dsl: '1.0.0'
  namespace: company
  name: echo-name
  version: '0.1.0'

do:
  - setEcho:
      set:
        message: '${ "echo: " + .name }'

This definition:

  • Declares the workflow metadata in the document section (DSL version, namespace, name, version).

  • Defines a single set task that writes a message field into the global workflow data based on the input .name.

You can customize the discovery directory by setting the quarkus.flow.definitions.dir property in your application.properties.

It accepts an absolute path or a path relative to the Maven module root. All .yaml and .yml files in the resolved directory are discovered automatically.

2. Inject the Flow bean

During the Quarkus build, the engine parses your YAML files and generates WorkflowDefinition and Flow CDI beans for them.

Quarkus Flow uses the document.namespace and document.name fields from your YAML file to create a unique identifier for the bean. It generates two formats you can use:

  • Fully Qualified Class Name (Recommended): namespace.name (e.g., if your YAML has namespace: org.acme and name: EchoWorkflow, the identifier is org.acme.EchoWorkflow).

  • Regular: namespace:name (e.g., org.acme:EchoWorkflow).

Create a JAX-RS resource that injects and runs the workflow using the generated identifier:

package org.acme;

import java.util.Map;
import java.util.Objects;

import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;

import io.quarkiverse.flow.Flow;
import io.smallrye.common.annotation.Identifier;
import io.smallrye.mutiny.Uni;

@Path("/echo")
public class EchoResource {

    @Inject
    @Identifier("company.EchoName") (1)
    Flow flow;

    @GET
    public Uni<String> echo(@QueryParam("name") String name) {
        final String finalName = Objects.requireNonNullElse(name, "(Duke)");
        return flow.startInstance(Map.of("name", finalName))
                .onItem()
                .transform(wf -> wf.asText().orElseThrow());
    }
}
1 Use the @Identifier annotation with the Fully Qualified Class Name derived from your YAML file.

The @Identifier value must exactly match the metadata generated from your YAML specification. If you change either document.namespace or document.name in the YAML, you must update the @Identifier in every Java class that injects this workflow.

You can define a global namespace prefix for the identifiers using the quarkus.flow.definitions.namespace.prefix property in your application.properties.

2.1 Reactive Execution and Error Handling

The EchoResource example uses the recommended non-blocking style for Quarkus REST endpoints (returning a Uni<String>).

A typical execution flow looks like this:

  1. flow.startInstance(…​) executes the workflow non-blocking and returns a Uni<WorkflowInstance>.

  2. .map(WorkflowInstance::output) extracts just the final JSON payload.

Because it is non-blocking, if the YAML workflow encounters an error (e.g., an HTTP task fails), the WorkflowException propagates cleanly to the HTTP layer. Quarkus Flow automatically translates it into an RFC 7807 Problem Details HTTP response.

(For a deeper discussion on error mapping and the pitfalls of blocking .join() calls, see HTTP Errors and Exception Mapping).

3. Run and live-reload

Start your Quarkus application in development mode:

./mvnw quarkus:dev

Call your new REST endpoint:

curl "http://localhost:8080/echo?name=John"

# Expected JSON response:
# {"message":"echo: John"}

The best part: Because Quarkus Flow integrates seamlessly with Quarkus dev mode, any changes you make to the YAML file in src/main/flow will be picked up and live-reloaded immediately. You do not need to restart your application when tweaking YAML tasks!

See also