Call HTTP and OpenAPI services

Quarkus Flow builds on top of the CNCF Serverless Workflow Specification to give you first-class HTTP and OpenAPI tasks.

This guide shows how to:

  • Call any HTTP endpoint using the fluent Java DSL.

  • Call operations defined in an OpenAPI document.

  • Filter and shape the returned HTTP body data.

  • Understand the HTTP error shape (RFC 7807) returned by the engine.

For HTTP client timeouts, TLS, proxies, and connection pooling, see Configure the HTTP client.

Prerequisites

1. Call HTTP endpoints

HTTP calls are represented in the CNCF Workflow Spec as call tasks. In Quarkus Flow, you define these using the fluent shortcuts from io.serverlessworkflow.fluent.func.dsl.FuncDSL.

1.1 Simple GET / POST

For the majority of use cases, you can rely on the quick get(…​) and post(…​) shortcuts.

import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.*;

public class ApiWorkflow extends Flow {
    @Override
    public Workflow descriptor() {
        return workflow("api-calls")
            .tasks(
                // A simple GET request
                get("fetchHealth", "https://api.example.com/health"),

                // A POST request passing a payload from the workflow context
                post("createCustomer",
                     Map.of("name", "Alice", "role", "admin"),
                     "https://api.example.com/customers")
            )
            .build();
    }
}

1.2 Using the Fluent HTTP Builder

If you need HTTP methods other than GET/POST, or need to configure headers, query parameters, or specific content types, use the fluent http() builder:

workflow("advanced-http")
    .tasks(
        http("updateCustomer")
            .PUT()
            .endpoint("https://api.example.com/customers/123")
            .header("X-Custom-Header", "Flow")
            .acceptJSON()
    )
    .build();

1.3 Shaping the HTTP Response

By default, an HTTP task returns only the parsed body of the HTTP response. If the response is JSON, it is automatically parsed so you can interact with it immediately.

You can map this body directly to your own POJOs, or use jq expressions to extract specific fields before exporting the result to the global workflow context:

// Using jq: Extract just the email string from the JSON body
get("fetchUser", "https://api.example.com/users/123")
    .outputAs(".email")
    .exportAs("{ userEmail: . }");

// Using Java POJOs: The engine deserializes the JSON body into UserResponse
get("fetchUser2", "https://api.example.com/users/456")
    .outputAs((UserResponse response) -> response.email(), UserResponse.class)
    .exportAs((String email, WorkflowContextData wf) -> {
        var current = (MyWorkflowData) wf.currentData();
        return current.withUserEmail(email);
    }, String.class);

1.4 Adding Authentication

You can attach authentication to any HTTP call using the AuthenticationConfigurer. This is usually paired with Quarkus Secrets to avoid hardcoding credentials.

// Basic Auth using a named secret bundle
get("https://api.example.com/secure", auth -> auth.use("my-basic-secret"));

// Bearer Token
http("fetchData")
    .GET()
    .endpoint("https://api.example.com/v1/resource")
    .auth(auth -> auth.bearer("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."));

// OAuth2 Client Credentials
post(payload, "https://api.example.com/v1/resource",
    auth -> auth.oauth2("https://auth.example.com/token"));

2. Call OpenAPI operations

OpenAPI tasks are a higher-level wrapper around HTTP tasks that read an OpenAPI document (JSON or YAML), let you call an operation by its operationId, and automatically take care of building the correct HTTP method, path, and content type.

2.1 Place the OpenAPI file

In Quarkus, place your OpenAPI specs in the src/main/resources folder.

src/
    main/
        resources/
            openapi/
                petstore.json

2.2 Call operations by operationId

Look inside your OpenAPI document to find the operationId you want to invoke:

{
  "paths": {
    "/pet/findByStatus": {
      "get": {
        "operationId": "findPetsByStatus"
      }
    }
  }
}

In your workflow, use the openapi() builder to reference the document and invoke the ID:

import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.*;

public class PetstoreFlow extends Flow {
    @Override
    public Workflow descriptor() {
        return workflow("petstore-flow")
            .tasks(
                openapi("fetchPets")
                    .document("openapi/petstore.json")
                    .operation("findPetsByStatus")
                    .parameter("status", "available") // Maps to query/path params automatically
                    .exportAs("{ availablePets: . }") // Merges the response body into the context
            )
            .build();
    }
}

3. Logging and troubleshooting

When dealing with external APIs, you need to see the exact HTTP request/response on the wire. Configure the underlying RESTEasy Reactive client logging in your application.properties:

# Enable Quarkus Flow tracing
quarkus.flow.tracing.enabled=true

# Log HTTP requests/responses performed by HTTP/OpenAPI tasks
quarkus.flow.http.client.logging.scope=request-response
quarkus.flow.http.client.logging.body-limit=2048
quarkus.log.category."org.jboss.resteasy.reactive.client.logging".level=DEBUG

When a call fails, you will see a tracing line for the failing task alongside the raw HTTP request/response pair.

4. HTTP errors and WorkflowException mapping

When an HTTP or OpenAPI task fails (e.g., a 4xx/5xx status or a timeout), the workflow engine halts execution and throws a WorkflowException containing a WorkflowError.

Quarkus Flow maps this error exactly to the CNCF Specification’s error model, which aligns with RFC 7807 Problem Details:

{
  "type": "https://serverlessworkflow.io/spec/1.0.0/errors/communication",
  "status": 401,
  "instance": "do/0/370b9a91-22c2-4b3e-85c4-10892c2864ff",
  "title": "HTTP 401 Unauthorized",
  "details": null
}

Reactive vs Blocking Execution

Quarkus Flow provides an ExceptionMapper that automatically catches these `WorkflowException`s and translates them into proper HTTP responses for your REST API clients.

For this mapper to work correctly, you should execute your workflows reactively (returning a Uni or CompletionStage):

@Path("/customers")
public class CustomerResource {

    @Inject
    ProfileFlow profileFlow;

    @POST
    @Path("/profile")
    public Uni<Map<String, Object>> createProfile(Map<String, Object> input) {
        // Start the workflow non-blocking.
        // If an HTTP task fails inside the workflow, the ExceptionMapper
        // will automatically return the RFC 7807 Problem Details response.
        return profileFlow.startInstance("customer-profile", input)
                     .map(WorkflowInstance::output);
    }
}

If you choose the blocking style (.await().indefinitely()), Java wraps the underlying exception in a CompletionException, meaning you must manually unwrap and rethrow the WorkflowException for the mapper to catch it. Prefer the reactive style whenever possible.

See also