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 writtenafterActions (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:

  1. The audit webhook runs first (blocking, fail-fast — if it fails, the client gets 502).
  2. The forward-replace responder proxies the request upstream with an added header, and its response is returned to the client.
  3. 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.