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
-
A Quarkus application with Quarkus Flow set up.
-
Basic familiarity with Data flow and context management.
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
-
Configure the HTTP client — timeouts, TLS, proxies and connection pooling.
-
Data flow and context management — deeper dive into
outputAsandexportAs. -
Java DSL cheatsheet — full list of HTTP / OpenAPI task builders.