Workflow execution via REST API with Quarkus Flow Runner
|
Preview — This feature is functional and tested, but backward compatibility is not guaranteed across releases. |
Deploy and execute Open Workflow definitions via REST API.
The Quarkus Flow Runner extension transforms your Quarkus application into a lightweight, cloud-native workflow execution engine. Load workflow definitions from YAML/JSON files at startup, and execute them through a secured REST API.
This enables runtime workflow deployment where definitions are managed externally (by CI/CD pipelines, GitOps operators, or development teams) and deployed as configuration rather than requiring application rebuilds.
Use cases
When to use the Runner:
-
GitOps workflow deployment - Store workflow definitions in Git, deploy via Kubernetes ConfigMaps, execute via API
-
Runtime workflow management - Update and deploy workflows without application rebuilds or recompilation
-
Multi-tenant workflow hosting - Deploy a single Runner instance serving workflows for multiple teams/namespaces
-
Event-driven automation - Execute workflows triggered by webhooks, Kafka events, or scheduled jobs
-
Externally-managed workflows - Workflows authored by tools, UI builders, or external systems that generate YAML definitions
-
Microservice-based architecture - Expose workflow execution as a REST API for integration with other services
When NOT to use the Runner:
-
Type-safe Java DSL preferred - Use standard
Quarkus Flow workflowsfor compile-time safety and IDE support -
Build-time optimization critical - Java workflows get compile-time validation, dead code elimination, and native image optimization
-
Tightly coupled workflows - Workflows that depend heavily on application CDI beans and business logic should remain as Java classes
Prerequisites
-
Java 17 or newer
-
Maven 3.8+ or Quarkus CLI
-
An existing Quarkus project, or create one:
quarkus create app org.acme:workflow-runner cd workflow-runner
1. Add the Runner dependency
Add the Flow Runner extension to enable REST-based workflow execution.
<dependency>
<groupId>io.quarkiverse.flow</groupId>
<artifactId>quarkus-flow-runner</artifactId>
<version>1.3.0</version>
</dependency>
The Runner extension automatically includes the core quarkus-flow dependency. You don’t need to add it separately.
|
2. Configure workflow loading
The Runner loads workflow definitions from a configured filesystem path at startup. This directory is typically a Kubernetes ConfigMap or volume mount in production.
# Enable the runner (default: true)
quarkus.flow.runner.enabled=true
# Path to workflow definitions (REQUIRED when enabled)
# Scans recursively for .yaml, .yml, .json files
quarkus.flow.runner.source.path=/deployments/workflows
On Kubernetes, ConfigMap and Secret volume mounts use an internal symlink structure.
By default, the Runner skips symbolic links during directory scanning to prevent duplicate workflow detection failures.
If your workflow files are intentionally provided via symbolic links outside of a Kubernetes volume mount, set quarkus.flow.runner.source.follow-symlinks=true.
|
File watcher for live workflow loading
By default, the Runner loads workflow definitions once at startup. You can enable a file watcher that periodically polls the source path for new workflow files and registers them automatically — without requiring a pod restart.
# Enable the file watcher (build-time property, default: false)
quarkus.flow.runner.source.watch.enabled=true
# Polling interval (runtime property, default: 5s)
quarkus.flow.runner.source.watch.interval=5s
When enabled, the watcher:
-
Polls the configured
source.pathdirectory at the specified interval -
Detects new
.yaml,.yml, and.jsonfiles (including in subdirectories) -
Parses and registers new workflow definitions via the same mechanism as startup loading
-
Caches already-processed files to avoid re-parsing on every poll cycle
-
Skips malformed files with a warning and retries them on the next cycle (handles partially-written files during ConfigMap rotation)
-
Respects the
follow-symlinkssetting
The watch.enabled property is a build-time property — changing it requires a rebuild. The watch.interval property is a runtime property and can be changed without rebuilding.
|
| The file watcher currently supports detecting new workflow files only. Updating or removing existing workflow definitions at runtime requires an application restart. See issue #842 for future support. |
The pre-built Runner container images (quay.io/quarkiverse/quarkus-flow-runner) have the file watcher enabled by default, since they target Kubernetes deployments where ConfigMap updates are common.
|
Local development setup:
Create a workflows/ directory in your project:
mkdir -p src/main/resources/workflows
Point the configuration to it during development:
quarkus.flow.runner.source.path=src/main/resources/workflows
3. Create workflow definitions
Workflow definitions follow the Open Workflow Specification (CNCF sandbox project). The Runner supports YAML and JSON formats.
Create your first workflow definition as a YAML file.
document:
dsl: "1.0.0"
namespace: examples
name: hello-runner
version: "1.0.0"
title: "Simple greeting workflow for the Runner"
input:
schema:
format: json
document:
type: object
properties:
name:
type: string
do:
- greet:
set:
greeting: "${ \"Hello, \" + .name + \"!\" }"
Key fields for the Runner:
-
document.dsl- DSL version (e.g., "1.0.3") (REQUIRED) -
document.namespace- Organizes workflows and enforces access control (REQUIRED) -
document.name- Workflow identifier within the namespace (REQUIRED) -
document.version- Semantic version string for versioning support (REQUIRED) -
document.title- Description shown in OpenAPI documentation (optional) -
input.schema- JSON Schema for input validation (optional but recommended) -
do- Array of tasks to execute (REQUIRED)
4. Choose a security mode
The Runner supports three authentication modes. For this tutorial, start with NONE for local testing, then configure proper authentication for production.
NONE (Development only)
| This mode disables all authentication. Use only for local development. Production deployments MUST use OIDC or API_KEY authentication. |
quarkus.flow.runner.security.type=NONE
When you start the application, you’ll see a warning:
WARN [io.qua.flo.run.sec] Flow Runner security is disabled (type=NONE).
This is NOT safe for production deployments.
API_KEY (Machine-to-machine)
API Key authentication is ideal for webhook integrations, CI/CD pipelines, and service-to-service communication.
# Enable API Key authentication
quarkus.flow.runner.security.type=API_KEY
# Define API keys with roles and optional namespace restrictions
quarkus.flow.runner.security.api-keys.admin.secret=${FLOW_ADMIN_KEY}
quarkus.flow.runner.security.api-keys.admin.roles=flow-admin
quarkus.flow.runner.security.api-keys.invoker.secret=${FLOW_INVOKER_KEY}
quarkus.flow.runner.security.api-keys.invoker.roles=flow-invoker
quarkus.flow.runner.security.api-keys.invoker.namespaces=examples,team-a
Environment variables:
export FLOW_ADMIN_KEY="admin-secret-change-me"
export FLOW_INVOKER_KEY="invoker-secret-change-me"
Predefined roles:
-
flow-admin- Access to all endpoints in every namespace -
flow-invoker- Access to all endpoints, restricted to the namespaces the key or token is authorized for
API Key usage:
curl -X POST http://localhost:8080/q/flow/exec/examples/hello-runner \
-H "Authorization: Bearer ${FLOW_INVOKER_KEY}" \
-H "Content-Type: application/json" \
-d '{"name": "World"}'
OIDC (Enterprise SSO)
For enterprise deployments with existing identity providers (Keycloak, Auth0, Okta, Azure AD), use OIDC authentication.
Add the OIDC extension:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc</artifactId>
</dependency>
Configure OIDC and Runner security:
# Enable OIDC authentication for the Runner
quarkus.flow.runner.security.type=OIDC
# Standard Quarkus OIDC configuration
quarkus.oidc.auth-server-url=https://keycloak.example.com/realms/workflows
quarkus.oidc.client-id=workflow-runner
quarkus.oidc.credentials.secret=${OIDC_CLIENT_SECRET}
# JWT claim containing authorized namespaces
quarkus.flow.runner.security.namespace.claim=namespace
JWT token requirements:
-
Must contain a
rolesclaim withflow-adminorflow-invoker -
A user with the
flow-adminrole can access all namespaces. The namespace claim is optional and is not used to restrict an administrator. -
A non-admin user must have an authorized namespace claim when namespace validation is enabled.
-
For non-admin users, a missing, null, empty, or blank namespace claim grants no namespace access and requests to a namespace are rejected with
403 Forbidden. -
Example token payload:
{
"sub": "alice@example.com",
"roles": ["flow-invoker"],
"namespace": ["examples", "team-a"]
}
Non-admin with unrestricted namespace access:
{
"sub": "automation@example.com",
"roles": ["flow-invoker"],
"namespace": ["*"]
}
Administrator without a namespace claim:
{
"sub": "administrator@example.com",
"roles": ["flow-admin"]
}
The administrator can access every namespace even though the token does not contain a namespace claim.
API usage:
curl -X POST http://localhost:8080/q/flow/exec/examples/hello-runner \
-H "Authorization: Bearer ${JWT_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"name": "World"}'
Custom authentication
For advanced scenarios (mTLS, custom tokens, existing auth filters), you can combine security.type=NONE with your own JAX-RS filters.
quarkus.flow.runner.security.type=NONE
quarkus.flow.runner.security.namespace.validate=false
Then implement a custom ContainerRequestFilter that injects SecurityIdentity. The Runner endpoints will respect @RolesAllowed annotations if a valid identity is present.
5. Start the application
Run Quarkus in development mode:
./mvnw quarkus:dev
Watch the startup logs to verify workflows loaded successfully:
INFO [io.qua.flo.run.loa] Loading workflow definitions from: src/main/resources/workflows
INFO [io.qua.flo.run.loa] Registered workflow: examples:hello-runner:1.0.0
INFO [io.qua.flo.run.loa] Loaded 1 workflow definition(s)
6. Execute workflows via REST API
Synchronous execution
Execute the workflow and wait for the result (blocks until completion) using the /sync endpoint:
curl -X POST http://localhost:8080/q/flow/exec/examples/hello-runner/sync \
-H "Content-Type: application/json" \
-d '{"name": "World"}'
Response (200 OK):
The response body contains the workflow output directly, allowing REST clients to map it to business POJOs:
{
"greeting": "Hello, World!"
}
Note that using sync for workflows with listen or wait tasks might lead to REST client timeouts.
Therefore, it is expected that you use the async version of the API for long running workflows.
Asynchronous execution
Start the workflow and return immediately (non-blocking):
curl -X POST http://localhost:8080/q/flow/exec/examples/hello-runner \
-H "Content-Type: application/json" \
-d '{"name": "World"}'
Response (202 Accepted):
{
"workflowApplicationId": "quarkus-flow-runner",
"instanceId": "550e8400-e29b-41d4-a716-446655440000",
"status": "WAITING",
"startedAt": "2026-06-11T10:15:30.123Z",
"completedAt": null
}
API Contract:
The Runner follows the HTTP API contract strictly for asynchronous execution:
-
Always returns 202 Accepted - Regardless of how quickly the workflow completes
-
No workflow output returned - Output is only available via synchronous
/syncendpoints -
Status reflects current state - Can be
WAITING,RUNNING, orCOMPLETEDdepending on when the response was captured
This ensures clients can rely on a consistent API contract: asynchronous execution means "acknowledge receipt and start execution" with no output, while synchronous execution (using /sync endpoints) means "wait for completion and return output".
For truly long-running workflows, use asynchronous execution and poll for status or implement callbacks (future feature).
The instanceId returned by an asynchronous execution lets you track the instance with the instances endpoints and control it with the suspend, resume, and cancel endpoints. See the API reference for every supported endpoint.
Execution response fields
Synchronous and asynchronous executions return different response structures:
Asynchronous execution (202 Accepted):
Returns execution metadata without workflow output:
| Field | Type | Description |
|---|---|---|
|
string |
Identifier of the workflow application (the Runner instance) that executed the workflow. It is derived from |
|
string |
Unique identifier of the workflow instance. |
|
string |
Current status of the instance ( |
|
string (ISO-8601) |
Timestamp when the instance started. |
|
string (ISO-8601) or |
Timestamp when the instance completed, or |
Set quarkus.application.name in application.properties to control the workflowApplicationId reported by asynchronous executions.
|
Synchronous execution (200 OK):
Returns the workflow output directly in the response body. The structure depends on your workflow definition and can be any valid JSON type (object, array, string, number, boolean, or null). This allows REST clients to directly map the response to business POJOs without unwrapping.
Execute specific workflow version
If you have multiple versions of the same workflow, specify the version in the path:
curl -X POST http://localhost:8080/q/flow/exec/examples/hello-runner/1.0.0/sync \
-H "Content-Type: application/json" \
-d '{"name": "World"}'
When you omit the version (/exec/{namespace}/{name}/sync or /exec/{namespace}/{name}), the Runner automatically selects the latest version based on semantic versioning.
7. Explore the OpenAPI documentation
The Runner automatically generates OpenAPI documentation for all loaded workflows. This provides a live API catalog and interactive testing interface.
Access the Swagger UI:
-
Ensure the OpenAPI extension is added to your project:
pom.xml<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-smallrye-openapi</artifactId> </dependency> -
Start Quarkus in dev mode and open: http://localhost:8080/q/swagger-ui
-
Find the Flow Runner Execution section to see all registered workflows as individual operations
OpenAPI spec download:
# JSON format
curl http://localhost:8080/q/openapi -o openapi.json
# YAML format
curl -H "Accept: application/yaml" http://localhost:8080/q/openapi -o openapi.yaml
Dynamic workflow operations:
By default (quarkus.flow.runner.openapi.expand-workflows=true), the Runner generates a dedicated operation for each registered workflow. This provides better documentation but exposes the workflow catalog publicly.
To disable per-workflow operations and only document the generic parameterized endpoint:
quarkus.flow.runner.openapi.expand-workflows=false
| The OpenAPI document is publicly accessible even when authentication is enabled. It shows workflow metadata (namespace, name, version, summary) but does not allow execution without proper credentials. |
Security in depth
Role-based access control (RBAC)
Every Runner endpoint under /q/flow requires one of two predefined roles:
| Role | Endpoints | Namespace restrictions |
|---|---|---|
|
All Runner endpoints |
None. Administrators can access every namespace, with or without a namespace claim. |
|
All Runner endpoints (execution, instance lifecycle, definitions, instances) |
Limited to the authorized namespaces when namespace validation is enabled (see below). |
Requests without a valid identity are rejected with 401 Unauthorized. Configure roles per API key or extract them from OIDC JWT tokens.
Namespace-based authorization (ABAC)
Beyond role-based access, the Runner enforces attribute-based access control at the namespace level. Users can only execute workflows in namespaces they are authorized for.
How it works:
-
The workflow path contains a namespace parameter:
/q/flow/exec/{namespace}/… -
The Runner extracts the user’s authorized namespaces from their authentication context
-
If the requested namespace is not in the user’s authorized list, the request is rejected with 403 Forbidden
Namespace configuration:
For API_KEY mode, configure namespaces per key:
# Allow this key to execute workflows in "team-a" and "team-b" only
quarkus.flow.runner.security.api-keys.team-key.secret=${TEAM_KEY}
quarkus.flow.runner.security.api-keys.team-key.roles=flow-invoker
quarkus.flow.runner.security.api-keys.team-key.namespaces=team-a,team-b
For OIDC mode, namespaces are extracted from JWT claims:
# JWT claim name containing authorized namespaces (default: "namespace")
quarkus.flow.runner.security.namespace.claim=namespace
The claim can contain:
-
Single namespace:
"namespace": "team-a" -
Array of namespaces:
"namespace": ["team-a", "team-b"] -
Comma-separated string:
"namespace": "team-a,team-b" -
Wildcard
*, which grants access to all namespaces:"namespace": "*". The wildcard can also be included in an array:"namespace": ["*"]
Only the exact namespace value * grants access to all namespaces.
Namespace matching is exact and case-sensitive. Values such as team-*,
my*, **, or " * " are treated as ordinary namespace names and do not act
as wildcard patterns.
|
Disabling namespace validation:
If your deployment doesn’t require namespace-based authorization (ABAC), you can disable it:
quarkus.flow.runner.security.namespace.validate=false
When disabled, all authenticated users can access workflows in all namespaces. Only role-based authorization (RBAC) is enforced.
Public OpenAPI document
When quarkus-smallrye-openapi is present, the OpenAPI document (/q/openapi) and Swagger UI are served without authentication to enable API discovery. With quarkus.flow.runner.openapi.expand-workflows=true (the default), the document includes one operation per registered workflow.
What the OpenAPI document exposes:
-
Workflow identifiers (
namespace:name:version) -
Titles and summaries
-
Input schemas (for client-side validation)
What is protected (requires authentication):
-
Every Runner endpoint under
/q/flow, includingGET /q/flow/definitions -
Workflow execution and instance lifecycle operations
-
Full workflow definitions (YAML/JSON source)
To hide the workflow catalog from the OpenAPI document, set quarkus.flow.runner.openapi.expand-workflows=false, or restrict /q/openapi and /q/swagger-ui at an API gateway.
API reference
Endpoint overview
All endpoints are served under /q/flow and require the flow-admin or flow-invoker role (see 4. Choose a security mode). When namespace validation is enabled, the {namespace} path parameter (or the namespace query parameter) is checked against the caller’s authorized namespaces.
| Method | Path | Purpose |
|---|---|---|
|
|
Start a workflow instance asynchronously ( |
|
|
Run a workflow instance and wait for its output ( |
|
|
Suspend a running instance |
|
|
Resume a suspended instance |
|
|
Cancel an instance |
|
|
List workflow definitions |
|
|
Get a workflow definition as YAML or JSON |
|
|
List active instances on this Runner |
|
|
List active instances of one workflow on this Runner |
Segments in square brackets are optional: when {version} is omitted, the Runner resolves the latest registered version (see Multiple workflow versions).
Common conventions
Authentication: Send credentials in the Authorization header (Authorization: Bearer <api-key-or-jwt>). With security.type=NONE, no header is required.
Request bodies: Execution endpoints accept Content-Type: application/json. The body is the workflow input and may be any JSON value (object, array, string, number, boolean). It is optional.
Common error responses:
| Status | Meaning |
|---|---|
|
Malformed JSON body, or an invalid query parameter value (for example, a terminal |
|
Missing or invalid credentials. |
|
The caller is authenticated but not authorized for the requested namespace. |
|
The workflow definition, version, or instance does not exist on this Runner. The body is a plain-text message, for example |
|
The |
Workflow execution
POST /q/flow/exec/{namespace}/{name}
Execute the latest version of a workflow asynchronously (non-blocking).
Path parameters:
-
namespace(string, required) - Workflow namespace -
name(string, required) - Workflow name
Query parameters:
-
wait(boolean, optional, defaultfalse) - Deprecated. Whentrue, the request blocks until the workflow completes and returns200 OKwith the execution metadata plus aworkflowOutputfield. Use the/syncendpoints instead.
Request body (JSON, optional):
{
"orderId": "12345",
"customerId": "alice@example.com"
}
Responses:
-
202 Accepted - Workflow accepted for asynchronous processing. Body: see Execution response fields
-
400 Bad Request - Invalid input JSON
-
401 Unauthorized - Authentication required
-
403 Forbidden - Access denied to namespace
-
404 Not Found - No versions of the workflow found
| This endpoint strictly returns 202 Accepted without workflow output, even if the workflow completes before the response is sent. This ensures a consistent contract where async execution never includes output in the response. |
Keep the instanceId from the response. You need it to suspend, resume, or cancel the instance (see Instance lifecycle (suspend, resume, cancel)).
|
Example:
curl -X POST "http://localhost:8080/q/flow/exec/examples/order-processing" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${API_KEY}" \
-d '{"orderId": "12345", "customerId": "alice@example.com"}'
POST /q/flow/exec/{namespace}/{name}/{version}
Execute a specific version of a workflow asynchronously (non-blocking).
Path parameters:
-
namespace(string, required) - Workflow namespace -
name(string, required) - Workflow name -
version(string, required) - Workflow version (semantic version format)
Request body (JSON, optional): Same as the latest version endpoint.
Responses: Same as the latest version endpoint (always 202 Accepted).
Example:
curl -X POST "http://localhost:8080/q/flow/exec/examples/order-processing/2.1.0" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${API_KEY}" \
-d '{"orderId": "12345"}'
POST /q/flow/exec/{namespace}/{name}/sync
Execute the latest version of a workflow synchronously (blocking until completion).
Path parameters:
-
namespace(string, required) - Workflow namespace -
name(string, required) - Workflow name
Request body (JSON, optional):
{
"orderId": "12345",
"customerId": "alice@example.com"
}
Responses:
-
200 OK - Workflow completed successfully. Response body contains the workflow output directly (structure depends on workflow definition)
-
400 Bad Request - Invalid input JSON
-
401 Unauthorized - Authentication required
-
403 Forbidden - Access denied to namespace
-
404 Not Found - Workflow not found
Example:
curl -X POST "http://localhost:8080/q/flow/exec/examples/order-processing/sync" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${API_KEY}" \
-d '{"orderId": "12345", "customerId": "alice@example.com"}'
Example response (200 OK):
{
"orderId": "12345",
"status": "fulfilled",
"trackingNumber": "TRK-789"
}
POST /q/flow/exec/{namespace}/{name}/{version}/sync
Execute a specific version of a workflow synchronously (blocking until completion).
Path parameters:
-
namespace(string, required) - Workflow namespace -
name(string, required) - Workflow name -
version(string, required) - Workflow version (semantic version format)
Request body (JSON, optional): Same as the latest version sync endpoint.
Responses: Same as the latest version sync endpoint (200 OK returns workflow output directly).
Example:
curl -X POST "http://localhost:8080/q/flow/exec/examples/order-processing/2.1.0/sync" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${API_KEY}" \
-d '{"orderId": "12345", "customerId": "alice@example.com"}'
Instance lifecycle (suspend, resume, cancel)
These endpoints control a workflow instance that was started on this Runner, identified by the instanceId returned from an asynchronous execution or listed by the instances endpoints.
Each operation is available in two forms:
-
POST /q/flow/exec/{namespace}/{name}/{version}/{instanceId}/<operation>- targets an instance of a specific workflow version -
POST /q/flow/exec/{namespace}/{name}/{instanceId}/<operation>- targets an instance of the latest workflow version
The instance is looked up among the active instances of the resolved workflow version. If the instance was started on an older version than the latest one, the "latest" form returns 404 Not Found. Use the versioned form (the workflowVersion field returned by the instances endpoints tells you which one) whenever more than one version is deployed.
|
Only instances held in this Runner’s in-memory active instance registry can be controlled. Completed, faulted, or cancelled instances, and instances running on another Runner replica, return 404 Not Found.
|
Path parameters:
-
namespace(string, required) - Workflow namespace (access validated if namespace authorization is enabled) -
name(string, required) - Workflow name -
version(string, required in the versioned form) - Workflow version -
instanceId(string, required) - Workflow instance identifier
Request body: None. Operations requested for the same instance are applied in the order they are received.
Responses (all operations):
-
200 OK - The operation was applied. The response has no body.
-
304 Not Modified - The instance is not in a state that allows the operation (see the table below). No change was made.
-
401 Unauthorized - Authentication required
-
403 Forbidden - Access denied to namespace
-
404 Not Found - The workflow version or the active instance was not found on this Runner
HTTP 304 responses do not carry a body, so clients should not expect one. To check the current status of the instance, query GET /q/flow/{namespace}/{name}/{version}/instances.
|
| Operation | Effect | Returns 304 Not Modified when |
|---|---|---|
|
The task currently running finishes, but the next task does not start until the instance is resumed. The instance status becomes |
The instance is already suspended, or is no longer active. |
|
A suspended instance continues with its next task. The instance status becomes |
The instance is not suspended, or is no longer active. |
|
The Runner tries to cancel the current task. Once it ends, no further task runs and the instance remains |
The instance is no longer active. |
POST /q/flow/exec/{namespace}/{name}[/{version}]/{instanceId}/suspend
Suspend a running workflow instance.
Example:
curl -i -X POST "http://localhost:8080/q/flow/exec/examples/order-processing/2.1.0/550e8400-e29b-41d4-a716-446655440000/suspend" \
-H "Authorization: Bearer ${API_KEY}"
HTTP/1.1 200 OK
POST /q/flow/exec/{namespace}/{name}[/{version}]/{instanceId}/resume
Resume a suspended workflow instance.
Example:
curl -i -X POST "http://localhost:8080/q/flow/exec/examples/order-processing/2.1.0/550e8400-e29b-41d4-a716-446655440000/resume" \
-H "Authorization: Bearer ${API_KEY}"
POST /q/flow/exec/{namespace}/{name}[/{version}]/{instanceId}/cancel
Cancel a workflow instance. Cancellation is final: a cancelled instance cannot be resumed.
Example:
curl -i -X POST "http://localhost:8080/q/flow/exec/examples/order-processing/2.1.0/550e8400-e29b-41d4-a716-446655440000/cancel" \
-H "Authorization: Bearer ${API_KEY}"
End-to-end example
The following sequence starts a long-running workflow, suspends it, inspects it, resumes it, and finally cancels it:
BASE=http://localhost:8080/q/flow
AUTH="Authorization: Bearer ${API_KEY}"
# 1. Start the workflow asynchronously and capture the instance id
INSTANCE_ID=$(curl -s -X POST "$BASE/exec/examples/order-processing/2.1.0" \
-H "$AUTH" -H "Content-Type: application/json" \
-d '{"orderId": "12345"}' | jq -r .instanceId)
# 2. Suspend it (200 OK)
curl -i -X POST "$BASE/exec/examples/order-processing/2.1.0/$INSTANCE_ID/suspend" -H "$AUTH"
# 3. Confirm it is suspended
curl -s "$BASE/examples/order-processing/2.1.0/instances?status=SUSPENDED" -H "$AUTH" | jq .
# 4. Resume it (200 OK); resuming again returns 304 Not Modified
curl -i -X POST "$BASE/exec/examples/order-processing/2.1.0/$INSTANCE_ID/resume" -H "$AUTH"
# 5. Cancel it (200 OK); afterwards the instance is no longer active and returns 404
curl -i -X POST "$BASE/exec/examples/order-processing/2.1.0/$INSTANCE_ID/cancel" -H "$AUTH"
Workflow definitions
GET /q/flow/definitions
List all workflow definitions accessible to the authenticated user.
Query parameters:
-
namespace(string, optional) - Filter workflows by namespace
Responses:
-
200 OK - Array of workflow metadata objects
-
401 Unauthorized - Authentication required
-
403 Forbidden - Access denied to the namespace given in the
namespacequery parameter
Example:
curl -H "Authorization: Bearer ${API_KEY}" \
"http://localhost:8080/q/flow/definitions?namespace=examples"
Response body example:
[
{
"namespace": "examples",
"name": "hello-runner",
"version": "1.0.0",
"dsl": "1.0.0",
"title": "Simple greeting workflow for the Runner",
"summary": null,
"metadata": {},
"inputSchema": { ... },
"links": {
"self": {
"href": "/q/flow/definitions/examples/hello-runner/1.0.0"
},
"execute": {
"href": "/q/flow/exec/examples/hello-runner/1.0.0"
}
}
}
]
Definition fields:
| Field | Type | Description |
|---|---|---|
|
string |
Workflow identity, taken from the |
|
string |
DSL version declared in |
|
string or |
Values of |
|
object |
Contents of |
|
object or |
The workflow’s |
|
string |
Path to retrieve the full definition of this version. |
|
string |
Path to execute this version asynchronously. |
Without the namespace query parameter, results are automatically filtered to the caller’s authorized namespaces when namespace validation is enabled.
|
GET /q/flow/definitions/{namespace}/{name}
Get the full workflow definition for the latest version.
Path parameters:
-
namespace(string, required) - Workflow namespace -
name(string, required) - Workflow name
Request headers:
-
Accept: application/jsonorAccept: application/yaml(controls response format; JSON is returned when the header is absent or*/*)
Responses:
-
200 OK - Workflow definition in requested format
-
401 Unauthorized - Authentication required
-
403 Forbidden - Access denied to namespace
-
404 Not Found - Workflow not found
-
415 Unsupported Media Type - Invalid Accept header
Example (YAML format):
curl -H "Accept: application/yaml" \
-H "Authorization: Bearer ${API_KEY}" \
http://localhost:8080/q/flow/definitions/examples/hello-runner
GET /q/flow/definitions/{namespace}/{name}/{version}
Get the full workflow definition for a specific version.
Path parameters:
-
namespace(string, required) - Workflow namespace -
name(string, required) - Workflow name -
version(string, required) - Workflow version
Request headers and responses: Same as the latest version endpoint.
Workflow instances
These endpoints expose the workflow instances currently tracked in this runner’s in-memory active instance registry. They report only what this specific pod/process is actively holding — not the full execution history persisted to a database. Use them to distinguish instances actively being processed on this runner from instances that are persisted but not running on any pod, for example when building a rebalancer service.
Requires the flow-admin or flow-invoker role.
GET /q/flow/instances
List active in-memory workflow instances across every workflow definition registered on this runner.
Query parameters:
-
status(string, optional) - Filter by workflow status. Only non-terminal values are accepted:PENDING,RUNNING,WAITING,SUSPENDED
Responses:
-
200 OK - Active in-memory workflow instances on this runner
-
400 Bad Request - Invalid status filter (terminal statuses
COMPLETED,FAULTED,CANCELLED, and unknown values are not allowed) -
401 Unauthorized - Authentication required
-
403 Forbidden - Access denied
Example:
curl -H "Authorization: Bearer ${API_KEY}" \
"http://localhost:8080/q/flow/instances?status=RUNNING"
Response body example:
{
"applicationId": "quarkus-flow-runner",
"instances": [
{
"instanceId": "550e8400-e29b-41d4-a716-446655440000",
"workflowName": "order-processing",
"workflowNamespace": "examples",
"workflowVersion": "2.1.0",
"status": "RUNNING",
"startedAt": "2026-06-11T10:15:30.123Z"
}
]
}
When namespace authorization is enabled, the returned instances are filtered to the definitions in the user’s authorized namespaces (flow-admin bypasses this filtering).
|
GET /q/flow/{namespace}/{name}/instances
List active in-memory workflow instances for the latest version of the given namespace/name.
Path parameters:
-
namespace(string, required) - Workflow namespace (access validated if namespace authorization enabled) -
name(string, required) - Workflow name
Query parameters and responses: Same as GET /q/flow/instances, plus 404 Not Found if the workflow definition does not exist.
GET /q/flow/{namespace}/{name}/{version}/instances
List active in-memory workflow instances for a specific namespace/name/version.
Path parameters:
-
namespace(string, required) - Workflow namespace (access validated if namespace authorization enabled) -
name(string, required) - Workflow name -
version(string, required) - Workflow version
Query parameters and responses: Same as the latest version endpoint above.
Example:
curl -H "Authorization: Bearer ${API_KEY}" \
http://localhost:8080/q/flow/examples/order-processing/2.1.0/instances
Instance fields
| Field | Type | Description |
|---|---|---|
|
string |
Identifier of the workflow application (the Runner instance) reporting these instances. Same value as |
|
array |
List of active instance snapshots (empty array when none match). |
|
string |
Unique identifier of the workflow instance. |
|
string |
Name of the workflow the instance belongs to. |
|
string |
Namespace of the workflow the instance belongs to. |
|
string |
Version of the workflow the instance belongs to. |
|
string |
Current status of the instance. Always one of the non-terminal values |
|
string (ISO-8601) |
Timestamp when the instance started. |
| This registry is in-memory and per-pod. It does not persist across restarts and, when running multiple Runner replicas, each replica only reports the instances it is currently holding. |
Configuration reference
Configuration property fixed at build time - All other configuration properties are overridable at runtime
Configuration property |
Type |
Default |
|---|---|---|
Enable or disable the file watcher on the source path directory. When enabled, the runner periodically polls the configured This is a build-time property. Changing it requires a rebuild. Environment variable: |
boolean |
|
Enable or disable the Flow Runner feature. When This is the master switch for the entire runner functionality. Environment variable: |
boolean |
|
Filesystem path to workflow definitions. Required when In Kubernetes/OpenShift deployments, this typically points to a ConfigMap or persistent volume mount:
Workflows are uniquely identified by Environment variable: |
string |
|
Whether to follow symbolic links when scanning for workflow definition files. When Set to Environment variable: |
boolean |
|
Polling interval for detecting new workflow files. Uses the Quarkus duration format (e.g., This is a runtime property and can be changed without rebuilding the application. Environment variable: |
|
|
Authentication type. Selects which authentication mechanism to use:
Environment variable: |
|
required |
JWT claim containing the namespaces authorized for the current identity. Used when
For a non-admin OIDC identity, a missing, null, empty, or blank claim grants no namespace access when namespace validation is enabled. Identities with the Namespace matching is exact and case-sensitive. Partial wildcard values such as Example:
Environment variable: |
string |
|
Enables or disables namespace authorization. When enabled:
When disabled, namespace authorization is skipped and access is controlled only through role-based authorization. Environment variable: |
boolean |
|
Enable or disable dynamic workflow operations in OpenAPI document. When
When Security Note: The OpenAPI document ( Exposed Information:
NOT Exposed:
Environment variable: |
boolean |
|
The API key secret value. Should be loaded from environment variables or Kubernetes Secrets:
Environment variable: |
string |
required |
Roles assigned to this API key. Predefined roles:
Example:
Environment variable: |
list of string |
required |
Namespaces authorized for this API key. The value Environment variable: |
list of string |
|
|
About the Duration format
To write duration values, use the standard You can also use a simplified format, starting with a number:
In other cases, the simplified format is translated to the
|
Advanced topics
Multiple workflow versions
The Runner supports running multiple versions of the same workflow simultaneously. Workflows are uniquely identified by the namespace:name:version tuple.
Version resolution strategy:
When executing without specifying a version (/q/flow/exec/{namespace}/{name}):
-
All registered versions of the workflow are retrieved
-
Versions are sorted using semantic versioning rules (highest first)
-
The highest version is selected as "latest"
Example:
If you have these versions registered:
-
examples:order-processing:2.1.0 -
examples:order-processing:2.0.0 -
examples:order-processing:1.9.5
Calling /q/flow/exec/examples/order-processing will execute version 2.1.0.
Deployment workflow:
-
Deploy new workflow version as a separate YAML file:
workflows/order-processing-v2.1.0.yamldocument: dsl: "1.0.0" namespace: examples name: order-processing version: "2.1.0" # ... input, do, and other workflow sections -
Update the ConfigMap or volume with the new file
-
If the file watcher is enabled, the new version is picked up automatically; otherwise restart the Runner
-
New executions automatically use version
2.1.0 -
Old executions on version
2.0.0continue running normally
Version requirements:
-
document.versionfield is required in all workflow definitions -
Version must follow semantic versioning format (
MAJOR.MINOR.PATCH) -
Pre-release versions are supported (e.g.,
1.0.0-alpha,2.0.0-rc.1) -
Invalid version strings cause startup failure with a clear error message
Deploying to Kubernetes/OpenShift
The Runner is designed for cloud-native deployments where workflows are managed as configuration.
ConfigMap-based deployment:
-
Create a ConfigMap with workflow definitions:
workflow-definitions.yamlapiVersion: v1 kind: ConfigMap metadata: name: workflow-definitions namespace: workflows data: hello-runner.yaml: | document: dsl: "1.0.0" namespace: examples name: hello-runner version: "1.0.0" title: "Simple greeting workflow" input: schema: format: json document: type: object properties: name: type: string do: - greet: set: greeting: "${ \"Hello, \" + .name + \"!\" }" -
Mount the ConfigMap in your Deployment:
deployment.yamlapiVersion: apps/v1 kind: Deployment metadata: name: workflow-runner spec: template: spec: containers: - name: runner image: quay.io/myorg/workflow-runner:latest env: - name: FLOW_API_KEY valueFrom: secretKeyRef: name: flow-secrets key: api-key volumeMounts: - name: workflows mountPath: /deployments/workflows readOnly: true volumes: - name: workflows configMap: name: workflow-definitions -
Configure the Runner to load from the mount path:
application.propertiesquarkus.flow.runner.enabled=true quarkus.flow.runner.source.path=/deployments/workflows quarkus.flow.runner.security.type=API_KEY quarkus.flow.runner.security.api-keys.default.secret=${FLOW_API_KEY} quarkus.flow.runner.security.api-keys.default.roles=flow-admin # Enable file watcher to pick up new workflows without pod restart quarkus.flow.runner.source.watch.enabled=true quarkus.flow.runner.source.watch.interval=5s
| With the file watcher enabled, adding a new workflow definition to the ConfigMap does not require a pod restart — the Runner detects and registers the new file automatically within the configured polling interval. |
GitOps workflow:
-
Store workflow definitions in a Git repository
-
Use a GitOps operator (Flux, ArgoCD) to sync workflows to ConfigMaps
-
With the file watcher enabled, new workflow definitions are picked up automatically — no Rolling Update needed for additions
-
New versions become available automatically
Troubleshooting
Workflows not loading at startup
Symptom: Application starts but no workflows are registered.
Causes:
-
Path does not exist or is not readable
-
Path contains no valid workflow files
-
Workflow files have syntax errors
Solutions:
-
Verify the path exists and is readable:
ls -la /deployments/workflows -
Check application logs for parse errors:
ERROR [io.qua.flo.run.loa] Failed to parse workflow: workflows/broken.yaml -
Validate YAML syntax using a linter:
yamllint workflows/ -
Ensure required fields are present in
documentsection (dsl,namespace,name,version) anddoarray is defined
403 Forbidden on execution
Symptom: Execution requests return 403 Forbidden even with valid authentication.
Causes:
-
User not authorized for the workflow’s namespace
-
Namespace claim missing, empty or contains only blank values from JWT token (OIDC mode and non-admin)
-
API key not configured with required namespaces (API_KEY mode)
Solutions:
For OIDC mode:
-
Inspect the JWT token payload:
echo $JWT_TOKEN | cut -d'.' -f2 | base64 -d | jq . -
Verify the
namespaceclaim contains the requested namespace:{ "namespace": ["examples", "team-a"] } -
If the claim name is different, configure it:
quarkus.flow.runner.security.namespace.claim=custom-namespaces
For API_KEY mode:
-
Check the API key configuration includes the namespace:
quarkus.flow.runner.security.api-keys.mykey.namespaces=examples,team-a -
If empty, the key has access to all namespaces (verify it’s not a typo)
For development, temporarily disable namespace validation:
quarkus.flow.runner.security.namespace.validate=false
| Never use this in production. |
Version resolution not working
Symptom: Execution fails with 404 Not Found when calling /q/flow/exec/{namespace}/{name} (without version).
Cause: No versions of the workflow are registered with that namespace and name.
Solutions:
-
List registered workflows:
curl http://localhost:8080/q/flow/definitions -
Verify the
document.namespaceanddocument.namematch exactly (case-sensitive) -
Check that workflow files were loaded at startup:
INFO [io.qua.flo.run.loa] Registered workflow: examples:hello-runner:1.0.0 -
Ensure the workflow definition includes a valid
document.versionfield:document: dsl: "1.0.0" namespace: examples name: hello-runner version: "1.0.0" # Required, must be semantic version
Authentication not required (security disabled)
Symptom: Endpoints are accessible without Authorization header.
Cause: Security mode is set to NONE.
Solution:
Verify the security configuration:
quarkus.flow.runner.security.type=API_KEY # or OIDC
Check application logs for the security mode warning:
WARN Flow Runner security is disabled (type=NONE)
If the configuration is correct but authentication is still disabled, ensure API keys or OIDC configuration is valid (the Runner may fall back to NONE mode on configuration errors).
What’s next?
Now that you have the Runner operational, explore these advanced topics:
Core Workflow Features:
-
Data flow and context management - Understand how to manipulate data between workflow tasks
-
Java DSL cheatsheet - Learn workflow DSL syntax for creating complex definitions
-
CNCF Specification Mapping - Understand how Quarkus Flow maps to the Open Workflow spec
AI and Integrations:
-
LangChain4j integration - Build agentic AI workflows with LLM tasks
-
HTTP and OpenAPI tasks - Call external REST APIs from workflows
-
Messaging and events - Integrate with Kafka, AMQP, and CloudEvents
Production Deployment:
-
Configure state persistence - Make workflows durable across restarts
-
Fault tolerance and resilience - Configure retries and circuit breakers
-
Secrets management - Handle sensitive data in workflow definitions
Development and Testing:
-
Testing and debugging - Write tests for workflow definitions
-
Quarkus OIDC Guide - Configure OIDC authentication in detail