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.

In test mode, if the same workflow file exists in both src/main/flow and src/test/flow with the same relative path, the src/test/flow file takes precedence.

Example:

  • src/main/flow/echo.yaml

  • src/test/flow/echo.yaml

In this case, Quarkus Flow uses src/test/flow/echo.yaml during tests.

Only exact relative-path matches are overridden. For example, src/test/flow/echo.yaml does not override src/main/flow/workflows/echo.yaml.

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.

2.1. Workflow Versioning

When you have multiple versions of the same workflow (same namespace:name but different version), Quarkus Flow automatically creates a versionless bean that points to the workflow with the highest semantic version.

For example, if you have two versions of a workflow, you can inject the latest version using the versionless identifier namespace:name (without the version):

@Inject
@Identifier("company:echo-name")  (1)
Flow flow;
1 Automatically points to the highest semantic version available

If you later add a new version with a higher semantic version number, the versionless bean will automatically point to it without requiring any code changes.

2.2. Naming Strategy

You can choose between two identifier formats using the quarkus.flow.definitions.naming-strategy property:

Strategy Format Example

spec (default)

namespace:name:version (versioned)
namespace:name (versionless)

Given namespace: company, name: echo-name, version: 1.0.0:
Versioned: company:echo-name:1.0.0
Versionless: company:echo-name

class

namespace.ClassName

Given namespace: company and name: echo-name, the identifier is company.EchoName (name is converted to PascalCase)

The spec strategy (recommended) includes the version in the identifier and creates a versionless bean for convenience. The class strategy converts the name to PascalCase for a Java class-like identifier.

To use the class strategy, add this to your application.properties:

quarkus.flow.definitions.naming-strategy=class

2.3. Inject and Execute the Workflow

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:echo-name") (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 identifier derived from your YAML file. With the default spec naming strategy, this follows the namespace:name format.

The @Identifier value must exactly match the metadata generated from your YAML specification. If you change document.namespace, document.name, or document.version 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.4. 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