Home Java WireMock: Mock HTTP Services for Spring REST Testing
Intermediate 3 min · July 14, 2026

WireMock: Mock HTTP Services for Spring REST Testing

Learn to use WireMock for mocking external HTTP services in Spring Boot tests.

N
Naren Founder & Principal Engineer

20+ years shipping production Java in banking & fintech. Lessons pulled from things that broke in production.

Follow
Production
production tested
July 15, 2026
last updated
2,398
articles · all by Naren
Before you start⏱ 15-20 min read
  • Basic knowledge of Spring Boot testing (JUnit, TestRestTemplate or WebTestClient)
  • Familiarity with REST APIs and HTTP methods
  • Java 8+ and Maven/Gradle
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • WireMock is a library for stubbing and mocking HTTP services in tests.
  • It runs as a standalone server or embedded in JUnit tests.
  • Use it to simulate external APIs, test error handling, and verify request/response contracts.
  • Key features: request matching, response templating, record/playback, and fault injection.
✦ Definition~90s read
What is Introduction to WireMock?

WireMock is a flexible library for stubbing and mocking HTTP services, enabling you to simulate external APIs in your Spring Boot tests.

Imagine you're testing a food delivery app that needs to check if a restaurant is open.
Plain-English First

Imagine you're testing a food delivery app that needs to check if a restaurant is open. Instead of calling the real restaurant API (which might be down or slow), you use WireMock to pretend you're the restaurant and always respond instantly. This lets you test your app's behavior without depending on external services.

Every Spring Boot service I've worked on eventually needs to talk to an external API. Whether it's a payment gateway, a third-party identity provider, or a weather service, your code's correctness depends on how it handles those HTTP calls. And that's exactly where most integration tests fall short.

I remember a fintech startup I consulted for: their CI pipeline was a nightmare. Tests would fail randomly because the test Stripe account would occasionally return 429 rate limits or 500 errors. The team spent hours blaming each other's code, only to realize the external API was the culprit. That's when I introduced WireMock.

WireMock lets you stub HTTP endpoints, simulate delays, inject faults, and even verify that your code sent the right request. It's not just about making tests pass — it's about making them deterministic. In this article, I'll show you how to integrate WireMock with Spring Boot tests, what the official docs gloss over, and how to debug it when things go sideways.

Setting Up WireMock in Spring Boot

Let's start with the basics. The fastest way to get WireMock into your Spring Boot test is via the spring-cloud-contract-wiremock starter, but I prefer the standalone WireMock library for more control. Here's my go-to setup:

``xml <dependency> <groupId>com.github.tomakehurst</groupId> <artifactId>wiremock-jre8</artifactId> <version>2.35.0</version> <scope>test</scope> </dependency> ``

Then in your test, use @WireMockTest to automatically start a WireMock server on a random port. Combine it with Spring's @SpringBootTest and @TestRestTemplate for a full integration test.

Here's a typical example: we have a service that calls an external API to get exchange rates. We'll stub that API with WireMock.

ExchangeRateServiceTest.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@WireMockTest(httpPort = 8089)
class ExchangeRateServiceTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    void shouldReturnConvertedAmount() {
        // Stub external API
        stubFor(get(urlEqualTo("/api/rates?from=USD&to=EUR"))
                .willReturn(aResponse()
                        .withStatus(200)
                        .withHeader("Content-Type", "application/json")
                        .withBody("{\"rate\": 0.85}")));

        // Call our service
        ResponseEntity<String> response = restTemplate.getForEntity(
                "/convert?amount=100&from=USD&to=EUR", String.class);

        assertThat(response.getStatusCode()).isEqualTo(200);
        assertThat(response.getBody()).contains("85.0");
    }
}
💡Use Dynamic Ports in CI
📊 Production Insight
In a microservices environment, I've seen teams run multiple WireMock instances for different services. Always use dynamic ports to avoid port conflicts in parallel test execution.
🎯 Key Takeaway
WireMockTest annotation starts a WireMock server automatically. Use dynamic ports for CI reliability.

Request Matching and Response Templating

WireMock's request matching is powerful. You can match on URL, method, headers, query parameters, and body. For dynamic responses, use response templating with Handlebars.

Consider a payment service that expects a JSON payload with a transaction_id. You can match the body and return a dynamic response:

``java stubFor(post(urlEqualTo("/payments")) .withRequestBody(matchingJsonPath("$.amount")) .willReturn(aResponse() .withStatus(201) .withHeader("Content-Type", "application/json") .withBody("{ \"id\": \"{{randomValue length=20 type='ALPHANUMERIC'}}\", \"status\": \"approved\" }") .withTransformers("response-template"))); ``

This uses the response-template transformer to generate a random ID. You need to enable the transformer globally or per stub.

For more complex scenarios, use WireMock's scenario feature to simulate stateful behavior, like a payment that is first pending then confirmed.

PaymentServiceTest.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Test
void shouldHandlePaymentFlow() {
    // Scenario: payment starts as pending, then approved
    stubFor(post(urlEqualTo("/payments"))
            .inScenario("Payment Flow")
            .whenScenarioStateIs(Scenario.STARTED)
            .willReturn(aResponse()
                    .withStatus(201)
                    .withBody("{\"status\":\"pending\"}"))
            .willSetStateTo("Approved"));

    stubFor(post(urlEqualTo("/payments"))
            .inScenario("Payment Flow")
            .whenScenarioStateIs("Approved")
            .willReturn(aResponse()
                    .withStatus(200)
                    .withBody("{\"status\":\"approved\"}")));

    // First call returns pending
    ResponseEntity<String> first = restTemplate.postForEntity("/pay", paymentRequest, String.class);
    assertThat(first.getBody()).contains("pending");

    // Second call returns approved
    ResponseEntity<String> second = restTemplate.postForEntity("/pay", paymentRequest, String.class);
    assertThat(second.getBody()).contains("approved");
}
🔥Response Templating Requires Setup
📊 Production Insight
I once spent hours debugging a test where the stub matched but the response was static. The problem: I forgot to enable the response-template transformer. Always double-check transformer registration.
🎯 Key Takeaway
Use JSON path matching for precise request validation and response templating for dynamic data.

Verifying Requests and Asserting Interactions

Testing that your service sent the correct request is just as important as stubbing the response. WireMock allows you to verify that a specific request was made, with exact matching.

For example, after calling your payment service, verify that it sent the correct payload to the external API:

``java verify(postRequestedFor(urlEqualTo("/payments")) .withRequestBody(matchingJsonPath("$.amount", equalTo("100.00"))) .withHeader("Authorization", containing("Bearer"))); ``

``java verify(exactly(1), postRequestedFor(urlEqualTo("/payments"))); ``

This is crucial for ensuring your service doesn't make duplicate requests or miss calls entirely.

VerificationExampleTest.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
@Test
void shouldSendCorrectPaymentRequest() {
    stubFor(post(urlEqualTo("/payments"))
            .willReturn(aResponse().withStatus(201)));

    restTemplate.postForEntity("/pay", new PaymentRequest(100.00), String.class);

    verify(postRequestedFor(urlEqualTo("/payments"))
            .withRequestBody(matchingJsonPath("$.amount", equalTo("100.0")))
            .withHeader("Authorization", containing("Bearer secret")));
}
⚠ Verification Order Matters
📊 Production Insight
In a recent project, we caught a bug where the service was sending the wrong currency code. The test verified the request body and failed, saving us a production incident.
🎯 Key Takeaway
Use verify() to assert that your service made the expected HTTP calls with correct parameters.

Simulating Faults and Edge Cases

One of WireMock's superpowers is fault injection. You can simulate timeouts, connection resets, malformed responses, and HTTP errors. This is invaluable for testing your error handling and retry logic.

``java stubFor(get(urlEqualTo("/slow")) .willReturn(aResponse() .withFixedDelay(5000) .withStatus(200))); ``

``java stubFor(get(urlEqualTo("/reset")) .willReturn(aResponse().withFault(Fault.CONNECTION_RESET_BY_PEER))); ``

Combine this with Spring's @Retryable to ensure your service handles transient failures gracefully.

FaultInjectionTest.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
void shouldHandleTimeout() {
    stubFor(get(urlEqualTo("/api/data"))
            .willReturn(aResponse()
                    .withFixedDelay(10000)
                    .withStatus(200)));

    assertThrows(ResourceAccessException.class, () -> {
        restTemplate.getForEntity("/fetch-data", String.class);
    });
}

@Test
void shouldHandleConnectionReset() {
    stubFor(get(urlEqualTo("/api/data"))
            .willReturn(aResponse().withFault(Fault.CONNECTION_RESET_BY_PEER)));

    assertThrows(ResourceAccessException.class, () -> {
        restTemplate.getForEntity("/fetch-data", String.class);
    });
}
💡Test Your Retry Logic
📊 Production Insight
I once saw a service that had a 30-second timeout but the external API took 31 seconds. The test with a 35-second delay caught it immediately. Always test slightly above your configured timeout.
🎯 Key Takeaway
Fault injection tests are essential for building resilient services. Don't skip them.

What the Official Docs Won't Tell You

After years of using WireMock in production-grade Spring Boot applications, here are the gotchas that the official documentation glosses over:

1. Static state pollution across test classes WireMock server, when used as a static field or via @WireMockTest, holds state across tests. If you don't reset it, stubs from one test can affect another. The official docs mention resetAll() but don't emphasize that you must call it in @BeforeEach or @AfterEach. I've seen CI builds fail randomly because of this.

2. Response templating is not enabled by default The response-template transformer must be registered explicitly. Many developers assume it works out of the box and waste hours debugging why {{randomValue}} isn't generating random data.

3. JSON matching with equalToJson can be tricky By default, equalToJson is strict about field ordering and whitespace. Use the lenient mode: equalToJson(body, true, true). The second parameter is ignoreArrayOrder, the third is ignoreExtraElements. Without these, tests can fail due to JSON formatting differences.

4. WireMock's HTTPS support requires extra setup If your application calls HTTPS endpoints, WireMock can simulate that, but you need to configure a keystore. The docs cover it, but most examples use HTTP. In production, your external APIs are likely HTTPS, so your tests should reflect that.

5. Record-playback can be a maintenance nightmare WireMock can record real API responses and play them back. It sounds great, but the recorded stubs are fragile. If the API changes even slightly, your tests break. I recommend using it only for initial test creation, then hand-tuning stubs for maintainability.

WireMockHttpsTest.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
@WireMockTest(httpsEnabled = true)
class HttpsTest {

    @Test
    void testHttpsStub() {
        stubFor(get(urlEqualTo("/secure"))
                .willReturn(aResponse().withStatus(200)));

        // Your service must trust WireMock's self-signed certificate
        ResponseEntity<String> response = restTemplate.getForEntity("https://localhost:8443/secure", String.class);
        assertThat(response.getStatusCode()).isEqualTo(200);
    }
}
⚠ HTTPS Tests Require Trust Configuration
📊 Production Insight
In a recent project, a developer spent two days debugging a test that passed locally but failed on CI. The root cause: a different test class had left a stub that matched the same URL. Reset all stubs in @BeforeEach and the problem vanished.
🎯 Key Takeaway
Always reset state between tests, enable transformers explicitly, and be aware of JSON matching nuances.

Integrating WireMock with Spring Cloud Contract

If you're working in a microservices ecosystem, Spring Cloud Contract (SCC) builds on top of WireMock to provide contract testing. SCC generates WireMock stubs from contract definitions, which can be shared between producer and consumer services.

Here's a minimal setup: add the spring-cloud-starter-contract-verifier dependency and write a contract in Groovy or YAML. SCC will generate tests and WireMock stubs automatically.

But be warned: SCC adds complexity. For simple projects, plain WireMock is often sufficient. I've seen teams over-engineer their testing with SCC when all they needed was a few stubs.

ContractTest.groovyJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Contract.make {
    description "should return exchange rate"
    request {
        method GET()
        url "/api/rates?from=USD&to=EUR"
        headers {
            contentType applicationJson()
        }
    }
    response {
        status 200
        headers {
            contentType applicationJson()
        }
        body """
        {
            "rate": 0.85
        }
        """
    }
}
🔥SCC vs Plain WireMock
📊 Production Insight
I worked with a team that adopted SCC for all their microservices. The contract tests became a bottleneck because every API change required updating contracts in multiple repos. For internal services, plain WireMock with integration tests was more pragmatic.
🎯 Key Takeaway
Spring Cloud Contract generates WireMock stubs from contracts, enabling consumer-driven contract testing.
● Production incidentPOST-MORTEMseverity: high

The Case of the Flaky CI Build

Symptom
Tests passed locally but failed intermittently on CI. Stack trace showed unexpected HTTP 404 from a mocked endpoint.
Assumption
Developers assumed it was a race condition or network issue in the CI environment.
Root cause
WireMock server was not reset between test classes. One test would stub a response with a specific scenario, and another test would fail because the stub wasn't cleared.
Fix
Added @BeforeEach method to reset WireMock: wireMockServer.resetAll(). Also used @DirtiesContext on test classes to ensure clean Spring context per test.
Key lesson
  • Always reset WireMock state between tests, especially when using static or shared instances.
  • Use @DirtiesContext judiciously — it slows down tests but prevents cross-test pollution.
  • Consider using WireMock's scenario feature for stateful testing, but document it clearly.
  • Never assume tests are isolated; verify by running the full suite.
  • Add a WireMock reset listener in JUnit's after-all callback for extra safety.
Production debug guideSymptom to Action5 entries
Symptom · 01
Test fails with 'Connection refused'
Fix
Check if WireMock server is started. Ensure @WireMockTest annotation or manual server start is in place.
Symptom · 02
Stub not matching — returns 404 instead of expected response
Fix
Enable WireMock logging: wireMockServer.setLogging(true). Check request URL, method, headers, and body against stub mapping.
Symptom · 03
Tests pass in isolation but fail in suite
Fix
Reset WireMock state between tests: wireMockServer.resetAll(). Check for leftover stubs.
Symptom · 04
Request body matching fails despite identical JSON
Fix
Use WireMock's 'equalToJson' with lenient mode: equalToJson(json, true, true). Check for whitespace or field ordering.
Symptom · 05
WireMock port conflicts with other tests
Fix
Use a dynamic port: @WireMockTest(httpPort = 0) or wireMockServer = new WireMockServer(wireMockConfig().dynamicPort()).
★ Quick Debug Cheat SheetImmediate actions for common WireMock issues
Stub not matched
Immediate action
Enable request logging
Commands
wireMockServer.setLogging(true);
Check logs for request details
Fix now
Update stub to match actual request
Test pollution+
Immediate action
Reset server
Commands
wireMockServer.resetAll();
Add @BeforeEach to call reset
Fix now
Reset before each test
JSON body mismatch+
Immediate action
Use lenient JSON matching
Commands
equalToJson(body, true, true)
Check field order and whitespace
Fix now
Update stub with lenient matching
Port conflict+
Immediate action
Use dynamic port
Commands
@WireMockTest(httpPort = 0)
Inject port: @LocalServerPort
Fix now
Switch to dynamic port
FeatureWireMockMockServerHoverfly
Request matchingRich (URL, body, headers, JSONPath)RichRich
Response templatingYes (Handlebars)YesYes
Fault injectionYes (timeouts, resets, malformed)YesLimited
Record/playbackYesYesYes
Spring Boot integrationExcellent (@WireMockTest)GoodGood
PerformanceFastFastModerate
⚙ Quick Reference
6 commands from this guide
FileCommand / CodePurpose
ExchangeRateServiceTest.java@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)Setting Up WireMock in Spring Boot
PaymentServiceTest.java@TestRequest Matching and Response Templating
VerificationExampleTest.java@TestVerifying Requests and Asserting Interactions
FaultInjectionTest.java@TestSimulating Faults and Edge Cases
WireMockHttpsTest.java@WireMockTest(httpsEnabled = true)What the Official Docs Won't Tell You
ContractTest.groovyContract.make {Integrating WireMock with Spring Cloud Contract

Key takeaways

1
WireMock lets you stub external HTTP services deterministically, making your tests reliable and fast.
2
Always reset state between tests, use dynamic ports, and enable response templating explicitly.
3
Combine request verification with fault injection to build robust error handling in your services.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
How do you stub a POST endpoint with a JSON body in WireMock?
Q02SENIOR
Explain how WireMock scenarios work and when you would use them.
Q03SENIOR
How would you test that your service retries on a 500 error using WireMo...
Q01 of 03JUNIOR

How do you stub a POST endpoint with a JSON body in WireMock?

ANSWER
Use stubFor(post(urlEqualTo("/endpoint")).withRequestBody(equalToJson("{...}")).willReturn(aResponse().withStatus(201))).
FAQ · 3 QUESTIONS

Frequently Asked Questions

01
Can WireMock stub HTTPS endpoints?
02
How do I reset WireMock between tests?
03
What is the difference between WireMock and Mockito?
N
Naren Founder & Principal Engineer

20+ years shipping production Java in banking & fintech. Lessons pulled from things that broke in production.

Follow
Verified
production tested
July 15, 2026
last updated
2,398
articles · all by Naren
🔥

That's Spring Boot. Mark it forged?

3 min read · try the examples if you haven't

Previous
REST API Testing with REST-assured: Given-When-Then BDD Style Testing
100 / 121 · Spring Boot
Next
The Complete Guide to RestTemplate in Spring: GET, POST, PUT, DELETE