Extremely Serious

Category: WireMock

WireMock Response Templates with Handlebars and Response Builders

WireMock gives you two powerful levers for dynamic responses: declarative Handlebars templates embedded in stubs, and imperative Java logic via the response builder APIs. They complement each other rather than compete.


Why response templating exists

In real systems, responses rarely stay static: IDs echo from the URL, correlation IDs flow through headers, timestamps change, payloads depend on the request body, and so on. WireMock’s response templating solves this by letting you:

  • Use Handlebars to keep mocks close to the contract, with simple expressions and helpers.
  • Drop to Java builders and transformers when behaviour becomes algorithmic or needs integration with other libraries.

The design goal is: keep the simple cases in configuration, and only push complex behaviour into code.


Handlebars in WireMock: the declarative layer

Handlebars lets you embed {{expressions}} directly in response headers, bodies, and even proxy URLs. At runtime, WireMock feeds a rich request model into the template engine so the template can “see” all relevant request data.

The request model

Some of the most useful fields in the request model:

  • request.url, request.path, request.pathSegments.[n] and named path variables (e.g. request.path.customerId).
  • request.query.foo or request.query.foo.[n] for multi-valued params.
  • request.headers.X-Request-Id, request.cookies.session, request.method, request.baseUrl.
  • request.body, request.bodyAsBase64, and multipart request.parts.* for more advanced cases.

Rationale: this model gives you enough context to “shape” the response purely from the incoming HTTP request, without Java code.

Enabling and scoping templating

In programmatic (local) mode, WireMock 3+ enables templating support out of the box, but actually applying it depends on how your instance is configured:

  • You can run with local templating, where templating is applied only to stubs that specify the response-template transformer.
  • Or you can flip to global templating, where every stub is rendered through Handlebars unless explicitly disabled.

This is intentional: it prevents accidental template evaluation on basic static stubs, while still allowing an “all templates” mode when you know your mapping set depends heavily on it.

Basic Handlebars example

Below is a minimal, self-contained Java example using a modern WireMock 3.x style API, focusing only on the stub and response (no build configuration). It echoes data from path, query, and headers via Handlebars.

import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;

import static com.github.tomakehurst.wiremock.client.WireMock.*;

public class HandlebarsExample {

    public static void main(String[] args) {
        WireMockServer wm = new WireMockServer(
            WireMockConfiguration.options()
                // Local templating enabled by default in Java mode;
                // if you’d changed it globally, you can toggle here.
                .templatingEnabled(true)
        );
        wm.start();

        configureFor("localhost", wm.port());

        wm.stubFor(
            get(urlPathMatching("/customers/(.*)"))
                .willReturn(
                    aResponse()
                        .withStatus(200)
                        .withHeader("Content-Type", "application/json")
                        // Handlebars: use path segment, header, and query param
                        .withBody("""
                            {
                              "id": "{{request.pathSegments.[1]}}",
                              "correlationId": "{{request.headers.X-Correlation-Id}}",
                              "greeting": "Hello, {{request.query.name}}"
                            }
                            """)
                        // Ensure the response goes through the template engine:
                        .withTransformers("response-template")
                )
        );

        // Simple validation (manual): curl the endpoint:
        // curl -H "X-Correlation-Id: abc-123" "http://localhost:8080/customers/42?name=Alice"
        // Response should contain id=42, correlationId=abc-123, greeting "Hello, Alice".
    }
}

Why this shape:

  • We keep the logic in the template, not in Java branching, so QA or other devs can read and modify it like a contract.
  • The same body can be copied verbatim into a JSON mapping file if you later move to standalone/Cloud, which makes your tests more portable.

Handlebars helpers: beyond simple interpolation

Handlebars in WireMock is extended with a large set of helpers for dates, random values, JSON/XML processing, math, array manipulation, and more. This lets templates stay expressive without turning into full programming languages.

Some particularly pragmatic helpers:

  • Date/time: now with format, timezone, and offsets; parseDate, truncateDate for consistent timestamps.
  • Random: randomValue, randomInt, pickRandom for synthetic but realistic data.
  • JSON: jsonPath, parseJson, toJson, formatJson, jsonArrayAdd, jsonMerge, jsonRemove, jsonSort for shaping JSON payloads.
  • XPath / XML: xPath, soapXPath, formatXml.
  • Utility: math, range, contains, matches, numberFormat, base64, urlEncode, formData, regexExtract, size, and more.

Rationale: you can keep test data generation in the template layer, avoiding brittle fixture code and making mocks self-describing.


Response builders: the imperative layer

While Handlebars owns the declarative side, the Java Response builder APIs give you full programmatic control. Conceptually, you work with:

  • ResponseDefinitionBuilder (via aResponse() in stubs) at the configuration level.
  • Response and its Builder when writing extensions like ResponseTransformerV2.

A typical stub already uses the builder implicitly:

wm.stubFor(
    get(urlEqualTo("/hello"))
        .willReturn(
            aResponse()
                .withStatus(200)
                .withHeader("Content-Type", "text/plain")
                .withBody("Hello from WireMock")
        )
);

Here the builder is used in its simplest form: static status, headers, and body. The real power shows up when you combine it with extensions.

Custom transformer with Response.Builder

A common pattern is: start with the stub’s definition, then refine the actual Response in a transformer. This is where Response.Builder (or the like(response).but() style) becomes useful.

Conceptual example using a ResponseTransformerV2, which takes the already-resolved Response and lets you mutate it:

import com.github.tomakehurst.wiremock.extension.ResponseTransformerV2;
import com.github.tomakehurst.wiremock.http.Request;
import com.github.tomakehurst.wiremock.http.Response;
import com.github.tomakehurst.wiremock.stubbing.ServeEvent;

public class UppercaseNameTransformer implements ResponseTransformerV2 {

    @Override
    public String getName() {
        return "uppercase-name-transformer";
    }

    @Override
    public Response transform(Response response, ServeEvent serveEvent) {
        String originalBody = response.getBodyAsString();
        Request request = serveEvent.getRequest();
        String name = request.queryParameter("name").isPresent()
                ? request.queryParameter("name").firstValue()
                : null;

        String effectiveName = (name != null && !name.isBlank())
                ? name.toUpperCase()
                : "UNKNOWN";

        String transformedBody = originalBody.replace("{{NAME}}", effectiveName);

        return Response.Builder
                .like(response)
                .but()
                .body(transformedBody)
                .build();
    }
}

Key rationale:

  • Immutability and reuse: like(response).but() copies status, headers, etc., so you only specify what changes.
  • Composability: multiple transformers can run without trampling each other; each focuses on a narrow concern.
  • Testability: behaviour is in Java, which you can unit test thoroughly when it gets complex.

A stub can then provide the template “skeleton” that this transformer fills:

wm.stubFor(
    get(urlPathEqualTo("/greet"))
        .willReturn(
            aResponse()
                .withStatus(200)
                .withHeader("Content-Type", "text/plain")
                .withBody("Hello, {{NAME}}!") // Placeholder, not Handlebars here
                .withTransformers("uppercase-name-transformer")
        )
);

Validation strategy:

  • Hit /greet?name=alice and assert you get Hello, ALICE!.
  • Hit /greet with no name and assert you get Hello, UNKNOWN!.

This demonstrates the division of responsibilities: the stub defines the shape, the transformer plus builder define the behaviour.


Example: combining Handlebars and Response builders

To tie everything together, here is a minimal Java program that:

  • Starts WireMock.
  • Defines one stub using pure Handlebars.
  • Defines another stub that uses a custom transformer implemented with Response.Builder.

Again, no build tooling, only the code that matters for behaviour.

import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
import com.github.tomakehurst.wiremock.extension.ResponseTransformerV2;
import com.github.tomakehurst.wiremock.http.Request;
import com.github.tomakehurst.wiremock.http.Response;
import com.github.tomakehurst.wiremock.stubbing.ServeEvent;

import static com.github.tomakehurst.wiremock.client.WireMock.*;

public class WireMockTemplatingDemo {

    public static void main(String[] args) {
        WireMockServer wm = new WireMockServer(
                WireMockConfiguration.options()
                        .port(8080)
                        .templatingEnabled(true) // ensure Handlebars engine is available
                        .extensions(new UppercaseNameTransformer())
        );
        wm.start();

        configureFor("localhost", 8080);

        // 1) Pure Handlebars-based response.
        wm.stubFor(
                get(urlPathMatching("/customers/(.*)"))
                        .willReturn(
                                aResponse()
                                        .withStatus(200)
                                        .withHeader("Content-Type", "application/json")
                                        .withBody("""
                            {
                              "id": "{{request.pathSegments.[1]}}",
                              "name": "{{request.query.name}}",
                              "requestId": "{{request.headers.X-Request-Id}}"
                            }
                            """)
                                        .withTransformers("response-template")
                        )
        );

        // 2) Response builder + custom transformer.
        wm.stubFor(
                get(urlPathEqualTo("/welcome"))
                        .willReturn(
                                aResponse()
                                        .withStatus(200)
                                        .withHeader("Content-Type", "text/plain")
                                        // Placeholder token; the transformer will replace it.
                                        .withBody("Welcome, {{NAME}}!")
                                        .withTransformers("uppercase-name-transformer")
                        )
        );

        System.out.println("WireMock started on http://localhost:8080");

        // Manual validation:
        // 1) Handlebars endpoint:
        // curl -H "X-Request-Id: req-123" "http://localhost:8080/customers/42?name=Alice"
        //    -> JSON with id=42, name=Alice, requestId=req-123
        //
        // 2) Transformer + Response.Builder endpoint:
        // curl "http://localhost:8080/welcome?name=alice"
        //    -> "Welcome, ALICE!"
        // curl "http://localhost:8080/welcome"
        //    -> "Welcome, UNKNOWN!"
    }

    // Custom transformer using Response.Builder
    public static class UppercaseNameTransformer implements ResponseTransformerV2 {

        @Override
        public String getName() {
            return "uppercase-name-transformer";
        }

        @Override
        public Response transform(Response response, ServeEvent serveEvent) {
            String body = response.getBodyAsString();
            Request request = serveEvent.getRequest();
            String name = request.queryParameter("name").isPresent()
                    ? request.queryParameter("name").firstValue()
                    : null;

            String effectiveName = (name != null && !name.isBlank())
                    ? name.toUpperCase()
                    : "UNKNOWN";

            String newBody = body.replace("{{NAME}}", effectiveName);

            return Response.Builder
                    .like(response)
                    .but()
                    .body(newBody)
                    .build();
        }
    }
}

Why this example is structured this way:

  • It demonstrates that Handlebars and Response builders are orthogonal tools: you can use either alone, or both together.
  • The Handlebars stub stays close to an API contract and is easy to lift into JSON mappings later.
  • The transformer shows how to use Response.Builder for behavioural logic while preserving the stub’s declarative shape.

When to prefer which approach

In practice, a good rule of thumb is:

  • Start with Handlebars templates whenever the response can be expressed as “request data + light helper usage”. This keeps mocks transparent and editable by a wide audience.
  • Move logic into Response builders and transformers when you need non-trivial algorithms, external data sources, or complex JSON manipulation that would make templates hard to read.

A lot of teams settle on a hybrid: templates for the bulk of the response, plus a few narrow custom transformers for cross-cutting concerns (IDs, timestamps, test data injection). That balance usually gives you both readability and power.

WireMock Matchers in Practice: From Strings to JSON Schemas

WireMock’s matching model is built around a small set of pattern types that you reuse everywhere: URLs, headers, query parameters, and bodies all use variations of the same abstractions. In this article we’ll build an intuition for those abstractions and then see them in a Java example.


The Core Abstractions: ContentPattern and StringValuePattern

At the heart of WireMock matching is the idea “given some content, decide how well it matches this expectation.” That idea is embodied in ContentPattern and its concrete subclasses.

  • ContentPattern is the conceptual base: “match content of type T and produce a MatchResult.”
  • StringValuePattern is the concrete type for matching strings and underpins almost every simple matcher you use in the DSL.

You rarely construct StringValuePattern directly; you use the DSL factory methods that create it for you. These include:

  • equalTo("foo") – exact match.
  • containing("foo") – substring.
  • matching("[A-Z]+") – regular expression.
  • equalToJson("{...}"), equalToXml("<root/>") – structural equality for JSON/XML.

Rationale: by funnelling everything through StringValuePattern, WireMock can reuse the same matching semantics across URLs, headers, query params, and body fragments, and can assign a distance score that lets it pick the best matching stub.

Mini example (headers and query):

stubFor(get(urlPathEqualTo("/search"))
    .withQueryParam("q", equalTo("WireMock"))
    .withHeader("Accept", containing("json"))
    .willReturn(okJson("""{ "result": "ok" }"""))
);

Both equalTo("WireMock") and containing("json") are StringValuePattern instances internally.


MultiValuePattern: Matching Lists of Values

Headers and query parameters can have multiple values; sometimes you care about the set of values as a whole, not just any one of them. That’s what MultiValuePattern is for.

Conceptually, MultiValuePattern answers: “does this list of strings satisfy my condition?” Typical usage for query parameters:

stubFor(get(urlPathEqualTo("/items"))
    .withQueryParam("tag", havingExactly("featured", "sale"))
    .willReturn(ok())
);

Here:

  • havingExactly("featured", "sale") expresses that the parameter tag must have exactly those two values and no others.
  • MultiValuePattern is the pattern over that list, analogous to how StringValuePattern is a pattern over a single string.

Rationale: this is important for APIs where order and multiplicity of query or header values matter (e.g. “all of these scopes must be present”). Without a dedicated multi-value abstraction you would be forced to encode such logic in brittle string hacks.


UrlPathPattern and URL-Level Matching

WireMock lets you match URLs at several levels in the Java DSL: exact string, regex, or path-only vs path+query. UrlPathPattern corresponds to urlPathMatching(...) when you use the Java client.

Key URL matchers in Java:

  • urlEqualTo("/path?query=...") – exact match on full path (with query).
  • urlMatching("regex") – regex on full path (with query).
  • urlPathEqualTo("/path") – exact match on path only.
  • urlPathMatching("regex") – regex on path only.
stubFor(get(urlPathMatching("/users/([A-Za-z0-9_-]+)/repos"))
    .willReturn(aResponse()
        .withStatus(200)));

In modern WireMock 3 you’ll also see templates like urlPathTemplate("/contacts/{contactId}"), which are easier to read and type-safe at the path level:

stubFor(get(urlPathTemplate("/contacts/{contactId}"))
    .willReturn(aResponse()
        .withStatus(200)));

Rationale: splitting “path” from “query” and using distinct matchers lets you:

  • Use exact matches (urlEqualTo, urlPathEqualTo) when you want deterministic behaviour (faster, simpler).
  • Only fall back to regex (urlMatching, urlPathMatching) when you truly need flexible matching, such as versioned paths or dynamic IDs.

MatchesJsonPathPattern: Querying Inside JSON Bodies

When the request body is JSON and you care about conditions inside it rather than full equality, WireMock uses MatchesJsonPathPattern. You access it via matchingJsonPath(...) in the DSL.

Two main flavours:

  • matchingJsonPath("$.message") – body must match the JSONPath (i.e. the expression finds at least one node).
  • matchingJsonPath("$.message", equalTo("Hello")) – evaluates JSONPath, converts the result to a string, then matches it with a StringValuePattern.

Example:

stubFor(post(urlEqualTo("/api/message"))
    .withRequestBody(matchingJsonPath("$.message", equalTo("Hello World!")))
    .willReturn(aResponse().withStatus(200))
);

Important detail: all WireMock matchers operate on strings, so the JSONPath result is stringified before applying the StringValuePattern. This even allows selecting a sub-document and then matching it with equalToJson(...).

Rationale: JSONPath is ideal when you need to assert that some field exists or meets a condition (e.g. price > 10) without tightly coupling your tests to the entire JSON shape. It makes tests robust to benign changes in unrelated fields.


JsonUnit Placeholders in equalToJson

When you do want structural equality for JSON, equalToJson is your tool, and it is powered by JsonUnit. JsonUnit supports placeholders, which WireMock exposes to let you relax parts of the JSON equality check.

You embed placeholders directly into the expected JSON:

stubFor(post(urlEqualTo("/orders"))
    .withRequestBody(equalToJson("""
        {
          "id": "${json-unit.any-string}",
          "amount": 123.45,
          "status": "NEW",
          "metadata": "${json-unit.ignore}"
        }
        """))
    .willReturn(ok())
);

Common placeholders:

  • ${json-unit.ignore} – ignore value and type, just require key presence.
  • ${json-unit.any-string} – value can be any string.
  • ${json-unit.any-number} – any number.
  • ${json-unit.any-boolean} – any boolean.
  • ${json-unit.regex}[A-Z]+ – value must match this regex.

You can also change delimiters if ${ and } clash with your payload conventions.

Rationale: equalToJson is excellent for enforcing payload shape and fixed fields, but brittle when some fields are inherently variable (IDs, timestamps, correlation IDs). JsonUnit placeholders let you keep strictness where it matters and loosen it where variability is expected.


JSON Schema Matching

For more formal validation you can use JSON Schema with matchers like matchingJsonSchema. This checks that the JSON body (or a path variable) conforms to an explicit schema: types, required fields, min/max, etc.

Typical body matcher:

stubFor(post(urlEqualTo("/things"))
    .withRequestBody(
        matchingJsonSchema("""
            {
              "$schema": "http://json-schema.org/draft-07/schema#",
              "type": "object",
              "required": ["id", "name"],
              "properties": {
                "id":   { "type": "string", "minLength": 2 },
                "name": { "type": "string" },
                "price": { "type": "number", "minimum": 0 }
              },
              "additionalProperties": false
            }
            """)
    )
    .willReturn(created())
);

You can also apply matchingJsonSchema to path parameters when you use path templates, e.g. constraining userId to a specific shape.

Rationale:

  • JsonPath answers “does there exist an element meeting this condition?”
  • equalToJson + JsonUnit placeholders answers “is this JSON equal to a template with some flexible parts?”
  • JSON Schema answers “does this JSON globally satisfy a formal contract?”.

JSON Schema is especially useful when your WireMock stub is acting as a consumer-driven contract test for another service.


Custom Matcher: Escaping the Built-in Model

When WireMock’s built-in matchers are not enough, you can implement Custom Matchers. The main extension is RequestMatcherExtension, which gives you full control over how a request is matched.

The minimal shape:

import com.github.tomakehurst.wiremock.extension.Parameters;
import com.github.tomakehurst.wiremock.extension.requestfilter.RequestMatcherExtension;
import com.github.tomakehurst.wiremock.http.MatchResult;
import com.github.tomakehurst.wiremock.http.Request;

public class BodyLengthMatcher extends RequestMatcherExtension {

    @Override
    public String getName() {
        return "body-too-long";
    }

    @Override
    public MatchResult match(Request request, Parameters parameters) {
        int maxLength = parameters.getInt("maxLength");
        boolean tooLong = request.getBody().length > maxLength;
        return MatchResult.of(tooLong);
    }
}

You then reference it by name in your stub:

stubFor(requestMatching("body-too-long", Parameters.one("maxLength", 2048))
    .willReturn(aResponse().withStatus(422))
);

There is also a lower‑level ValueMatcher<T> interface that you can use in verification or in some advanced APIs to match arbitrary values, without registering a global extension.

Rationale: custom matchers are your “escape hatch” for domain‑specific logic which would otherwise be contorted into regexes or JSONPath. Examples include:

  • Checking that a JWT in a header is valid and contains certain claims.
  • Ensuring consistency between header and body fields.

Putting It All Together

Below is a Java 17 style example which combines several of the discussed matcher types. You can drop this into a plain Maven/Gradle project that has wiremock dependencies;

Example: Product API Stub with Multiple Matchers

import com.github.tomakehurst.wiremock.WireMockServer;

import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;

public class ProductApiStub {

    public static void main(String[] args) {
        WireMockServer server = new WireMockServer(
            wireMockConfig().port(8080)
        );
        server.start();

        configureFor("localhost", 8080);
        registerStubs(server);

        Runtime.getRuntime().addShutdownHook(new Thread(server::stop));
        System.out.println("WireMock running at http://localhost:8080");
    }

    private static void registerStubs(WireMockServer server) {
        server.stubFor(post(urlPathTemplate("/shops/{shopId}/products"))
            // UrlPathTemplate + JSON Schema for path parameter
            .withPathParam("shopId", matchingJsonSchema("""
                {
                  "type": "string",
                  "minLength": 3,
                  "maxLength": 10
                }
                """))

            // MultiValuePattern on query param
            .withQueryParam("tag", havingExactly("featured", "sale"))

            // StringValuePattern: header must match regex
            .withHeader("X-Request-Id", matching("req-[0-9a-f]{8}"))

            // JsonUnit placeholders in equalToJson
            .withRequestBody(equalToJson("""
                {
                  "id": "${json-unit.any-string}",
                  "name": "Socks",
                  "price": 9.99,
                  "metadata": "${json-unit.ignore}"
                }
                """))

            // MatchesJsonPathPattern + StringValuePattern
            .withRequestBody(matchingJsonPath("$[?(@.price > 0)]"))

            .willReturn(okJson("""
                {
                  "status": "ACCEPTED",
                  "message": "Product created"
                }
                """)));
    }
}

What this stub requires:

  • Path: /shops/{shopId}/products where shopId is a string 3–10 chars long (validated via JSON Schema).
  • Query: ?tag=featured&tag=sale and only those values (MultiValuePattern).
  • Header: X-Request-Id must match req-[0-9a-f]{8} (regex StringValuePattern).
  • Body: JSON with fixed fields name and price, but any string for id and anything for metadata, thanks to JsonUnit placeholders.
  • Body: JSONPath asserts that $.price is strictly greater than 0.

How to Validate the Example

Using any HTTP client (curl, HTTPie, Postman, Java HTTP client), send:

curl -i \
  -X POST "http://localhost:8080/shops/abc/products?tag=featured&tag=sale" \
  -H "Content-Type: application/json" \
  -H "X-Request-Id: req-deadbeef" \
  -d '{
        "id": "any-random-id",
        "name": "Socks",
        "price": 9.99,
        "metadata": { "color": "blue" }
      }'

You should receive a 200 OK with JSON body:

{
  "status": "ACCEPTED",
  "message": "Product created"
}

If you break one constraint at a time (e.g. price negative, missing tag value, bad X-Request-Id format, too-short shopId), the request will no longer match that stub, and you will either hit no stub or some other fallback stub, which is precisely how you verify each matcher behaves as expected.

WireMock Java Stubbing: From Configuration to StubMapping

In this article we will walk through the main Java concepts behind WireMock: how you configure the server, choose a port, describe requests and responses, and how everything ends up as a StubMapping. The goal is that you not only know how to use the API, but also why it is structured this way, the way an experienced engineer would reason about test doubles.


Configuring WireMockServer with WireMockConfiguration

WireMockConfiguration is the object that describes how your WireMock HTTP server should run. You rarely construct it directly; instead you use a static factory called options(), which returns a configuration builder.

At a high level:

  • WireMockConfiguration controls ports, HTTPS, file locations, extensions, and more.
  • The fluent style (via options()) encourages explicit, readable configuration instead of magic defaults scattered through the codebase.
  • Because it is a separate object from WireMockServer, you can reuse or tweak configuration for different test scenarios.

Example shape (without imports for now):

WireMockServer wireMockServer = new WireMockServer(
    options()
        .dynamicPort()
        .dynamicHttpsPort()
);

You pass the built configuration into the WireMockServer constructor, which then uses it to bind sockets, set up handlers, and so on. Conceptually, think of WireMockConfiguration as the blueprint; WireMockServer is the running building.


Dynamic Port: Why and How

In test environments, hard‑coding ports (e.g. 8080, 9090) is a common source of flakiness. If two tests (or two services) try to use the same port, one will fail with “address already in use.”

WireMock addresses this with dynamicPort():

  • dynamicPort() tells WireMock to pick any free TCP port available on the machine.
  • After the server starts, you ask the server which port it actually bound to, via wireMockServer.port().
  • This pattern is ideal for parallel test runs and CI environments, where port availability is unpredictable.

Example pattern:

WireMockServer wireMockServer = new WireMockServer(
    options().dynamicPort()
);

wireMockServer.start();

int port = wireMockServer.port(); // the chosen port at runtime
String baseUrl = "http://localhost:" + port;

You then configure your HTTP client (or the service under test) to call baseUrl, not a hard‑coded port. The rationale is to shift from “global fixed port” to “locally discovered port,” which removes an entire class of brittle test failures.


Creating a Stub: The Big Picture

When we say “create a stub” in WireMock, we mean:

Define a mapping from a request description to a response description, and register it with the server so that runtime HTTP calls are intercepted according to that mapping.

This mapping is built in three conceptual layers:

  • A request pattern (what should be matched).
  • A response definition (what should be returned).
  • A stub mapping that joins these two together and gives it identity and lifecycle inside WireMock.

In Java, the fluent DSL exposes this as:

wireMockServer.stubFor(
    get(urlEqualTo("/api/message"))
        .willReturn(
            aResponse()
                .withStatus(200)
                .withHeader("Content-Type", "text/plain")
                .withBody("hello-wiremock")
        )
);

This one line of code hides several objects: a MappingBuilder, a RequestPattern, a ResponseDefinition, and eventually a StubMapping. The design encourages a declarative style: you describe what should happen, not how to dispatch it.


MappingBuilder: Fluent Construction of a Stub

MappingBuilder is the central builder used by the Java DSL. Calls like get(urlEqualTo("/foo")) or post(urlPathMatching("/orders/.*")) return a MappingBuilder instance.

It is responsible for:

  • Capturing the HTTP method (GET, POST, etc.).
  • Associating a URL matcher (exact equality, regex, path, etc.).
  • Enriching with conditions on headers, query parameters, cookies, and body content.
  • Attaching a response definition via willReturn.

You rarely instantiate MappingBuilder yourself. Instead you use static helpers from the DSL:

get(urlEqualTo("/api/message"))
post(urlPathEqualTo("/orders"))
put(urlMatching("/v1/users/[0-9]+"))

Each of these returns a MappingBuilder, and you chain further methods to refine the match. The rationale is to keep your test code highly readable while still configuring quite a lot of matching logic.


RequestPattern: Describing the Request Shape

Under the hood, MappingBuilder gradually accumulates a RequestPattern (or more precisely, builds a RequestPatternBuilder). A RequestPattern is an object representation of “what an incoming HTTP request must look like for this stub to apply.”

A RequestPattern may include:

  • HTTP method (e.g. GET).
  • URL matcher: urlEqualTo, urlPathEqualTo, regex matchers, etc.
  • Optional header conditions: withHeader("X-Env", equalTo("test")).
  • Optional query param or cookie matchers.
  • Optional body matchers: raw equality, regex, JSONPath, XPath, and so on.

Example via DSL:

post(urlPathEqualTo("/orders"))
    .withHeader("X-Tenant", equalTo("test"))
    .withQueryParam("source", equalTo("mobile"))
    .withRequestBody(matchingJsonPath("$.items[0].id"));

Each of these DSL calls contributes to the underlying RequestPattern. The motivation for this design is to let you express complex request matching without writing imperative “if header equals X and URL contains Y” code; WireMock handles that logic internally.


ResponseDefinition and aResponse: Describing the Response

If RequestPattern says “what we expect to receive,” then ResponseDefinition says “what we will send back.” It captures all aspects of the stubbed response:

  • Status code and optional status message.
  • Headers (e.g., content type, custom headers).
  • Body content (string, JSON, binary, templated content).
  • Optional behaviour like artificial delays or faults.

The idiomatic way to construct a ResponseDefinition in Java is via the aResponse() factory, which returns a ResponseDefinitionBuilder:

aResponse()
    .withStatus(201)
    .withHeader("Content-Type", "application/json")
    .withBody("{\"id\":123}");

Using a builder for responses has several benefits:

  • It separates pure data (status, headers, body) from the network I/O, so you can reason about responses as values.
  • It encourages small, focused stubs rather than ad‑hoc code that manipulates sockets or streams.
  • It allows extensions and transformers to hook into a well‑defined structure.

Once built, this response definition is attached to a mapping via willReturn.


willReturn: Connecting Request and Response

The willReturn method lives on MappingBuilder and takes a ResponseDefinitionBuilder (typically produced by aResponse()).

Conceptually:

  • Before willReturn, you are only describing the request side.
  • After willReturn, you have a complete “if request matches X, then respond with Y” mapping.
  • The resulting MappingBuilder can be passed to stubFor, which finally registers it with the server.

Example:

get(urlEqualTo("/api/message"))
    .willReturn(
        aResponse()
            .withStatus(200)
            .withBody("hello-wiremock")
    );

The wording is deliberate. The DSL reads like: “GET /api/message willReturn this response.” This is a very intentional choice to make tests self‑documenting and easy to skim.


StubMapping: The Persisted Stub Definition

Once you call stubFor(mappingBuilder), WireMock converts the builder into a concrete StubMapping instance. This is the in‑memory (and optionally JSON‑on‑disk) representation of your stub.

A StubMapping includes:

  • The RequestPattern (what to match).
  • The ResponseDefinition (what to send).
  • Metadata: UUID, name, priority, scenario state, and other advanced properties.

StubMapping is what WireMock uses at runtime to:

  • Evaluate incoming requests against all known stubs.
  • Decide which stub wins (based on priority rules).
  • Produce the actual HTTP response that the client receives.

From an architectural perspective, StubMapping lets WireMock treat stubs as data. That is why you can:

  • Export stubs as JSON.
  • Import them via admin endpoints.
  • Manipulate them dynamically without recompiling or restarting your tests.

WireMock Class: The Fluent DSL Entry Point

The WireMock class is the static gateway to the Java DSL. It provides methods used throughout examples:

  • Request builders: get(), post(), put(), delete(), any().
  • URL matchers: urlEqualTo(), urlPathEqualTo(), regex variants.
  • Response builders: aResponse(), plus convenience methods like ok(), badRequest(), etc.
  • Utility methods to bind the static DSL to a specific server (configureFor(host, port)).

In tests you typically import its static methods:

import static com.github.tomakehurst.wiremock.client.WireMock.*;

This is what enables code such as:

get(urlEqualTo("/api/message"))
    .willReturn(aResponse().withStatus(200));

instead of more verbose, object‑oriented calls. The goal is to minimize ceremony and make test intent immediately obvious.


A Simple Example

Let’s now put all these pieces together in a small JUnit 5 test using:

  • Java 11+ HttpClient.
  • WireMockServer with dynamicPort().
  • A single stub built with the core DSL concepts we have discussed.

This example intentionally avoids any build or dependency configuration, focusing only on the Java code.

import com.github.tomakehurst.wiremock.WireMockServer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
import static org.junit.jupiter.api.Assertions.assertEquals;

class WireMockExampleTest {

    private WireMockServer wireMockServer;

    @BeforeEach
    void startServer() {
        // Configure WireMock with a dynamic port to avoid clashes.
        wireMockServer = new WireMockServer(
                options().dynamicPort()
        );
        wireMockServer.start();

        // Bind the static DSL to this server instance.
        configureFor("localhost", wireMockServer.port());
    }

    @AfterEach
    void stopServer() {
        wireMockServer.stop();
    }

    @Test
    void shouldReturnStubbedMessage() throws Exception {
        // Create a stub (MappingBuilder -> RequestPattern + ResponseDefinition)
        wireMockServer.stubFor(
                get(urlEqualTo("/api/message"))
                        .willReturn(
                                aResponse()
                                        .withStatus(200)
                                        .withHeader("Content-Type", "text/plain")
                                        .withBody("hello-wiremock")
                        )
        );

        // Build an HTTP client and request using the dynamic port.
        HttpResponse<String> response;
        try (HttpClient client = HttpClient.newHttpClient()) {
            String baseUrl = "http://localhost:" + wireMockServer.port();

            HttpRequest request = HttpRequest.newBuilder()
                    .uri(URI.create(baseUrl + "/api/message"))
                    .GET()
                    .build();

            response = client.send(request, HttpResponse.BodyHandlers.ofString());
        }

        // Validate that the stub mapping was applied correctly.
        assertEquals(200, response.statusCode());
        assertEquals("hello-wiremock", response.body());
    }
}

How to validate this example

To validate the example:

  • Ensure you have WireMock and JUnit 5 in your project dependencies (via Maven, Gradle, or your build tool of choice).
  • Run the test class.
  • The test passes if:
    • The WireMockServer starts on a dynamic port without conflicts.
    • The request to /api/message is matched by the RequestPattern defined in the MappingBuilder.
    • The ResponseDefinition created with aResponse() and attached via willReturn produces the expected status and body.