Before & After Actions
An expectation normally has a single primary action — the response (or forward) sent back to the client. Before-actions and after-actions let an expectation also fire extra HTTP webhooks or callbacks as side-effects, either before or after that primary response.
This is useful when a request should do more than produce one response — for example mirroring traffic to a second service, fanning a request out to several systems, or calling a downstream dependency and only responding if that call succeeds.
When to use them
| Pattern | Use | Which list |
|---|---|---|
| Tee / mirror | Copy each matched request to another service while still returning the mocked response to the client. | afterActions |
| Shadow | Send traffic to a new implementation in the background to compare behaviour, without affecting the client. | afterActions |
| Fan-out | Notify several downstream systems for a single incoming request. | afterActions (or non-blocking beforeActions) |
| Gate / precondition | Call a downstream dependency first and only return the primary response if it succeeds. | blocking beforeActions |
Before vs after
- After-actions run after the primary response has been written to the client. They are always fire-and-forget: their own responses are discarded and any failures are only logged, so they never change or delay the response the client receives.
- Before-actions run before the primary response. By default they block the response until they complete, so they can be used as a precondition — and they can abort the response entirely if they fail.
For a matched request the order is: beforeActions (may block or gate the response) → the primary action (response or forward) → the client response is written → afterActions (fire-and-forget, after the response). After-actions run only once the response has been written, so they cannot change or delay what the client receives.
Action shape
Both beforeActions and afterActions accept either a single action object or an array of them. Each action specifies exactly one of the following targets, plus an optional delay:
| Field | Description |
|---|---|
| httpRequest | An HTTP request (webhook) that MockServer sends. This is the only target that a before-action can block on. |
| httpClassCallback | A class callback invoked for the request (fire-and-forget). |
| httpObjectCallback | A WebSocket / object callback to a connected client (fire-and-forget). |
| delay | Optional delay before the action runs. |
Before-action controls
Before-actions support three extra optional fields. These are meaningful only for before-actions and are ignored on after-actions.
| Field | Type | Default | Description |
|---|---|---|---|
| blocking | boolean | true | When true the response waits for the action to complete; when false the action is started before the response but not waited for. |
| timeout | Delay object — { "timeUnit": …, "value": … } | server maxSocketTimeout | Maximum time to wait for a blocking action, expressed as a structured Delay (a timeUnit plus a value), not a bare number. If it elapses the action is treated as failed. |
| failurePolicy | string enum | BEST_EFFORT | BEST_EFFORT — log the failure and continue to the primary response. FAIL_FAST — abort and return 502 Bad Gateway (the primary action does not run). |
Only webhook before-actions can block. A httpClassCallback or httpObjectCallback before-action is always dispatched fire-and-forget, even if blocking is true (MockServer logs a warning). Use a httpRequest before-action when you need the response to wait or to gate on success.
Example — fan-out with after-actions
Return a mocked response to the client and also mirror the request to two other services in the background.
PUT /mockserver/expectation HTTP/1.1
Content-Type: application/json
{
"httpRequest" : {
"method" : "POST",
"path" : "/order"
},
"httpResponse" : {
"statusCode" : 201,
"body" : "{ \"status\": \"created\" }"
},
"afterActions" : [
{ "httpRequest" : { "method" : "POST", "path" : "/analytics", "headers" : { "Host" : [ "analytics.svc:8080" ] } } },
{ "httpRequest" : { "method" : "POST", "path" : "/audit", "headers" : { "Host" : [ "audit.svc:8080" ] } } }
]
}
Example — gate with a blocking before-action
Call a downstream dependency first; if it fails or takes longer than two seconds, return 502 instead of the mocked response.
PUT /mockserver/expectation HTTP/1.1
Content-Type: application/json
{
"httpRequest" : {
"method" : "GET",
"path" : "/account"
},
"httpResponse" : {
"statusCode" : 200,
"body" : "{ \"account\": \"ok\" }"
},
"beforeActions" : [
{
"httpRequest" : { "method" : "GET", "path" : "/auth/check", "headers" : { "Host" : [ "auth.svc:8080" ] } },
"blocking" : true,
"timeout" : { "timeUnit" : "SECONDS", "value" : 2 },
"failurePolicy" : "FAIL_FAST"
}
]
}
Java client
new MockServerClient("localhost", 1080)
.when(
request()
.withMethod("GET")
.withPath("/account")
)
.withBeforeAction(
beforeAction()
.withHttpRequest(request().withMethod("GET").withPath("/auth/check").withHeader("Host", "auth.svc:8080"))
.withBlocking(true)
.withTimeout(seconds(2))
.withFailurePolicy(FailurePolicy.FAIL_FAST)
)
.withAfterAction(
afterAction().withHttpRequest(request().withMethod("POST").withPath("/audit").withHeader("Host", "audit.svc:8080"))
)
.respond(
response()
.withStatusCode(200)
.withBody("{ \"account\": \"ok\" }")
);
Using values from the triggering request
A webhook's fields can reference values from the request that triggered the expectation using {$request.…} runtime expressions, which are resolved before the webhook is sent. Supported expressions include {$url}, {$request.method}, {$request.header.NAME}, {$request.query.NAME}, {$request.path.NAME}, and {$request.body#/json/pointer}. Unknown or missing values resolve to an empty string. These are the same expressions used by OpenAPI callbacks.
Unified ordered steps
For more complex pipelines, the steps field provides a unified, ordered alternative to separate beforeActions, a primary action, and afterActions. A steps list declares an ordered sequence of actions where exactly one step is marked as the responder (the action that produces the HTTP response), and the remaining steps are side-effects.
Steps that appear before the responder behave like before-actions (they can block, timeout, and gate the response). Steps that appear after the responder behave like after-actions (fire-and-forget).
When to use steps instead of beforeActions/afterActions
- When you want a single ordered list that makes the pipeline sequence explicit.
- When the responder is a forward (proxy) and you also want side-effect forwards, all in one ordered list.
- When you want side-effects both before and after the response in a single declaration.
Backward compatibility: existing expectations using beforeActions and/or afterActions with a top-level primary action work unchanged. The steps field is an alternative — you do not need to migrate.
Step shape
Each step specifies exactly one action target:
| Field | Can be responder? | Description |
|---|---|---|
| httpResponse | Yes | Static or templated response. |
| httpForward | Yes | Proxy forward. As a side-effect, the upstream response is discarded. |
| httpOverrideForwardedRequest | Yes | Forward with request/response overrides. |
| httpClassCallback | Yes | Server-side class callback. |
| httpObjectCallback | Yes | WebSocket object callback. |
| httpRequest | No (side-effect only) | Webhook — fires an HTTP request. This is the only target that can block in a pre-responder step. |
| httpError | Yes (solo only) | Connection-level error. Must be the only step — cannot combine with other steps. |
Each step also supports:
- responder (boolean) — exactly one step must have responder: true.
- delay — delay before the step runs.
- blocking, timeout, failurePolicy — for pre-responder side-effect steps (same semantics as before-action controls).
Validation rules
- Exactly one step must be the responder (responder: true).
- Each step must have exactly one action target.
- httpError cannot be combined with other steps — it must be the only step.
- httpRequest (webhook) cannot be a responder — it is side-effect-only.
- steps cannot be combined with beforeActions — use steps for the full ordered pipeline.
- steps cannot be combined with a top-level response action (e.g. httpResponse, httpForward) — the responder step defines the action.
- steps can coexist with afterActions — after-actions fire after the entire steps pipeline completes.
Invalid combinations are rejected at expectation creation time with a clear error message.
Example — webhook then forward-replace responder
PUT /mockserver/expectation HTTP/1.1
Content-Type: application/json
{
"httpRequest" : {
"method" : "POST",
"path" : "/order"
},
"steps" : [
{
"httpRequest" : { "method" : "POST", "path" : "/audit", "headers" : { "Host" : [ "audit.svc:8080" ] } },
"blocking" : true,
"timeout" : { "timeUnit" : "SECONDS", "value" : 3 },
"failurePolicy" : "FAIL_FAST"
},
{
"httpOverrideForwardedRequest" : {
"requestOverride" : { "headers" : { "X-Audited" : [ "true" ] } }
},
"responder" : true
},
{
"httpRequest" : { "method" : "POST", "path" : "/analytics", "headers" : { "Host" : [ "analytics.svc:8080" ] } }
}
]
}
In this pipeline:
- The audit webhook runs first (blocking, fail-fast — if it fails, the client gets 502).
- The forward-replace responder proxies the request upstream with an added header, and its response is returned to the client.
- The analytics webhook runs after the response as a fire-and-forget side-effect.
Java client
import static org.mockserver.model.ExpectationStep.step;
new MockServerClient("localhost", 1080)
.when(
request()
.withMethod("POST")
.withPath("/order")
)
.withSteps(
step()
.withHttpRequest(request().withMethod("POST").withPath("/audit").withHeader("Host", "audit.svc:8080"))
.withBlocking(true)
.withTimeout(seconds(3))
.withFailurePolicy(FailurePolicy.FAIL_FAST),
step()
.withHttpOverrideForwardedRequest(
forwardOverriddenRequest()
.withRequestOverride(request().withHeader("X-Audited", "true"))
)
.withResponder(true),
step()
.withHttpRequest(request().withMethod("POST").withPath("/analytics").withHeader("Host", "analytics.svc:8080"))
)
.upsert();
// When steps are present, the responder step defines the primary action.
// Use .upsert() to submit the expectation without setting a redundant
// top-level action (the server rejects steps + a top-level action).
Note: When steps is present, the primary action is determined by the responder step. Use .upsert() to submit the expectation without setting a redundant top-level action. Using .respond() or .forward() would set a top-level action that conflicts with the steps pipeline, and the server would reject the expectation. afterActions are still allowed alongside steps and fire after the entire steps pipeline completes.
Notes
- An expectation without beforeActions, afterActions, or steps behaves exactly as before — these fields are fully optional and backward compatible.
- After-actions never affect the client response; even on a FAIL_FAST before-action abort, the 502 counts as the response and any after-actions still fire.
- Webhook responses are discarded — these actions are about side-effects, not about composing the client response.
- The steps field is an alternative to beforeActions + a top-level primary action. When steps is present it determines the dispatch pipeline; the responder step's action becomes the primary action. steps cannot be combined with beforeActions or a top-level response action (the server rejects such expectations), but afterActions still fire after the steps pipeline completes.