Browse the Knowledge Hub211 resources

WireMock review: tutorial, pros and cons
WireMock is an open-source tool for mocking HTTP APIs. It runs inside a JVM test, as a standalone JAR or as a Docker container, and serves stubbed responses defined in Java or JSON. It matches requests in detail, records traffic from real services, simulates state, and injects delays and broken connections, so a team can test how its code behaves when a dependency misbehaves.
QAble runs this tool on client engagements. The assessment below is from that experience.
- last reviewed
- 13 October 2026
- version covered
- WireMock 3.13
- licence
- Open source
At a glance
WireMock at a glance
- Vendor
- WireMock project
- Category
- Mocking & virtualisation
- Licence
- Open source (Apache 2.0)
- Platforms
- JVM, Docker, Standalone
- Languages
- Java, JSON
- Integrates with
- JUnit, Spring, Testcontainers, Docker
- First released
- 2011
- Version reviewed
- WireMock 3.13
Verdict
Our verdict on WireMock
Strengths
- Fault injection is built in: fixed or random delays, dribbled bodies, empty responses and reset connections
- One stub format: JSON mappings load in JUnit, the standalone JAR and the Docker image alike
- The JUnit 5 extension starts a server per test class on a random port and resets stubs between tests
- Near-miss reports show the closest stub when a request fails to match, which shortens debugging a lot
- Record and playback through a proxy turns real traffic into editable stubs in minutes
- Free under Apache 2.0 with no feature tiers; WireMock Cloud is an optional hosted product
Weaknesses
- Stubs drift from the real API silently; nothing verifies them against the provider
- Unmatched requests return a plain 404 by default, so a missing stub can look like an application bug
- HTTP only in 3.x: gRPC and GraphQL need extensions, WebSockets need the 4.x beta
- Large recorded stub sets become hard to read and expensive to keep current
Best for
Testing how a service or front end copes with a dependency that is slow, wrong or down. A fixed delay, a log-normal latency tail, a dribbled body or a reset connection is one line of stub definition, and those are exactly the conditions a vendor sandbox will not produce on demand. It is also the most practical way we know to give a JVM test suite a fast, deterministic stand-in for a third-party API, and the same JSON stubs then run unchanged in Docker for everyone else.
Not for
Proving that two services actually agree. A mock is a copy of a contract, and nothing in WireMock checks that copy against the real provider, so a suite that only ever talks to WireMock passes happily after the provider changes a field name. Use Pact or a scheduled run against the real service for that. It is also the wrong tool for protocols beyond HTTP: gRPC and GraphQL need extensions, and WebSocket mocking exists only in the 4.x beta.
Setup cost
Low. One Maven or Gradle dependency and the @WireMockTest annotation give each test class its own server on a random port; outside the JVM, a single docker run with a mounted mappings folder does the same job. The real setup work is making the application under test take its base URL from configuration, so tests can point it at WireMock, and agreeing where shared JSON stubs live.
Maintenance burden
Moderate, and it grows with the stub set. Recorded stubs are verbose and capture whatever the real service returned on the day, including volatile headers. Every API change on the provider side means a stub change on yours, and nothing tells you it is needed. Keep stubs few and hand-tidied, pair them with contract tests or a periodic re-record, and turn on failure for unmatched requests so missing stubs surface immediately.
Honest limitations
HTTP only in the stable release. Matching rules can become as complex as the service you are replacing, and at that point the mock is a second implementation to maintain. Stateful scenarios are simple state machines, not a database. Under load, the request journal and the default ten container threads become bottlenecks unless you configure them, and the Connection Reset fault is unreliable on Windows, where the docs say it tends to hang instead.
How it works
What is WireMock and how does it work?
A real HTTP server, not an in-process fake
WireMock starts a Jetty web server that listens on a real port. Your code under test makes genuine HTTP calls to it, through its normal client, connection pool, serialisation and timeout settings. That is the important difference from mocking a client class with Mockito: the whole outbound path is exercised, so a misconfigured timeout, a wrong header or a JSON mapping bug shows up in the test. The same server runs embedded in a JUnit test, as a standalone JAR or in the official Docker image, and in every mode it is controlled through the same admin API under /__admin, which is what the Java DSL calls behind the scenes.
Stub mappings and how a request is matched
A stub mapping pairs a request pattern with a response definition. The pattern can match method, URL, path template, query parameters, headers, cookies, basic auth and body, using equality, regular expressions, JSONPath, XPath or equalToJson. When a request arrives, WireMock checks it against the stubs and serves the response of the match. Where several stubs match, the most recently added wins unless you set priorities, where 1 is the highest. Every request is also stored in an in-memory request journal, which is what makes verification and near-miss reports possible, and what grows without limit if the server runs for days.
Templating, state and faults on the response side
Responses can be static, served from files under __files, or rendered with Handlebars templates that read the request, so an ID in the path can be echoed back in the body. When WireMock is started from Java, templating is on in local mode, applied only to stubs that list the response-template transformer; standalone flags choose local or global mode. Scenarios add a simple state machine: a stub can require a state and move the scenario to another, which models create-then-read flows. Faults sit beside these: fixed and random delays, chunked dribble delays, and four connection-level failures, from an empty response to a reset connection.
Recording from a real service
Set WireMock in front of a real API as a proxy, send traffic through it, and it converts what it saw into stub mappings, either by recording between a start and stop call or by snapshotting requests it has already received. The record spec controls which requests are captured, which headers become match conditions, when bodies are extracted into files, and whether repeated requests become scenarios. Recording is the quickest route to a realistic stub set, and also the quickest route to a bloated one, because it captures everything literally, including volatile values such as dates and tokens.
Strengths
What it does well
Failure modes on demand
withFixedDelay, withLogNormalRandomDelay, withUniformRandomDelay and withChunkedDribbleDelay produce slow dependencies, and withFault produces EMPTY_RESPONSE, MALFORMED_RESPONSE_CHUNK, RANDOM_DATA_THEN_CLOSE or CONNECTION_RESET_BY_PEER. These are the cases that decide whether a timeout, retry or circuit breaker actually works, and they are almost impossible to provoke reliably against a real sandbox.
Verification of outbound calls
Because every request lands in the journal, a test can assert that the system made a call, how many times and with what body, using verify with a request pattern and an optional count. That turns WireMock into a spy as well as a stub, which is the only clean way to test fire-and-forget calls such as audit events or notifications.
Near-miss diagnostics
When a request matches no stub, WireMock can rank stubs by distance from it and show the closest, field by field. The admin endpoint /__admin/requests/unmatched/near-misses lists the top candidates for every unmatched request. In practice this is what turns a puzzling 404 into a two-minute fix, because the usual cause is one header, one query parameter or one character of path.
One stub format across environments
A JSON mapping file under mappings, with bodies under __files, is read identically by the JUnit extension, the standalone JAR and the Docker image mounted at /home/wiremock. Developers can run the same stubs locally in Docker that the CI tests use, and non-JVM teams can use WireMock purely as a container without touching Java.
Webhooks and callbacks
Since 3.1 webhooks are part of the core and enabled by default. A stub can fire an asynchronous outbound HTTP call after it responds, with its own URL, headers, body and delay, and the body can be templated from the original request. That lets you test flows where a payment provider or job service calls you back.
Spring Boot and Testcontainers integrations
The WireMock Spring Boot integration adds @EnableWireMock, which starts servers for a Spring test and injects their base URL into properties such as wiremock.server.baseUrl. Official Testcontainers modules for Java and Python run the Docker image from a test. Both remove the hand-written glue that usually connects a mock server to application configuration.
Pricing
WireMock pricing and licensing
WireMock itself is open source under the Apache 2.0 licence, with every feature on this page in the free project: the JUnit extension, the standalone JAR, the Docker image, recording, templating and fault injection. WireMock Inc sells WireMock Cloud, a separate hosted product with a web editor, OpenAPI import and team features, and support with an SLA comes through its Enterprise plan. The project docs also list a paid IntelliJ plugin and independent trainers. Most teams pay nothing and spend engineering time instead.
- Licence: Apache 2.0 for the core project and its official Docker image.
- No tiers in the open-source tool: no account, licence key or usage limit for any feature.
- WireMock Cloud: a commercial hosted service from WireMock Inc, priced separately; check its own pricing page if you need hosted, shared mocks.
- Support: the project states that, under Apache 2.0, it provides no support or guarantees itself; community channels or a commercial vendor fill that gap.
- Hidden cost: stub maintenance. Each provider API change needs a stub change, and recorded stubs need tidying before they are worth keeping.
Checked against the vendor’s own pages on 13 October 2026. Pricing changes; confirm on the official site before buying.
Getting started
WireMock tutorial: getting started
Setup, step by step
- Check the JVM: WireMock 3 needs Java 11 or later; the 4.x beta moves the baseline to Java 17.
- Add org.wiremock:wiremock 3.13.2 as a test dependency, alongside JUnit 5.
- Annotate a test class with @WireMockTest and take WireMockRuntimeInfo as a test method parameter.
- Point the client under test at the base URL that WireMockRuntimeInfo reports, never a fixed port.
- Stub the call with stubFor, run the code, then verify the request WireMock received.
- Move stable stubs into JSON files under src/test/resources once more than one test needs them.
Install
# Maven (test scope)
<dependency>
<groupId>org.wiremock</groupId>
<artifactId>wiremock</artifactId>
<version>3.13.2</version>
<scope>test</scope>
</dependency>
# Or run it as a container
docker run -it --rm -p 8080:8080 wiremock/wiremock:3.13.2A first example
import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
import com.github.tomakehurst.wiremock.junit5.WireMockTest;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import org.junit.jupiter.api.Test;
// Starts one server for this class on a random port; stubs reset before each test
@WireMockTest
class PaymentApiTest {
@Test
void readsAnAuthorisedPayment(WireMockRuntimeInfo wm) throws Exception {
// The static DSL is bound to this class's server automatically
stubFor(get(urlPathEqualTo("/payments/42"))
.willReturn(okJson("{\"id\":42,\"status\":\"AUTHORISED\"}")));
// In a real test, pass wm.getHttpBaseUrl() to your own API client
HttpResponse<String> response = HttpClient.newHttpClient().send(
HttpRequest.newBuilder(URI.create(wm.getHttpBaseUrl() + "/payments/42")).build(),
HttpResponse.BodyHandlers.ofString());
assertEquals(200, response.statusCode());
// The request journal makes WireMock a spy as well as a stub
verify(getRequestedFor(urlPathEqualTo("/payments/42")));
}
}What trips people up first
A stub that never matches. The test gets a 404, and because the JUnit extension does not fail on unmatched requests by default, the failure appears later as an odd error in your own code. The usual causes are urlEqualTo used where the client adds a query string, a header the client sends with different capitalisation or charset, or a JSON body compared for exact equality. Check the near misses, which show the closest stub and the field that differed, and switch to urlPathEqualTo with withQueryParam, or equalToJson with ignoreExtraElements. Then turn on failOnUnmatchedRequests so the next one fails where it happens.
In practice
Using WireMock in practice
Laying out stubs so JUnit and Docker share them
Keep two kinds of stub. Behaviour that a single test depends on, such as a failure or an edge case, belongs in that test as Java, where a reader can see it next to the assertion. Stable happy-path responses that many tests and developers need belong in JSON under src/test/resources/wiremock/mappings, with large bodies in __files beside it. Point the JUnit extension at that root with usingFilesUnderClasspath, and mount the same folder at /home/wiremock in Docker, so local runs, CI and other teams read one set of files. Use path templates rather than regular expressions where you can, because they read like the API documentation and let a template echo the path variable back.
// src/test/resources/wiremock/mappings/get-payment.json
{
"request": {
"method": "GET",
"urlPathTemplate": "/payments/{id}"
},
"response": {
"status": 200,
"headers": { "Content-Type": "application/json" },
"body": "{\"id\": \"{{request.path.id}}\", \"status\": \"AUTHORISED\"}",
"transformers": ["response-template"]
}
}A resilience suite built from faults and scenarios
The highest-value WireMock tests we write are the ones a real environment cannot produce. Give each outbound dependency a small set: a response slower than the client timeout, a connection reset, a malformed body, and a failure followed by success to prove retries work. Scenarios model the last case cleanly, because the first call moves the state on and the second call sees a different stub. Assert on what the user or caller experiences, such as a fallback or a clear error, and verify the call count so a missing retry or a retry storm is caught. Keep the delay just above the configured timeout rather than at thirty seconds, or the suite becomes slow for no extra information.
import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static com.github.tomakehurst.wiremock.stubbing.Scenario.STARTED;
import com.github.tomakehurst.wiremock.http.Fault;
// Slower than a 2 second client timeout: expect the fallback path
stubFor(get(urlPathEqualTo("/rates"))
.willReturn(okJson("{\"gbp\":1.0}").withFixedDelay(2500)));
// Connection dropped mid-response (unreliable on Windows, fine on Linux CI)
stubFor(get(urlPathEqualTo("/inventory"))
.willReturn(aResponse().withFault(Fault.CONNECTION_RESET_BY_PEER)));
// First call fails, second succeeds: proves the retry policy
stubFor(post(urlPathEqualTo("/orders")).inScenario("retry")
.whenScenarioStateIs(STARTED)
.willReturn(serviceUnavailable())
.willSetStateTo("recovered"));
stubFor(post(urlPathEqualTo("/orders")).inScenario("retry")
.whenScenarioStateIs("recovered")
.willReturn(okJson("{\"orderId\":\"A-1\"}")));
// After exercising the client:
verify(exactly(2), postRequestedFor(urlPathEqualTo("/orders")));Running WireMock in GitHub Actions
For JVM projects nothing extra is needed: the JUnit extension starts WireMock inside the build. For a front end or a non-JVM service, run the Docker image as a step after checkout rather than as a job service container, because service containers start before the repository is checked out and so cannot mount your mappings. Wait on the /__admin/health endpoint before the tests start, and on failure print the near misses, which usually explain the failure without a re-run. Pin the image version: a floating latest tag will move to 4.x when it leaves beta, and 4.x changes Java baselines and some behaviour.
# .github/workflows/integration.yml
name: integration
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Start WireMock with the repository's stubs
run: |
docker run -d --name wiremock -p 8080:8080 \
-v "$PWD/src/test/resources/wiremock:/home/wiremock" \
wiremock/wiremock:3.13.2 --local-response-templating
curl --retry 20 --retry-delay 1 --retry-all-errors -sf \
http://localhost:8080/__admin/health
- uses: actions/setup-node@v7
with:
node-version: 24
cache: npm
- run: npm ci
- run: npm test
env:
PAYMENTS_API_URL: http://localhost:8080
- name: Show unmatched requests and their closest stubs
if: failure()
run: curl -s http://localhost:8080/__admin/requests/unmatched/near-missesRecording a real API without keeping the noise
Recording is how we bootstrap stubs for a third-party API, but we never commit the raw output. Start the recorder against the sandbox, drive the journeys you need, stop, and then edit: delete stubs nobody asserts on, replace volatile values such as timestamps, IDs and tokens with templates or matchers, and remove headers like Date and Set-Cookie that only make diffs noisy. Restrict capture with the record spec filters to the API path you care about, and capture only the request headers that genuinely change the response. Scrub anything sensitive before the files reach the repository, since recorded bodies contain whatever the sandbox returned. A stub set of twenty readable files beats two hundred recorded ones.
Keeping stubs honest over time
Every stub is an assumption about another team’s API, and those assumptions age. Three habits keep them useful. First, run a small set of the same journeys against the real sandbox on a schedule, so drift shows up as a failing nightly job rather than a production incident. Second, where you own both sides, put a Pact contract between them, so the provider’s build fails when it breaks what your stubs assume. Third, re-record periodically and diff the result against the committed stubs. For performance testing, configure the server for it: add --async-response-enabled when using delays, raise --container-threads above the default of 10, and use --no-request-journal so a long run cannot exhaust the heap.
Pitfalls
Common pitfalls
Missing stubs that look like application bugs
Why it happens: An unmatched request gets an HTML 404, and the JUnit 5 extension’s failOnUnmatchedRequests option defaults to false, so the test fails later and somewhere else, or not at all if the code swallows errors.
The fix: Build the extension with failOnUnmatchedRequests(true), or check /__admin/requests/unmatched in an afterEach. Read the near misses before changing any code.
urlEqualTo breaking when query parameters change order
Why it happens: url and urlEqualTo match the path and the query string together as one value, so an extra parameter or a different order from the client is a different URL.
The fix: Match urlPathEqualTo or urlPathTemplate and add withQueryParam for each parameter that matters. The docs recommend path matching for order-independent query matching.
A catch-all stub swallowing specific ones
Why it happens: When several stubs match, the most recently added wins. A broad regex stub loaded after a specific one, for example from a later JSON file, silently takes over.
The fix: Give catch-all and default stubs a low priority such as 10 and specific stubs priority 1, so the outcome no longer depends on load order.
Port clashes and cross-talk in parallel test runs
Why it happens: Tests written against a fixed port such as 8080 collide when JUnit runs classes in parallel or when two builds share an agent, and stubs from one test can answer another.
The fix: Leave @WireMockTest on its default random port and inject the base URL from WireMockRuntimeInfo into the client configuration. Use static extension fields for one server per class.
WireMock becoming the bottleneck in a load test
Why it happens: The request journal keeps every request in memory, synchronous delays hold a container thread each, and there are 10 container threads by default, so the mock saturates before the system under test.
The fix: Run standalone with --no-request-journal or --max-request-journal-entries, --async-response-enabled, and more --container-threads. Give it its own machine or container resources.
Following WireMock 2 tutorials
Why it happens: Older articles use the com.github.tomakehurst coordinates, the wiremock-jre8 artifact, Java 8 and the webhooks extension as a separate download. Those coordinates stopped at 3.0.1, and 3.x needs Java 11.
The fix: Use org.wiremock:wiremock or wiremock-standalone at the current 3.x version, Java 11 or later, and the built-in webhooks. Package names in imports did not change in 3.x.
Compared
How WireMock compares
| Tool | Protocols | Runs as | Stub definition | Checks the real provider | Licence |
|---|---|---|---|---|---|
| WireMock | HTTP(S); gRPC and GraphQL via extensions | JVM library, standalone JAR, Docker | Java DSL or JSON files | No | Apache 2.0 |
| Mountebank | HTTP, HTTPS, TCP, SMTP and more | Node.js process | JSON imposters over a REST API | No | MIT |
| Mockoon | HTTP(S) REST APIs | Desktop app, CLI, Docker image | Desktop editor or JSON environment | No | MIT, plus paid Mockoon Pro |
| Pact | HTTP and messages; plugins for more | Library inside your unit tests | Generated from consumer tests | Yes, provider verification | MIT; Pact JVM is Apache 2.0 |
FAQ
Frequently asked questions
Is WireMock free?
Yes. WireMock is open source under the Apache 2.0 licence, and the JUnit extension, standalone JAR, Docker image, recording, templating and fault injection are all free with no account or limits. WireMock Inc sells a separate hosted product, WireMock Cloud, and enterprise support through it, but you do not need either to use WireMock in tests or CI.
What is the difference between WireMock and Mockito?
Mockito replaces a Java object inside the process, so the HTTP client, serialisation and network settings are never exercised. WireMock is a real HTTP server, so your code makes genuine calls through its real client. Use Mockito for unit tests of logic, and WireMock when you need to know that the integration code itself, including timeouts, headers and JSON mapping, behaves correctly.
Should I use WireMock or Pact?
They answer different questions and work well together. WireMock gives your tests a controllable stand-in for a dependency, including failures that are hard to produce for real. Pact checks that consumer and provider still agree, by verifying the consumer’s expectations against the real provider in its own build. If you own both services, use Pact to keep the contract honest and WireMock for failure and edge-case testing.
Can WireMock be used without Java?
Yes. Run the official Docker image or the standalone JAR, define stubs as JSON files or through the admin REST API, and point any application at it. Teams working in JavaScript, Python, Go or .NET use it this way, and there are community clients and an official Testcontainers module for Python. WireMock.Net is a separate .NET implementation with its own project.
Is WireMock 4 ready to use?
Not for production test suites yet. As of 13 October 2026 the 4.x line is in beta, at 4.0.0-beta.40, and the project warns that there will be breaking changes. It requires Java 17, ships Jetty 12.1 only, splits the artifact into wiremock-core, wiremock-jetty and others, and makes classes such as StubMapping immutable, so code that called setRequest must use transform instead. Stay on 3.13 for now.
Can WireMock mock gRPC, GraphQL or WebSockets?
gRPC and GraphQL, yes, through extensions. WireMock’s own gRPC extension reads descriptor files generated from your .proto files and lets ordinary JSON stubs answer gRPC calls, and a community GraphQL extension documented on wiremock.org matches queries by meaning rather than text. WebSocket mocking is built on a new message-based framework that is available only in the 4.x beta. For other protocols, such as raw TCP or SMTP, a multi-protocol tool such as Mountebank is the better fit.
How do I simulate a slow or failing API with WireMock?
Add a delay or fault to the stub’s response. withFixedDelay holds the response for a set number of milliseconds, withLogNormalRandomDelay samples a realistic long tail, and withChunkedDribbleDelay sends the body slowly in pieces. withFault returns an empty response, garbage data or a reset connection. The same options exist in JSON as fixedDelayMilliseconds, delayDistribution, chunkedDribbleDelay and fault.
Alternatives
WireMock alternatives worth weighing
Mountebank
Multi-protocol service virtualisation covering HTTP, HTTPS, TCP and SMTP from one tool.
Mockoon
Desktop application for creating mock REST APIs in seconds, with a CLI for pipeline use.
Pact
Consumer-driven contract testing, letting services verify integration without being deployed together.
Resources
Where to go next
Keep going
Prepare, or get it done
Picking the wrong tool costs more than picking late.
QAble advises on toolchain decisions and then implements them, with engineers who have run these tools on real delivery. Start with a free QA audit.