Lab 2 – Call HTTP & OpenAPI services

In this lab, you will extend your Quarkus Flow application to communicate with the outside world.

You will:

  • Create a dummy secured REST endpoint to act as a downstream service.

  • Add an HTTP task to your workflow to call that secured endpoint.

  • Call an OpenAPI operation simply by referencing its operationId.

  • Secure the calls using the Quarkus CredentialsProvider fallback mechanism.

  • See how Quarkus Flow automatically maps HTTP failures (WorkflowException) into standard RFC 7807 Problem Details responses.

  • Use the Dev UI and trace logs to troubleshoot network issues.

For your reference, this lab has a full working example here: https://github.com/quarkiverse/quarkus-flow/tree/main/examples/http-basic-auth

1. Add HTTP client and Secret configuration

First, let’s configure the HTTP client timeouts, enable wire logging to see the actual payloads, and define a dummy secret in your application.properties for local testing.

(We will also define a quick test user for Quarkus Security so our dummy endpoint can authenticate the request).

application.properties
# HTTP Client Tuning
quarkus.flow.http.client.connect-timeout=5000
quarkus.flow.http.client.read-timeout=10000

# Enable RESTEasy Reactive wire logging
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

# Define a local dummy secret for the workflow to use
demo.username=alice
demo.password=s3cr3t!

# Define the Quarkus Security test user for the downstream endpoint to validate against
quarkus.security.users.embedded.users.alice=s3cr3t!
quarkus.security.users.embedded.roles.alice=user

(For advanced details on these settings, see Configure the HTTP client and Resolve secrets securely).

2. Create the secured downstream service

To test our workflow, we need a downstream service that requires authentication. Create a standard JAX-RS resource protected by @RolesAllowed("user"):

package org.acme.example;

import java.time.Instant;
import java.util.Map;

import jakarta.annotation.security.RolesAllowed;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.SecurityContext;

@Path("/secure/profile")
@Produces(MediaType.APPLICATION_JSON)
public class SecureCustomerProfileResource {

    @Context
    SecurityContext securityContext;

    @GET
    @RolesAllowed("user")
    public Map<String, Object> getProfile() {
        // In a real system, you'd look up the current customer context.
        // Here we just return a canned "profile" plus the caller principal.
        String caller = securityContext.getUserPrincipal() != null ? securityContext.getUserPrincipal().getName()
                : "anonymous";

        return Map.of("customerId", 123, "name", "Jane Doe", "tier", "GOLD", "lastUpdatedAt", Instant.now().toString(),
                "servedBy", caller);
    }
}

3. Add the HTTP workflow

Now, create the workflow that calls your newly secured endpoint.

package org.acme.example;

import static io.serverlessworkflow.fluent.func.FuncWorkflowBuilder.workflow;
import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.get;
import static io.serverlessworkflow.fluent.spec.dsl.DSL.basic;
import static io.serverlessworkflow.fluent.spec.dsl.DSL.secret;

import java.net.URI;

import jakarta.enterprise.context.ApplicationScoped;

import org.eclipse.microprofile.config.inject.ConfigProperty;

import io.quarkiverse.flow.Flow;
import io.serverlessworkflow.api.types.Workflow;

@ApplicationScoped
public class CustomerProfileFlow extends Flow {

    @ConfigProperty(name = "demo.server", defaultValue = "http://localhost:8080")
    String securedServer;

    @Override
    public Workflow descriptor() {
        URI endpoint = URI.create(securedServer + "/secure/profile");

        return workflow("secure-customer-profile")
                // Load secrets into workflow data (see secrets documentation)
                .use(secret("demo"))
                .tasks(
                        // GET with HTTP Basic credentials taken from the secret
                        get(endpoint, basic("${ $secret.demo.username }", "${ $secret.demo.password }")))
                .build();
    }
}

Notice how this workflow manages security:

  • .use(secret("demo")) tells the engine to load the demo secret bundle.

  • get(endpoint, basic(…​)) attaches the HTTP Basic Auth headers by injecting the demo.username and demo.password directly from the workflow’s secure context.

4. Expose the workflow as a REST endpoint

Create a JAX-RS resource that invokes the workflow so you can trigger it from your browser:

package org.acme.example;

import java.util.Map;

import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

import io.smallrye.mutiny.Uni;

@Path("/api/profile")
@Produces(MediaType.APPLICATION_JSON)
public class CustomerProfileResource {

    @Inject
    CustomerProfileFlow customerProfileFlow;

    @GET
    public Uni<Map<String, Object>> getProfileViaWorkflow() {
        return customerProfileFlow.startInstance().onItem().transform(r -> r.asMap().orElseThrow());
    }
}

Key points:

  • The endpoint is non-blocking, returning a Uni<Map<String, Object>>.

  • Because it is reactive, if the HTTP call inside the workflow fails (e.g., a 401 Unauthorized or 404 Not Found), Quarkus Flow’s ExceptionMapper catches it automatically.

5. Try a failing call

With dev mode running (./mvnw quarkus:dev):

  1. Call the endpoint but intentionally break the credentials (for example, change demo.password=wrong in your application.properties).

  2. Observe the HTTP response. You won’t get a messy Java stack trace; you get a clean, standard Problem Details response:

    HTTP/1.1 401 Unauthorized
    Content-Type: application/json
    {
      "type": "https://serverlessworkflow.io/spec/1.0.0/errors/communication",
      "status": 401,
      "title": "HTTP 401 Unauthorized",
      "instance": "do/0/..."
    }
  3. Open the Quarkus Dev UI (http://localhost:8080/q/dev). Check the logs from the REST client logger to see the exact wire request that failed!

6. Call an OpenAPI operation

If you have an OpenAPI specification file for a downstream service, you don’t even need to write the HTTP URL or method.

  1. Download the Petstore Swagger JSON and place it in your project at src/main/resources/openapi/petstore.json.

  2. Add a new workflow that uses the openapi() builder:

package org.acme.example;

import static io.serverlessworkflow.fluent.func.FuncWorkflowBuilder.workflow;
import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.openapi;

import java.net.URI;

import jakarta.enterprise.context.ApplicationScoped;

import io.quarkiverse.flow.Flow;
import io.serverlessworkflow.api.types.Workflow;

@ApplicationScoped
public class PetstoreFlow extends Flow {

    @Override
    public Workflow descriptor() {
        final URI petstoreUri = URI.create("openapi/petstore.json");

        return workflow("petstore")
                .tasks(
                        // 1) Find pets by status
                        openapi()
                                .document(petstoreUri).operation("findPetsByStatus").parameter("status", "sold")
                                // Pick the first pet id and expose it as `selectedPetId`
                                .outputAs("${ { selectedPetId: .[0].id } }"),
                        // 2) Fetch that pet by id
                        openapi()
                                .document(petstoreUri).operation("getPetById").parameter("petId", "${ .selectedPetId }"))
                .build();
    }
}

Run the workflow and inspect the Dev UI traces. You will see that Quarkus Flow automatically read the petstore.json file, found the findPetsByStatus operation, determined it was a GET request, and appended the parameters correctly to the URL.

7. Summary

You have successfully completed Lab 2! You learned:

  • How to execute standard HTTP and OpenAPI calls from your workflows.

  • How to use local application.properties to mock secrets for HTTP Authentication.

  • How HTTP failures seamlessly translate into WorkflowException / WorkflowError RFC 7807 responses.

  • How wire logging makes troubleshooting third-party APIs incredibly easy.

Next up: Let’s dive into asynchronous, event-driven orchestrations. Proceed to Lab 3 – Events with YAML workflows.