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.fooorrequest.query.foo.[n]for multi-valued params.request.headers.X-Request-Id,request.cookies.session,request.method,request.baseUrl.request.body,request.bodyAsBase64, and multipartrequest.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-templatetransformer. - 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:
nowwith format, timezone, and offsets;parseDate,truncateDatefor consistent timestamps. - Random:
randomValue,randomInt,pickRandomfor synthetic but realistic data. - JSON:
jsonPath,parseJson,toJson,formatJson,jsonArrayAdd,jsonMerge,jsonRemove,jsonSortfor 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(viaaResponse()in stubs) at the configuration level.Responseand itsBuilderwhen writing extensions likeResponseTransformerV2.
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=aliceand assert you getHello, ALICE!. - Hit
/greetwith nonameand assert you getHello, 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.
Recent Comments