Workflow definitions from YAML files

This guide shows you how to load CNCF Workflow definitions (YAML DSL) into your Quarkus application and expose them as WorkflowDefinition/Flow CDI beans you can inject and execute.

Quarkus Flow recommends the Java DSL as the primary way to define workflows in Quarkus applications, because it gives you type safety, refactor-friendly code and excellent IDE support. Loading workflows from YAML is available as an alternative for teams that already maintain CNCF Workflow specification files or need to share definitions across runtimes. For the Java DSL reference, see Java DSL cheatsheet.

If you are new to the CNCF Workflow concepts, see CNCF Workflow mapping and concepts first.

Prerequisites

  • A Quarkus application with Quarkus Flow set up.

  • A directory that will hold your workflow definitions:

    • by default: src/main/flow

    • or a custom directory configured via quarkus.flow.definitions.dir

  • Basic familiarity with YAML and the CNCF Workflow DSL.

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.

Create the directory and add a simple 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 workflow data based on the input .name

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

The value can be:

  • an absolute path, or

  • a relative path resolved from the Maven module root (the directory that contains your pom.xml).

By default it is src/main/flow. All .yaml and .yml files in the resolved directory are discovered at build time.

2. Inject the Flow bean

For each discovered specification file, Quarkus Flow generates a WorkflowDefinition and Flow CDI beans.

Quarkus Flow uses the document.namespace and document.name fields to create unique identifiers for each workflow.

The extension creates two types of identifiers (@Identifier) values:

  • Regular: {namespace}:{name}.

  • Fully Qualified Class Name: {namespace}.{name} being converted to a Java package and class name format.

All identifiers can be used for WorkflowDefinition and Flow injection points.

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

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 from your YAML file.

The @Identifier value must exactly match Regular or Fully Qualified Class Name generated from in your YAML specification.

If you change either document.namespace or document.name, you must update every injection point that references this workflow.

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

A typical execution flow in this example looks like:

  1. flow.startInstance(Map.of("name", finalName)) creates a new io.smallrye.mutiny.Uni<WorkflowModel> instance with the initial data (name).

  2. .onItem().transform(…​) observes and transform the item emitted by Uni and creates a new Uni<String> with the JSON representation of the workflow data.

  3. The JAX-RS method returns a Uni<String>, so the endpoint remains reactive/non-blocking.

2.1 Blocking vs non-blocking REST endpoints

The EchoResource above uses the recommended non-blocking style for Quarkus REST endpoints:

  • the method returns Uni<String>

  • the workflow is started with startInstance(…​) and the Uni is propagated back to the caller

This style integrates well with Quarkus’ reactive model and, when a workflow is invoked from a JAX-RS resource, lets any WorkflowException propagate directly to the HTTP layer. Quarkus Flow registers a standard JAX-RS ExceptionMapper<WorkflowException>, so any resource that throws WorkflowException (including those that call workflows) will automatically be translated into an RFC 7807 / WorkflowError HTTP response.

If you prefer a blocking style in very simple scenarios (for example in a CLI or quick prototype), you can:

  • call .instance().start().join() using a CompletionStage or .await().indefinetely() when using Mutiny to wait for the result, and return a Response or a plain DTO from your JAX-RS method.

In that case, be aware that blocking on a CompletionStage/Uni may wrap underlying exceptions (for example in ExecutionException, CompletionException), and you might need to unwrap and rethrow WorkflowException if you want the standard HTTP error mapping. For a deeper discussion and a full HTTP/OpenAPI example, see Non-blocking vs blocking style.

3. Run the application

Start your Quarkus application in development mode:

./mvnw quarkus:dev

Quarkus Flow will:

  • discover the YAML file in src/main/flow.

  • compile it into a Flow bean.

  • wire it into your EchoResource via CDI.

Any change to the YAML file is picked up by Quarkus dev mode with a live reload, just like regular Java code.

4. Test the workflow

Call the REST endpoint:

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

5. Expected outcome

After completing these steps, you should have:

  • a workflow specification file loaded automatically by Quarkus Flow

  • a Flow CDI bean available for injection using @Identifier("company.EchoName")

  • a working REST endpoint that executes the workflow and returns the result

The expected JSON response from the curl command is:

{"message":"echo: John"}