Mark as Flow (Manual Flows)

Manually mark code as a flow to track entry points that aren't covered by Hud's automatic instrumentation, using the io.hud.api Flow library.

Overview

Hud automatically instruments your application's entry points — HTTP frameworks, message queues, background workers, and more — with no code changes. Manual flows let you extend this to entry points that automatic instrumentation doesn't cover.

When you mark a block of code as a flow, the Hud agent collects flow metrics and forensics for it, and Hud treats it as a first-class entry point: it appears in the UI and MCP, and gets issue detection, error rates, duration graphs, and related issues — just like an auto-detected flow.

Manual flows in Java are provided by the lightweight io.hud.api library and its Flow class. There are three kinds of manual flows:

Flow typeUse it forWhere it appears in HudAPI
HTTPUnsupported HTTP endpointsEndpoints pageFlow.startHttp
QueueCustom or in-house queue consumersQueues pageFlow.startQueue
CustomEverything else (e.g. scripts, scheduled tasks, cron jobs)Custom Flows pageFlow.start

Every flow started with Flow.start, Flow.startQueue, or Flow.startHttp must be finished with Flow.end(). All three flow types also support Flow.setContext and Flow.setFailure.



Requirements

SDKMinimum agent versionAPI librarySupported flow types
Java0.2.2io.hud.api 0.1.0Custom
Java0.3.1io.hud.api 0.2.0HTTP, Custom, Queue
🚧

Important Notes

  • The manual flow API only works when your service runs with the Hud agent attached (-javaagent). Without the agent, every Flow call is a safe no-op — Flow.start (and Flow.startQueue / Flow.startHttp) returns null, and try-with-resources tolerates a null resource — so the same code runs unchanged outside Hud.
  • Flow names must be stable, non-empty strings. Avoid high-cardinality names (e.g. embedding user IDs or timestamps) — put variable data in Flow.setContext instead. The number of distinct custom flow names is capped (default 1000); once the cap is reached, additional new flow names are skipped and Hud reports partial data.
  • Manual flows cannot be nested: calling Flow.start (or startQueue / startHttp) inside an already-active flow (or inside an auto-instrumented request) is skipped, and the parent flow is used.
  • Keep the agent up to date. If the io.hud.api library is newer than the running agent supports, the API becomes a no-op until you upgrade the agent.

Add the API library

Hud API library is available on Maven Central.

Add io.hud.api to your build as a regular (runtime) dependency.

dependencies {
    implementation("io.hud:hud-api:0.2.0")
}
<dependency>
  <groupId>io.hud</groupId>
  <artifactId>hud-api</artifactId>
  <version>0.2.0</version>
</dependency>

The library requires Java 8 or later and has no third-party dependencies.

📘

Already running the agent?

The io.hud.api library only adds the manual-flow API surface. You still start Hud the usual way — see Java Installation for attaching the agent with -javaagent.


Mark HTTP flows

Use HTTP flows for HTTP entry points that Hud doesn't auto-instrument. Flow.startHttp(path, method) starts an HTTP flow named by the path and method you provide; it appears on the Endpoints page.

Wrap the request handling in a try-with-resources block so the flow ends automatically, and use Flow.setStatusCode(code) to report the response status. If you never call setStatusCode, the flow is reported with a status code of 200.

import io.hud.api.Flow;

try (AutoCloseable flow = Flow.startHttp("/orders/{id}", "GET")) {
    Order order = handleRequest(id);
    Flow.setStatusCode(200);
}

Flow.setStatusCode(code, failureReason) sets the status code and flags the flow as failed in one call — useful on error responses:

import io.hud.api.Flow;

try (AutoCloseable flow = Flow.startHttp("/orders/{id}", "GET")) {
    Order order = handleRequest(id);
    Flow.setStatusCode(200);
} catch (NotFoundException e) {
    Flow.setStatusCode(404, "OrderNotFound");
    throw e;
}

To enrich the flow's forensics, pass a StartHttpFlowOptions with request data.

import io.hud.api.Flow;
import io.hud.api.StartHttpFlowOptions;
import java.util.Map;

try (AutoCloseable flow = Flow.startHttp(
        "/orders/{id}",
        "GET",
        new StartHttpFlowOptions()
            .params(Map.of("id", "42"))
            .query(Map.of("expand", "items"))
            .headers(requestHeaders))) {
    Order order = handleRequest(id);
    Flow.setStatusCode(200);
}
👉

setStatusCode only takes effect inside an active HTTP flow — calling it outside one is skipped.


Mark Queue flows

To instrument a custom or in-house queue consumer, start the flow with Flow.startQueue(name). The flow appears on the Queues page as a first-class queue (not as a custom flow), and no SQS / Kafka dependency is required.

Optionally pass a StartQueueFlowOptions to describe the message being handled:

  • framework(String) — a short string identifying your queue (e.g. "sqs", "kafka"). Defaults to "custom".
  • enqueuedAt(Instant) — when the message was enqueued. Hud uses it to record the end-to-end duration (how long the message waited in the queue before processing).
  • messageCount(int) — the number of messages processed in this invocation.
  • messages(List<Object>) — the messages handled in this invocation.

Option A: try-with-resources (recommended)

import io.hud.api.Flow;
import io.hud.api.StartQueueFlowOptions;

try (AutoCloseable flow = Flow.startQueue(
        "orders",
        new StartQueueFlowOptions()
            .framework("sqs")
            .enqueuedAt(message.getEnqueuedAt())  // an Instant
            .messageCount(1))) {
    processOrder(message.getPayload());
}

Option B: Manual start / end

import io.hud.api.Flow;
import io.hud.api.StartQueueFlowOptions;

void handleMessage(Message message) {
    Flow.startQueue("orders", new StartQueueFlowOptions().framework("sqs"));
    try {
        processOrder(message.getPayload());
    } finally {
        Flow.end();
    }
}

Mark Custom flows

Use custom flows for entry points that aren't an HTTP endpoint or a queue — scheduled tasks, cron jobs, background loops, and so on.

Custom flows appear on the Custom Flows page, which includes a table of all custom flows plus a dedicated page per flow with detailed graphs, functions, forensics, and related issues.

Optionally pass a StartFlowOptions to record the framework the flow runs under; it defaults to "custom".

import io.hud.api.Flow;
import io.hud.api.StartFlowOptions;

try (AutoCloseable flow = Flow.start("nightly-report", new StartFlowOptions().framework("scheduler"))) {
    Data data = collectData(date);
    buildReport(data);
}

Option A: try-with-resources (recommended)

Flow.start(name) returns an AutoCloseable handle. Wrap your work in a try-with-resources block; the flow ends automatically when the block exits (including on exceptions).

import io.hud.api.Flow;

try (AutoCloseable flow = Flow.start("nightly-report")) {
    Data data = collectData(date);
    buildReport(data);
}

Option B: Manual start / end

When you can't wrap the work in a single block, bracket it with Flow.start(name) and Flow.end(). Always pair every start with an end, including on the error path.

import io.hud.api.Flow;

void runCronJob() {
    Flow.start("cleanup-expired-sessions");
    try {
        deleteExpiredSessions();
    } finally {
        Flow.end();
    }
}
👉

An end() without a preceding start() is safely skipped.


Marking a flow as failed

By default a flow is recorded as successful. Mark it as failed to have Hud surface it in error rates and open an investigation.

  • Flow.setFailure(reason) — flag the current flow as failed while it's still active.
  • Flow.end(reason) — flag the current flow as failed and end it in one call (equivalent to setFailure(reason) followed by end()).
import io.hud.api.Flow;

void runCronJob() {
    Flow.start("cleanup-expired-sessions");
    try {
        deleteExpiredSessions();
        Flow.end();
    } catch (Exception e) {
        Flow.end("CleanupFailed");
        throw e;
    }
}

setFailure and end(reason) only take effect inside an active flow — calling them outside a flow is skipped with a warning.


Attaching business context

Use Flow.setContext(Map) to attach business key/values (e.g. orderId, tenant) to the current flow's forensics. Supported value types are strings, numbers, booleans, maps, and lists.

import io.hud.api.Flow;
import java.util.HashMap;
import java.util.Map;

try (AutoCloseable flow = Flow.start("process-batch")) {
    Map<String, Object> context = new HashMap<>();
    context.put("batchId", batchId);
    context.put("tenant", "acme");
    context.put("retryCount", 2);
    Flow.setContext(context);

    Result result = runBatch(batchId);
    if (result.isRejected()) {
        Flow.setFailure("BatchRejected");
    }
}

Like setFailure, setContext only applies inside an active flow.


API reference

package io.hud.api;

public final class Flow {
    // Every start* method returns an AutoCloseable handle for try-with-resources,
    // or null when no flow was started (e.g. the agent is not attached, or another
    // flow is already active).

    // --- Custom flows (Custom Flows page) ---
    // Begin a named flow on the current thread; use with try-with-resources.
    public static AutoCloseable start(String name);
    public static AutoCloseable start(String name, StartFlowOptions options);

    // --- Queue flows (Queues page) ---
    public static AutoCloseable startQueue(String name);
    public static AutoCloseable startQueue(String name, StartQueueFlowOptions options);

    // --- HTTP flows (Endpoints page) ---
    public static AutoCloseable startHttp(String path, String method);
    public static AutoCloseable startHttp(String path, String method, StartHttpFlowOptions options);

    // Set the HTTP status code on the active HTTP flow.
    public static void setStatusCode(int statusCode);

    // Set the status code and flag the active HTTP flow as failed with the given reason.
    public static void setStatusCode(int statusCode, String failureReason);

    // --- Ending / failing any flow type ---
    // End the current flow on the current thread.
    public static void end();

    // End the current flow and flag it as failed with the given reason.
    public static void end(String failureReason);

    // Flag the current flow as failed with the given reason.
    public static void setFailure(String reason);

    // Attach business key/values to the current flow.
    // Supported value types: string, number, boolean, maps, and lists.
    public static void setContext(Map<String, Object> context);
}

Options are configured with a fluent builder. Every field is optional; anything left unset is omitted.

// Custom flow options
new StartFlowOptions()
    .framework(String);              // defaults to "custom"

// Queue flow options (extends StartFlowOptions)
new StartQueueFlowOptions()
    .framework(String)               // defaults to "custom"
    .enqueuedAt(Instant)             // sets the end-to-end duration
    .messageCount(int)               // number of messages processed in this invocation
    .messages(List<Object>);         // messages handled

// HTTP flow options (extends StartFlowOptions)
new StartHttpFlowOptions()
    .framework(String)               // defaults to "custom"
    .params(Map<String, String>)     // path / route params
    .query(Map<String, String>)      // query params
    .body(String)                    // raw request body
    .headers(Map<String, String>);   // headers

Did this page help you?