FlowProbe is a Java command-line tool for defining, executing, and validating multi-step HTTP flows from YAML.
A flow can call an endpoint, validate its response, export values from the returned JSON, reuse those values in later requests, and stop immediately when a step fails. When a failure occurs, FlowProbe can render reproducible cURL commands and optionally create an Azure DevOps work item with the failure context.
Project status: pre-release. FlowProbe is approaching its first public release, but the CLI and YAML contract may still evolve before
v0.1.0.
A useful environment check is often more than a single health endpoint.
A real workflow may need to:
- Call one service.
- Validate the response.
- Extract an identifier or other value.
- Inject that value into another request.
- Validate the next response.
- Stop immediately if any step fails.
- Produce enough information to reproduce the failure.
FlowProbe keeps that workflow in a portable YAML file that can be executed locally or, as the project evolves, from CI/CD environments.
- Define ordered HTTP flows in YAML.
- Execute multi-step HTTP requests.
- Validate HTTP status codes.
- Validate JSON response values with
equalsandnotEqualsexpectations. - Default to accepting any
2xxresponse when no explicit status is configured. - Export values from JSON responses using JSON Pointer paths.
- Preserve exported JSON scalar types such as strings, numbers, booleans, and
null. - Resolve placeholders in URLs, headers, request bodies, and nested body structures.
- Preserve the original type when a body value is exactly a placeholder.
- Stop execution after the first failed step.
- Return non-zero exit codes for failed flows and invalid CLI arguments.
- Render executed requests as reproducible cURL commands.
- Redact common sensitive HTTP headers from rendered cURL output.
- Optionally create an Azure DevOps work item when a flow fails.
- Store Azure DevOps configuration through the operating system credential store using
java-keyring. - Build as a GraalVM Native Image executable.
- Java 21
- Gradle Kotlin DSL
- Picocli
- SnakeYAML
- Java HTTP Client
- Jackson
- java-keyring
- GraalVM Native Image
- JUnit 5
- Mockito
- JaCoCo
FlowProbe does not yet have an official binary release or package-manager installation.
For now, build and run it from source.
For JVM execution:
- Java 21
For native compilation:
- GraalVM for JDK 21
- Native Image support available in the selected GraalVM distribution
Clone the repository:
git clone https://github.com/ctorressoftware/flow-probe.git
cd flow-probeRun the full verification suite:
./gradlew clean checkShow CLI help:
./gradlew run --args="--help"Show the current version:
./gradlew run --args="--version"Run the basic example:
./gradlew run --args="run --file examples/basic.yaml"Run an example with response expectations:
./gradlew run --args="run --file examples/expectations.yaml"Run a multi-step flow that exports a value and reuses it in the next request:
./gradlew run --args="run --file examples/exports.yaml"The examples currently use the public PokéAPI and therefore require network access.
Currently implemented commands:
flowprobe run --file <path>
flowprobe configure <provider>
flowprobe --help
flowprobe --version
Currently supported provider:
azure
Using Gradle:
./gradlew run --args="run --file /absolute/path/to/flow.yaml"Using a native executable:
./build/native/nativeCompile/flowprobe run \
--file /absolute/path/to/flow.yamlTicket creation is opt-in and non-interactive during run:
./gradlew run \
--args="run --file /absolute/path/to/flow.yaml --create-impediment"The work item is created only if the flow fails.
A flow contains a name and an ordered list of steps.
name: "pokemon-flow"
steps:
- name: "get-pokemon-list"
request:
url: "https://pokeapi.co/api/v2/pokemon?limit=1"
method: "GET"
headers:
accept: "application/json"
expect:
status: 200
exports:
pokemonName: "/results/0/name"
- name: "get-exported-pokemon"
request:
url: "https://pokeapi.co/api/v2/pokemon/${pokemonName}"
method: "GET"
headers:
accept: "application/json"
expect:
status: 200
body:
- path: "/name"
operator: "equals"
value: "${pokemonName}"| Field | Required | Description |
|---|---|---|
name |
Yes | Human-readable flow name. |
steps |
Yes | Ordered list of HTTP steps. |
| Field | Required | Description |
|---|---|---|
name |
Yes | Step name. |
request |
Yes | HTTP request definition. |
expect |
No | Response expectations. |
exports |
No | Values extracted from the response and added to the execution context. |
| Field | Required | Description |
|---|---|---|
url |
Yes | Target URL. Placeholders are supported. |
method |
Yes | HTTP method. |
headers |
No | HTTP headers. Placeholders are supported in names and values. |
body |
No | Request body. Maps, lists, scalar values, and placeholders are supported. |
An explicit status expectation:
expect:
status: 200If expect is omitted, or expect.status is omitted, FlowProbe considers any status in the 200-299 range successful.
Body expectations use JSON Pointer paths:
expect:
status: 200
body:
- path: "/name"
operator: "equals"
value: "pikachu"Currently supported operators:
equals
notEquals
Example:
expect:
body:
- path: "/active"
operator: "equals"
value: true
- path: "/status"
operator: "notEquals"
value: "disabled"Paths are JSON Pointer expressions and must begin with /.
Exports map a context variable name to a JSON Pointer path in the response:
exports:
userId: "/user/id"
enabled: "/user/enabled"Given:
{
"user": {
"id": 25,
"enabled": true
}
}FlowProbe stores the values with their JSON types preserved:
userId -> number 25
enabled -> boolean true
A missing export path causes execution to fail instead of silently producing an empty value.
Placeholders use this syntax:
${variableName}
For URLs and headers, interpolation is textual:
url: "https://example.test/users/${userId}"
headers:
X-Enabled: "${enabled}"If userId is the number 25 and enabled is the boolean true, the resulting HTTP values are text:
https://example.test/users/25
X-Enabled: true
Request bodies preserve types when a value is exactly one placeholder:
body:
id: "${userId}"
enabled: "${enabled}"
message: "user-${userId}"With userId = 25 and enabled = true, the serialized JSON is:
{
"id": 25,
"enabled": true,
"message": "user-25"
}This distinction allows exported JSON values to remain correctly typed across multiple HTTP steps.
FlowProbe executes steps in declaration order.
For each step it performs the following sequence:
resolve placeholders
↓
execute HTTP request
↓
validate response
↓
export response values
↓
continue to next step
If validation fails:
- the failed step is recorded as unsuccessful;
- its exports are not added to the context;
- subsequent steps are not executed;
- the flow exits with a non-zero code.
| Code | Meaning |
|---|---|
0 |
Flow completed successfully. |
1 |
Flow execution or runtime error. |
2 |
Invalid CLI arguments. |
These exit codes make FlowProbe suitable for scripting and future CI/CD integration.
FlowProbe renders executed requests as cURL commands so a request can be reproduced outside the tool.
Common sensitive headers are redacted, including headers such as:
Authorization
Proxy-Authorization
Cookie
X-API-Key
Api-Key
X-Auth-Token
X-Access-Token
X-Amz-Security-Token
Example:
Authorization: <redacted>
Header redaction does not currently attempt to detect arbitrary secrets embedded in URLs or request bodies. Avoid placing credentials directly in flow definitions.
Azure DevOps is currently the only ticket provider wired into FlowProbe.
./gradlew run --args="configure azure"FlowProbe asks for:
Azure DevOps organization
Azure DevOps project
Azure DevOps work item type
Azure DevOps Personal Access Token (PAT)
The configuration is serialized and stored through java-keyring in the operating system credential store instead of a plain-text project configuration file.
When possible, run configuration from a real terminal so the PAT can be read through Console.readPassword without echoing it. Environments without an attached Java Console currently fall back to regular standard-input reading.
Use the narrowest Azure DevOps PAT permission required for work-item creation. Azure DevOps documents vso.work_write as the scope that grants read/create/update access to work items.
Official API documentation:
./gradlew run \
--args="run --file /path/to/flow.yaml --create-impediment"FlowProbe uses the failed request information to build the work-item description. Rendered sensitive headers are redacted before being included.
The repository includes:
examples/
├── basic.yaml
├── expectations.yaml
└── exports.yaml
basic.yaml— one request with status validation.expectations.yaml— status and JSON body expectations.exports.yaml— multi-step execution with an exported placeholder.
FlowProbe supports GraalVM Native Image.
Compile:
./gradlew clean nativeCompileThe executable is generated at:
build/native/nativeCompile/flowprobe
Run it:
./build/native/nativeCompile/flowprobe --helpNative executables are platform-specific. Native behavior has primarily been verified on macOS during development.
Reachability metadata is stored under:
src/main/resources/META-INF/native-image/
When code paths involving reflection, serialization, JNI, proxies, or native integrations change, regenerate/merge metadata by exercising the affected behavior with the Native Image tracing agent.
Example:
./gradlew runWithNativeAgent \
-PappArgs="configure azure"Then rebuild the native image:
./gradlew clean nativeCompileFlowProbe uses a hexagonal architecture with explicit dependency wiring.
io.github.ctorressoftware
├── domain
│ ├── constant
│ ├── exception
│ └── model
├── application
│ ├── port
│ │ ├── in
│ │ └── out
│ └── usecase
└── infrastructure
├── callservice
├── cli
├── json
├── persistence
├── provider
├── readfile
├── renderer
└── ticket
AppConfig is the composition root. FlowProbe does not use a dependency-injection framework; dependencies are connected explicitly through constructors.
The main boundaries are:
- Domain — flow, step, expectations, context, requests, execution summaries.
- Application — use cases, orchestration, validation logic, ports.
- Infrastructure — Picocli, SnakeYAML, Jackson, HTTP, credential storage, Azure DevOps, and cURL rendering.
Run unit and integration tests:
./gradlew testRun the full verification suite, including JaCoCo coverage verification:
./gradlew clean checkThe project includes local HTTP end-to-end tests using the JDK HttpServer. These tests verify multi-step execution, typed exports, real request-body serialization, expectations, and fail-fast behavior without depending on an external service.
- No official binary release or Homebrew formula is available yet.
- Azure DevOps is the only implemented ticket provider.
- cURL is the only request renderer currently exposed.
- Body expectations currently support only
equalsandnotEquals. - Explicit
value: nullbody expectations are not yet supported. - Native Image behavior has primarily been verified on macOS.
- Azure DevOps dynamic URI path segments still need complete percent-encoding support for values containing URI-sensitive characters.
- Execution summaries do not yet expose full expectation-level failure details.
- Step execution duration is not yet measured.
- Retry policies are not implemented.
The immediate goal is the first public release:
- GitHub Actions CI.
- Native Image build and smoke test.
v0.1.0-rc.1release candidate.- Homebrew distribution.
- First stable
v0.1.0release.
Possible later improvements include:
- Additional expectation operators.
- More ticket providers.
- Additional request renderers.
- Structured execution reports.
- Better failure diagnostics.
- Multi-flow and directory execution.
- Additional operating-system builds.
Contributions are welcome. See CONTRIBUTING.md.
Before submitting a pull request:
./gradlew clean checkPlease do not report vulnerabilities or expose credentials in public issues.
See SECURITY.md for the current reporting policy.
FlowProbe is licensed under the Apache License 2.0.
Created by Carlos Torres.