1. Introduction
WireMock is a powerful HTTP mock server that stubs and verifies HTTP requests. It provides a controlled test environment, ensuring integration tests are fast, repeatable, and independent of external systems. In this example, I will demonstrate how to integrate WireMock into a Spring Boot project so the tests pass even when the third-party services become unavailable or return unexpected data.
2. Setup
In this step, I will create a gradle project with WireMock, spring-cloud-starter-contract-stub-runner, Lombok, and spring-boot-starter-web libraries via Spring Initializr.
2.1 Build.gradle
No modification for the generated build.gradle file.
build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '3.4.4'
id 'io.spring.dependency-management' version '1.1.7'
}
group = 'com.zheng.demo'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
ext {
set('springCloudVersion', "2024.0.1")
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.cloud:spring-cloud-starter-contract-stub-runner'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
tasks.named('test') {
useJUnitPlatform()
}2.2 Application.properties
Updated the generated application.properties file to include the logging level and the third-party host URI.
application.properties
spring.application.name=wiremock-demo 3rdParth.host=https://api.restful-api.dev/objects logging.level.org.zheng.demo=DEBUG
- Line 3: The third-party API is a free API with a limit of 100 requests per day. Clients will receive
405 Method Not Allowedwhen exceeding the limit.
2.3 logback.xml
Added the logback.xml to enable the logging for the spring boot application.
logback.xml
<?xml version="1.0" encoding="UTF-8"?> <configuration> <include resource="org/springframework/boot/logging/logback/defaults.xml" /> <include resource="org/springframework/boot/logging/logback/console-appender.xml" /> <root level="INFO"> <appender-ref ref="CONSOLE" /> </root> <logger name="com.zheng.demo" level="DEBUG" additivity="false"> <appender-ref ref="CONSOLE" /> </logger> </configuration>
- Line 11: enable the log level to
DEBUG.
2.4 Generated WiremockDemoApplication.java
Added a RestTempalte bean in the generated WiremockDemoApplication.java.
WiremockDemoApplication.java
package com.zheng.demo.wiremock_demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class WiremockDemoApplication {
public static void main(String[] args) {
SpringApplication.run(WiremockDemoApplication.class, args);
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
3. Java Model Object
3.1 ProductDetail.java
In this step, I will create a ProductDetail.java class that matches the Rest API response.
ProductDetail.java
package com.zheng.demo.wiremock_demo.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ProductDetail {
private int year;
private double price;
@JsonProperty("CPU model")
private String cpuModel;
@JsonProperty("Hard disk size")
private String hardDiskSize;
}
3.2 DemoObject.java
In this step, I will create a DemoObject.java class that matches the Rest API response.
DemoObject.java
package com.zheng.demo.wiremock_demo.model;
import java.time.LocalDateTime;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class DemoObject {
private String id;
private String name;
private ProductDetail data;
private LocalDateTime updatedAt;
}
4. RestDemoController
In this step, I will create a RestDemoController.java that invokes the third-party API for the CRUD operations.
RestDemoController.java
package com.zheng.demo.wiremock_demo.rest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import com.zheng.demo.wiremock_demo.model.DemoObject;
@RestController
@RequestMapping("/demo")
public class RestDemoController {
private static final String PATH = "/";
private static final Logger logger = LoggerFactory.getLogger(RestDemoController.class);
@Value("${3rdParth.host}")
private String thirdParthRestApiUrlBase;
private final RestTemplate restTemplate;
public RestDemoController(RestTemplate externalRestClient) {
super();
this.restTemplate = externalRestClient;
}
@GetMapping("/{id}")
public DemoObject getObjectById(@PathVariable("id") int id) {
String url = thirdParthRestApiUrlBase + PATH + id;
logger.info("getObjectById url=" + url);
return restTemplate.getForObject(url, DemoObject.class);
}
@PostMapping
public DemoObject createObject(@RequestBody DemoObject obj) {
logger.info("createObject url=" + thirdParthRestApiUrlBase);
return restTemplate.postForObject(thirdParthRestApiUrlBase, obj, DemoObject.class);
}
@PutMapping("/{id}")
public ResponseEntity<DemoObject> updateObject(@PathVariable("id") int id, @RequestBody DemoObject obj) {
String url = thirdParthRestApiUrlBase + PATH + id;
logger.info("updateObject url=" + url);
restTemplate.put(url, obj, DemoObject.class);
return ResponseEntity.ok().body(getObjectById(id));
}
@DeleteMapping("/{id}")
public ResponseEntity<Object> deleteObjectById(@PathVariable("id") int id) {
String url = thirdParthRestApiUrlBase + PATH + id;
logger.info("deleteObjectById url=" + url);
restTemplate.delete(url);
return ResponseEntity.status(HttpStatusCode.valueOf(204)).build();
}
}
- Line 28, 29: defines the
thirdParthRestApiUrlBasevaraible that gets the value from3rdParth.hostproperty in theapplication.propertiesfile. - Line 41, 47, 54, 62: prints out the logging for the third-party url. This will be used in 4.1 and 5.1.
4.1 Test the Spring Boot Application
In this step, I will start the spring boot application and test the Rest API via a curl command.
curl command for http://localhost:8080/demo/7
curl -X GET \ 'http://localhost:8080/demo/7' \ --header 'Accept: */*'
Here is the response.
http://localhost:8080/demo/7 Response
{
"id": "7",
"name": "Apple MacBook Pro 16",
"data": {
"year": 2019,
"price": 1849.99,
"CPU model": "Intel Core i9",
"Hard disk size": "1 TB"
}
}Monitor the spring boot application and capture the server log.
application log
2025-04-05T08:58:45.525-05:00 INFO 12028 --- [wiremock-demo] [nio-8080-exec-2] c.z.d.w.rest.RestDemoController : getObjectById url=https://api.restful-api.dev/objects/7
- Line 1: the application is hitting the actual third-party service β
https://api.restful-api.dev/objects/7.
5. Spring boot Wiremock Test
In this step, I will create a RestDemoControllerTest.java that stubs the CRUD options of the third-party API with a mock server.
RestDemoControllerTest.java
package com.zheng.demo.wiremock_demo.rest;
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.delete;
import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson;
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.post;
import static com.github.tomakehurst.wiremock.client.WireMock.put;
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock;
import org.springframework.http.ResponseEntity;
import org.springframework.test.util.ReflectionTestUtils;
import com.zheng.demo.wiremock_demo.model.DemoObject;
import com.zheng.demo.wiremock_demo.model.ProductDetail;
@SpringBootTest
@AutoConfigureWireMock(port = 8090)
class RestDemoControllerTest {
private static final String OBJECT_JSON = "{\"id\":\"7\",\"name\":\"Apple MacBook Pro 16\",\"data\":{\"year\":2019,\"price\":1849.99,\"CPU model\":\"Intel Core i9\",\"Hard disk size\":\"1 TB\"}}";
@Autowired
private RestDemoController testClass;
@BeforeEach
void setup() {
ReflectionTestUtils.setField(testClass, "thirdParthRestApiUrlBase", "http://localhost:8090");
}
@Test
void test_getObjectById() {
stubFor(get(urlEqualTo("/7")).willReturn(
aResponse().withStatus(200).withHeader("Content-Type", "application/json").withBody(OBJECT_JSON)));
DemoObject rep = testClass.getObjectById(7);
assertEquals("7", rep.getId());
}
@Test
void test_createObject() {
stubFor(post(urlEqualTo("/")).withRequestBody(equalToJson(OBJECT_JSON)).willReturn(
aResponse().withStatus(200).withHeader("Content-Type", "application/json").withBody(OBJECT_JSON)));
ProductDetail pd = ProductDetail.builder().year(2019).price(1849.99).cpuModel("Intel Core i9")
.hardDiskSize("1 TB").build();
DemoObject obj = DemoObject.builder().id("7").name("Apple MacBook Pro 16").data(pd).build();
DemoObject rep = testClass.createObject(obj);
assertEquals("7", rep.getId());
}
@Test
void test_putObject() {
stubFor(put(urlEqualTo("/7")).withRequestBody(equalToJson(OBJECT_JSON))
.willReturn(aResponse().withStatus(204)));
stubFor(get(urlEqualTo("/7")).willReturn(
aResponse().withStatus(200).withHeader("Content-Type", "application/json").withBody(OBJECT_JSON)));
ProductDetail pd = ProductDetail.builder().year(2019).price(1849.99).cpuModel("Intel Core i9")
.hardDiskSize("1 TB").build();
DemoObject obj = DemoObject.builder().id("7").name("Apple MacBook Pro 16").data(pd).build();
ResponseEntity<DemoObject> rep = testClass.updateObject(7, obj);
assertEquals("7", rep.getBody().getId());
}
@Test
void test_deleteObject() {
stubFor(delete(urlEqualTo("/7")).willReturn(aResponse().withStatus(204)));
ResponseEntity<Object> rep = testClass.deleteObjectById(7);
assertTrue(rep.getStatusCode().is2xxSuccessful());
}
}
- Line 26:
@AutoConfigureWireMock(port = 8090)starts the mock server at port8090. - Line 35: use the ReflectionTestUtilsβs
setFieldmethod to set thetestClass.thirdParthRestApiUrlBaseto the mock server:http://localhost:8090. - Line 40, 49, 60, 62, 73: use WireMock to stub Get, Post, Put, and Delete operationsβ requests and responses.
5.1 Execute Spring boot Wiremock Test
In this step, I will execute the RestDemoControllerTest.java and capture the console log.
Mock Testing Console Log
2025-04-05T10:16:26.920-05:00 INFO --- [ main] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [com.zheng.demo.wiremock_demo.rest.RestDemoControllerTest]: RestDemoControllerTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
2025-04-05T10:16:27.090-05:00 INFO --- [ main] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration com.zheng.demo.wiremock_demo.WiremockDemoApplication for test class com.zheng.demo.wiremock_demo.rest.RestDemoControllerTest
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.4.4)
2025-04-05T10:16:27.514-05:00 INFO 27828 --- [wiremock-demo] [ main] c.z.d.w.rest.RestDemoControllerTest : Starting RestDemoControllerTest using Java 17.0.11 with PID 27828 (started by azpm0 in C:\MaryTools\workspace\wiremock-demo)
2025-04-05T10:16:27.514-05:00 DEBUG 27828 --- [wiremock-demo] [ main] c.z.d.w.rest.RestDemoControllerTest : Running with Spring Boot v3.4.4, Spring v6.2.5
2025-04-05T10:16:27.515-05:00 INFO 27828 --- [wiremock-demo] [ main] c.z.d.w.rest.RestDemoControllerTest : No active profile set, falling back to 1 default profile: "default"
2025-04-05T10:16:29.710-05:00 INFO 27828 --- [wiremock-demo] [ main] c.z.d.w.rest.RestDemoControllerTest : Started RestDemoControllerTest in 2.467 seconds (process running for 3.726)
2025-04-05T10:16:30.592-05:00 INFO 27828 --- [wiremock-demo] [ main] c.z.d.w.rest.RestDemoController : deleteObjectById url=http://localhost:8090/7
2025-04-05T10:16:30.758-05:00 INFO 27828 --- [wiremock-demo] [tp2061865206-32] WireMock : Request received:
127.0.0.1 - DELETE /7
Accept: [*/*]
User-Agent: [Java/17.0.11]
Host: [localhost:8090]
Connection: [keep-alive]
Content-Type: [application/x-www-form-urlencoded]
Transfer-Encoding: [chunked]
Matched response definition:
{
"status" : 204
}
Response:
HTTP/1.1 204
Matched-Stub-Id: [45e076f0-5261-4a0b-9dbb-97b2e8e9b793]
2025-04-05T10:16:30.797-05:00 INFO 27828 --- [wiremock-demo] [ main] c.z.d.w.rest.RestDemoController : updateObject url=http://localhost:8090/7
2025-04-05T10:16:30.862-05:00 INFO 27828 --- [wiremock-demo] [tp2061865206-30] WireMock : Request received:
127.0.0.1 - PUT /7
Content-Type: [application/json]
Accept: [*/*]
User-Agent: [Java/17.0.11]
Host: [localhost:8090]
Connection: [keep-alive]
Transfer-Encoding: [chunked]
{"id":"7","name":"Apple MacBook Pro 16","data":{"year":2019,"price":1849.99,"CPU model":"Intel Core i9","Hard disk size":"1 TB"}}
Matched response definition:
{
"status" : 204
}
Response:
HTTP/1.1 204
Matched-Stub-Id: [38f980f5-973c-4607-8af4-88fdc4e6750b]
2025-04-05T10:16:30.863-05:00 INFO 27828 --- [wiremock-demo] [ main] c.z.d.w.rest.RestDemoController : getObjectById url=http://localhost:8090/7
2025-04-05T10:16:30.895-05:00 INFO 27828 --- [wiremock-demo] [tp2061865206-32] WireMock : Request received:
127.0.0.1 - GET /7
Accept: [application/json, application/yaml, application/*+json]
User-Agent: [Java/17.0.11]
Host: [localhost:8090]
Connection: [keep-alive]
Matched response definition:
{
"status" : 200,
"body" : "{\"id\":\"7\",\"name\":\"Apple MacBook Pro 16\",\"data\":{\"year\":2019,\"price\":1849.99,\"CPU model\":\"Intel Core i9\",\"Hard disk size\":\"1 TB\"}}",
"headers" : {
"Content-Type" : "application/json"
}
}
Response:
HTTP/1.1 200
Content-Type: [application/json]
Matched-Stub-Id: [d60084b6-7f40-4f79-98e0-d25a4d2ee2ed]
2025-04-05T10:16:30.911-05:00 INFO 27828 --- [wiremock-demo] [ main] c.z.d.w.rest.RestDemoController : getObjectById url=http://localhost:8090/7
2025-04-05T10:16:30.913-05:00 INFO 27828 --- [wiremock-demo] [tp2061865206-30] WireMock : Request received:
127.0.0.1 - GET /7
Accept: [application/json, application/yaml, application/*+json]
User-Agent: [Java/17.0.11]
Host: [localhost:8090]
Connection: [keep-alive]
Matched response definition:
{
"status" : 200,
"body" : "{\"id\":\"7\",\"name\":\"Apple MacBook Pro 16\",\"data\":{\"year\":2019,\"price\":1849.99,\"CPU model\":\"Intel Core i9\",\"Hard disk size\":\"1 TB\"}}",
"headers" : {
"Content-Type" : "application/json"
}
}
Response:
HTTP/1.1 200
Content-Type: [application/json]
Matched-Stub-Id: [0930abe7-4bec-4762-90f8-9989ec165b4d]
2025-04-05T10:16:30.920-05:00 INFO 27828 --- [wiremock-demo] [ main] c.z.d.w.rest.RestDemoController : createObject url=http://localhost:8090
2025-04-05T10:16:30.934-05:00 INFO 27828 --- [wiremock-demo] [tp2061865206-32] WireMock : Request received:
127.0.0.1 - POST /
Accept: [application/json, application/yaml, application/*+json]
Content-Type: [application/json]
User-Agent: [Java/17.0.11]
Host: [localhost:8090]
Connection: [keep-alive]
Transfer-Encoding: [chunked]
{"id":"7","name":"Apple MacBook Pro 16","data":{"year":2019,"price":1849.99,"CPU model":"Intel Core i9","Hard disk size":"1 TB"}}
Matched response definition:
{
"status" : 200,
"body" : "{\"id\":\"7\",\"name\":\"Apple MacBook Pro 16\",\"data\":{\"year\":2019,\"price\":1849.99,\"CPU model\":\"Intel Core i9\",\"Hard disk size\":\"1 TB\"}}",
"headers" : {
"Content-Type" : "application/json"
}
}
Response:
HTTP/1.1 200
Content-Type: [application/json]
Matched-Stub-Id: [97a6cc5b-8a01-4ad4-8301-6c56bf32b790]
2025-04-05T10:16:30.938-05:00 WARN 27828 --- [wiremock-demo] [ main] o.s.c.c.w.WireMockTestExecutionListener : You've used fixed ports for WireMock setup - will mark context as dirty. Please use random ports, as much as possible. Your tests will be faster and more reliable and this warning will go away
- Highlighted lines print the mock server URL:
localhost:8090instead of the actual services.
6. Conclusion
Testing external dependencies such as REST APIs can be challenging when developing web applications. Making network calls is slow and unreliable. WireMock provides a controlled test environment, ensuring integration tests are fast, repeatable, and independent of external systems. In this example, I created a spring boot web application that utilized Wiremock to stub the third-party API requests and responses.
7. Download
This was an example of a gradle project that included wiremock in a spring boot application.
You can download the full source code of this example here: WireMock with Spring Boot Example
Thank you!
We will contact you soon.
Mary ZhengApril 9th, 2025Last Updated: April 9th, 2025

This site uses Akismet to reduce spam. Learn how your comment data is processed.