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.