{"id":2162,"date":"2026-03-14T01:02:43","date_gmt":"2026-03-13T12:02:43","guid":{"rendered":"https:\/\/www.ronella.xyz\/?p=2162"},"modified":"2026-03-14T01:02:43","modified_gmt":"2026-03-13T12:02:43","slug":"wiremock-matchers-in-practice-from-strings-to-json-schemas","status":"publish","type":"post","link":"https:\/\/www.ronella.xyz\/?p=2162","title":{"rendered":"WireMock Matchers in Practice: From Strings to JSON Schemas"},"content":{"rendered":"<p>WireMock\u2019s matching model is built around a small set of pattern types that you reuse everywhere: URLs, headers, query parameters, and bodies all use variations of the same abstractions.  In this article we\u2019ll build an intuition for those abstractions and then see them in a Java example.<\/p>\n<hr \/>\n<h2>The Core Abstractions: ContentPattern and StringValuePattern<\/h2>\n<p>At the heart of WireMock matching is the idea \u201cgiven some content, decide how well it matches this expectation.\u201d That idea is embodied in <strong>ContentPattern<\/strong> and its concrete subclasses.<\/p>\n<ul>\n<li>ContentPattern<T> is the conceptual base: \u201cmatch content of type T and produce a <code>MatchResult<\/code>.\u201d<\/li>\n<li>StringValuePattern is the concrete type for matching strings and underpins almost every simple matcher you use in the DSL.<\/li>\n<\/ul>\n<p>You rarely construct <code>StringValuePattern<\/code> directly; you use the DSL factory methods that create it for you. These include:<\/p>\n<ul>\n<li><code>equalTo(&quot;foo&quot;)<\/code> \u2013 exact match.<\/li>\n<li><code>containing(&quot;foo&quot;)<\/code> \u2013 substring.<\/li>\n<li><code>matching(&quot;[A-Z]+&quot;)<\/code> \u2013 regular expression.<\/li>\n<li><code>equalToJson(&quot;{...}&quot;)<\/code>, <code>equalToXml(&quot;&lt;root\/&gt;&quot;)<\/code> \u2013 structural equality for JSON\/XML.<\/li>\n<\/ul>\n<p><strong>Rationale:<\/strong> by funnelling everything through StringValuePattern, WireMock can reuse the same matching semantics across URLs, headers, query params, and body fragments, and can assign a <em>distance score<\/em> that lets it pick the best matching stub.<\/p>\n<p>Mini example (headers and query):<\/p>\n<pre><code class=\"language-java\">stubFor(get(urlPathEqualTo(&quot;\/search&quot;))\n    .withQueryParam(&quot;q&quot;, equalTo(&quot;WireMock&quot;))\n    .withHeader(&quot;Accept&quot;, containing(&quot;json&quot;))\n    .willReturn(okJson(&quot;&quot;&quot;{ &quot;result&quot;: &quot;ok&quot; }&quot;&quot;&quot;))\n);<\/code><\/pre>\n<p>Both <code>equalTo(&quot;WireMock&quot;)<\/code> and <code>containing(&quot;json&quot;)<\/code> are StringValuePattern instances internally.<\/p>\n<hr \/>\n<h2>MultiValuePattern: Matching Lists of Values<\/h2>\n<p>Headers and query parameters can have multiple values; sometimes you care about the set of values as a whole, not just any one of them. That\u2019s what <strong>MultiValuePattern<\/strong> is for.<\/p>\n<p>Conceptually, MultiValuePattern answers: \u201cdoes this list of strings satisfy my condition?\u201d Typical usage for query parameters:<\/p>\n<pre><code class=\"language-java\">stubFor(get(urlPathEqualTo(&quot;\/items&quot;))\n    .withQueryParam(&quot;tag&quot;, havingExactly(&quot;featured&quot;, &quot;sale&quot;))\n    .willReturn(ok())\n);<\/code><\/pre>\n<p>Here:<\/p>\n<ul>\n<li><code>havingExactly(&quot;featured&quot;, &quot;sale&quot;)<\/code> expresses that the parameter <code>tag<\/code> must have exactly those two values and no others.<\/li>\n<li>MultiValuePattern is the pattern over that list, analogous to how StringValuePattern is a pattern over a single string.<\/li>\n<\/ul>\n<p><strong>Rationale:<\/strong> this is important for APIs where order and multiplicity of query or header values matter (e.g. \u201call of these scopes must be present\u201d). Without a dedicated multi-value abstraction you would be forced to encode such logic in brittle string hacks.<\/p>\n<hr \/>\n<h2>UrlPathPattern and URL-Level Matching<\/h2>\n<p>WireMock lets you match URLs at several levels in the Java DSL: exact string, regex, or path-only vs path+query. <strong>UrlPathPattern<\/strong> corresponds to <code>urlPathMatching(...)<\/code> when you use the Java client.<\/p>\n<p>Key URL matchers in Java:<\/p>\n<ul>\n<li><code>urlEqualTo(&quot;\/path?query=...&quot;)<\/code> \u2013 exact match on full path (with query).<\/li>\n<li><code>urlMatching(&quot;regex&quot;)<\/code> \u2013 regex on full path (with query).<\/li>\n<li><code>urlPathEqualTo(&quot;\/path&quot;)<\/code> \u2013 exact match on path only.<\/li>\n<li><code>urlPathMatching(&quot;regex&quot;)<\/code> \u2013 regex on path only.<\/li>\n<\/ul>\n<pre><code class=\"language-java\">stubFor(get(urlPathMatching(&quot;\/users\/([A-Za-z0-9_-]+)\/repos&quot;))\n    .willReturn(aResponse()\n        .withStatus(200)));<\/code><\/pre>\n<p>In modern WireMock 3 you\u2019ll also see <em>templates<\/em> like <code>urlPathTemplate(&quot;\/contacts\/{contactId}&quot;)<\/code>, which are easier to read and type-safe at the path level:<\/p>\n<pre><code class=\"language-java\">stubFor(get(urlPathTemplate(&quot;\/contacts\/{contactId}&quot;))\n    .willReturn(aResponse()\n        .withStatus(200)));<\/code><\/pre>\n<p><strong>Rationale:<\/strong> splitting \u201cpath\u201d from \u201cquery\u201d and using distinct matchers lets you:<\/p>\n<ul>\n<li>Use exact matches (<code>urlEqualTo<\/code>, <code>urlPathEqualTo<\/code>) when you want deterministic behaviour (faster, simpler).<\/li>\n<li>Only fall back to regex (<code>urlMatching<\/code>, <code>urlPathMatching<\/code>) when you truly need flexible matching, such as versioned paths or dynamic IDs.<\/li>\n<\/ul>\n<hr \/>\n<h2>MatchesJsonPathPattern: Querying Inside JSON Bodies<\/h2>\n<p>When the request body is JSON and you care about <em>conditions<\/em> inside it rather than full equality, WireMock uses <strong>MatchesJsonPathPattern<\/strong>. You access it via <code>matchingJsonPath(...)<\/code> in the DSL.<\/p>\n<p>Two main flavours:<\/p>\n<ul>\n<li><code>matchingJsonPath(&quot;$.message&quot;)<\/code> \u2013 body must match the JSONPath (i.e. the expression finds at least one node).<\/li>\n<li><code>matchingJsonPath(&quot;$.message&quot;, equalTo(&quot;Hello&quot;))<\/code> \u2013 evaluates JSONPath, converts the result to a string, then matches it with a StringValuePattern.<\/li>\n<\/ul>\n<p>Example:<\/p>\n<pre><code class=\"language-java\">stubFor(post(urlEqualTo(&quot;\/api\/message&quot;))\n    .withRequestBody(matchingJsonPath(&quot;$.message&quot;, equalTo(&quot;Hello World!&quot;)))\n    .willReturn(aResponse().withStatus(200))\n);<\/code><\/pre>\n<p>Important detail: all WireMock matchers operate on strings, so the JSONPath result is stringified before applying the StringValuePattern. This even allows selecting a <em>sub-document<\/em> and then matching it with <code>equalToJson(...)<\/code>.<\/p>\n<p><strong>Rationale:<\/strong> JSONPath is ideal when you need to assert that some field exists or meets a condition (e.g. <code>price &gt; 10<\/code>) without tightly coupling your tests to the entire JSON shape. It makes tests robust to benign changes in unrelated fields.<\/p>\n<hr \/>\n<h2>JsonUnit Placeholders in equalToJson<\/h2>\n<p>When you <em>do<\/em> want structural equality for JSON, <code>equalToJson<\/code> is your tool, and it is powered by JsonUnit. JsonUnit supports <strong>placeholders<\/strong>, which WireMock exposes to let you relax parts of the JSON equality check.<\/p>\n<p>You embed placeholders directly into the expected JSON:<\/p>\n<pre><code class=\"language-java\">stubFor(post(urlEqualTo(&quot;\/orders&quot;))\n    .withRequestBody(equalToJson(&quot;&quot;&quot;\n        {\n          &quot;id&quot;: &quot;${json-unit.any-string}&quot;,\n          &quot;amount&quot;: 123.45,\n          &quot;status&quot;: &quot;NEW&quot;,\n          &quot;metadata&quot;: &quot;${json-unit.ignore}&quot;\n        }\n        &quot;&quot;&quot;))\n    .willReturn(ok())\n);<\/code><\/pre>\n<p>Common placeholders:<\/p>\n<ul>\n<li><code>${json-unit.ignore}<\/code> \u2013 ignore value and type, just require key presence.<\/li>\n<li><code>${json-unit.any-string}<\/code> \u2013 value can be any string.<\/li>\n<li><code>${json-unit.any-number}<\/code> \u2013 any number.<\/li>\n<li><code>${json-unit.any-boolean}<\/code> \u2013 any boolean.<\/li>\n<li><code>${json-unit.regex}[A-Z]+<\/code> \u2013 value must match this regex.<\/li>\n<\/ul>\n<p>You can also change delimiters if <code>${<\/code> and <code>}<\/code> clash with your payload conventions.<\/p>\n<p><strong>Rationale:<\/strong> equalToJson is excellent for enforcing payload shape and fixed fields, but brittle when some fields are inherently variable (IDs, timestamps, correlation IDs). JsonUnit placeholders let you keep strictness where it matters and loosen it where variability is expected.<\/p>\n<hr \/>\n<h2>JSON Schema Matching<\/h2>\n<p>For more formal validation you can use <strong>JSON Schema<\/strong> with matchers like <code>matchingJsonSchema<\/code>. This checks that the JSON body (or a path variable) conforms to an explicit schema: types, required fields, min\/max, etc.<\/p>\n<p>Typical body matcher:<\/p>\n<pre><code class=\"language-java\">stubFor(post(urlEqualTo(&quot;\/things&quot;))\n    .withRequestBody(\n        matchingJsonSchema(&quot;&quot;&quot;\n            {\n              &quot;$schema&quot;: &quot;http:\/\/json-schema.org\/draft-07\/schema#&quot;,\n              &quot;type&quot;: &quot;object&quot;,\n              &quot;required&quot;: [&quot;id&quot;, &quot;name&quot;],\n              &quot;properties&quot;: {\n                &quot;id&quot;:   { &quot;type&quot;: &quot;string&quot;, &quot;minLength&quot;: 2 },\n                &quot;name&quot;: { &quot;type&quot;: &quot;string&quot; },\n                &quot;price&quot;: { &quot;type&quot;: &quot;number&quot;, &quot;minimum&quot;: 0 }\n              },\n              &quot;additionalProperties&quot;: false\n            }\n            &quot;&quot;&quot;)\n    )\n    .willReturn(created())\n);<\/code><\/pre>\n<p>You can also apply <code>matchingJsonSchema<\/code> to path parameters when you use path templates, e.g. constraining <code>userId<\/code> to a specific shape.<\/p>\n<p><strong>Rationale:<\/strong><\/p>\n<ul>\n<li>JsonPath answers \u201cdoes there exist an element meeting this condition?\u201d<\/li>\n<li>equalToJson + JsonUnit placeholders answers \u201cis this JSON equal to a template with some flexible parts?\u201d<\/li>\n<li>JSON Schema answers \u201cdoes this JSON globally satisfy a formal contract?\u201d.<\/li>\n<\/ul>\n<p>JSON Schema is especially useful when your WireMock stub is acting as a consumer-driven contract test for another service.<\/p>\n<hr \/>\n<h2>Custom Matcher: Escaping the Built-in Model<\/h2>\n<p>When WireMock\u2019s built-in matchers are not enough, you can implement <strong>Custom Matchers<\/strong>. The main extension is <code>RequestMatcherExtension<\/code>, which gives you full control over how a request is matched.<\/p>\n<p>The minimal shape:<\/p>\n<pre><code class=\"language-java\">import com.github.tomakehurst.wiremock.extension.Parameters;\nimport com.github.tomakehurst.wiremock.extension.requestfilter.RequestMatcherExtension;\nimport com.github.tomakehurst.wiremock.http.MatchResult;\nimport com.github.tomakehurst.wiremock.http.Request;\n\npublic class BodyLengthMatcher extends RequestMatcherExtension {\n\n    @Override\n    public String getName() {\n        return &quot;body-too-long&quot;;\n    }\n\n    @Override\n    public MatchResult match(Request request, Parameters parameters) {\n        int maxLength = parameters.getInt(&quot;maxLength&quot;);\n        boolean tooLong = request.getBody().length &gt; maxLength;\n        return MatchResult.of(tooLong);\n    }\n}<\/code><\/pre>\n<p>You then reference it by name in your stub:<\/p>\n<pre><code class=\"language-java\">stubFor(requestMatching(&quot;body-too-long&quot;, Parameters.one(&quot;maxLength&quot;, 2048))\n    .willReturn(aResponse().withStatus(422))\n);<\/code><\/pre>\n<p>There is also a lower\u2011level <code>ValueMatcher&lt;T&gt;<\/code> interface that you can use in verification or in some advanced APIs to match arbitrary values, without registering a global extension.<\/p>\n<p><strong>Rationale:<\/strong> custom matchers are your \u201cescape hatch\u201d for domain\u2011specific logic which would otherwise be contorted into regexes or JSONPath. Examples include:<\/p>\n<ul>\n<li>Checking that a JWT in a header is valid and contains certain claims.<\/li>\n<li>Ensuring consistency between header and body fields.<\/li>\n<\/ul>\n<hr \/>\n<h2>Putting It All Together<\/h2>\n<p>Below is a Java 17 style example which combines several of the discussed matcher types. You can drop this into a plain Maven\/Gradle project that has <code>wiremock<\/code> dependencies;<\/p>\n<h2>Example: Product API Stub with Multiple Matchers<\/h2>\n<pre><code class=\"language-java\">import com.github.tomakehurst.wiremock.WireMockServer;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.*;\nimport static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;\n\npublic class ProductApiStub {\n\n    public static void main(String[] args) {\n        WireMockServer server = new WireMockServer(\n            wireMockConfig().port(8080)\n        );\n        server.start();\n\n        configureFor(&quot;localhost&quot;, 8080);\n        registerStubs(server);\n\n        Runtime.getRuntime().addShutdownHook(new Thread(server::stop));\n        System.out.println(&quot;WireMock running at http:\/\/localhost:8080&quot;);\n    }\n\n    private static void registerStubs(WireMockServer server) {\n        server.stubFor(post(urlPathTemplate(&quot;\/shops\/{shopId}\/products&quot;))\n            \/\/ UrlPathTemplate + JSON Schema for path parameter\n            .withPathParam(&quot;shopId&quot;, matchingJsonSchema(&quot;&quot;&quot;\n                {\n                  &quot;type&quot;: &quot;string&quot;,\n                  &quot;minLength&quot;: 3,\n                  &quot;maxLength&quot;: 10\n                }\n                &quot;&quot;&quot;))\n\n            \/\/ MultiValuePattern on query param\n            .withQueryParam(&quot;tag&quot;, havingExactly(&quot;featured&quot;, &quot;sale&quot;))\n\n            \/\/ StringValuePattern: header must match regex\n            .withHeader(&quot;X-Request-Id&quot;, matching(&quot;req-[0-9a-f]{8}&quot;))\n\n            \/\/ JsonUnit placeholders in equalToJson\n            .withRequestBody(equalToJson(&quot;&quot;&quot;\n                {\n                  &quot;id&quot;: &quot;${json-unit.any-string}&quot;,\n                  &quot;name&quot;: &quot;Socks&quot;,\n                  &quot;price&quot;: 9.99,\n                  &quot;metadata&quot;: &quot;${json-unit.ignore}&quot;\n                }\n                &quot;&quot;&quot;))\n\n            \/\/ MatchesJsonPathPattern + StringValuePattern\n            .withRequestBody(matchingJsonPath(&quot;$[?(@.price &gt; 0)]&quot;))\n\n            .willReturn(okJson(&quot;&quot;&quot;\n                {\n                  &quot;status&quot;: &quot;ACCEPTED&quot;,\n                  &quot;message&quot;: &quot;Product created&quot;\n                }\n                &quot;&quot;&quot;)));\n    }\n}<\/code><\/pre>\n<p>What this stub requires:<\/p>\n<ul>\n<li>Path: <code>\/shops\/{shopId}\/products<\/code> where <code>shopId<\/code> is a string 3\u201310 chars long (validated via JSON Schema).<\/li>\n<li>Query: <code>?tag=featured&amp;tag=sale<\/code> and only those values (MultiValuePattern).<\/li>\n<li>Header: <code>X-Request-Id<\/code> must match <code>req-[0-9a-f]{8}<\/code> (regex StringValuePattern).<\/li>\n<li>Body: JSON with fixed fields <code>name<\/code> and <code>price<\/code>, but any string for <code>id<\/code> and anything for <code>metadata<\/code>, thanks to JsonUnit placeholders.<\/li>\n<li>Body: JSONPath asserts that <code>$.price<\/code> is strictly greater than 0.<\/li>\n<\/ul>\n<h2>How to Validate the Example<\/h2>\n<p>Using any HTTP client (curl, HTTPie, Postman, Java HTTP client), send:<\/p>\n<pre><code class=\"language-bash\">curl -i \\\n  -X POST &quot;http:\/\/localhost:8080\/shops\/abc\/products?tag=featured&amp;tag=sale&quot; \\\n  -H &quot;Content-Type: application\/json&quot; \\\n  -H &quot;X-Request-Id: req-deadbeef&quot; \\\n  -d &#039;{\n        &quot;id&quot;: &quot;any-random-id&quot;,\n        &quot;name&quot;: &quot;Socks&quot;,\n        &quot;price&quot;: 9.99,\n        &quot;metadata&quot;: { &quot;color&quot;: &quot;blue&quot; }\n      }&#039;<\/code><\/pre>\n<p>You should receive a 200 OK with JSON body:<\/p>\n<pre><code class=\"language-json\">{\n  &quot;status&quot;: &quot;ACCEPTED&quot;,\n  &quot;message&quot;: &quot;Product created&quot;\n}<\/code><\/pre>\n<p>If you break one constraint at a time (e.g. price negative, missing <code>tag<\/code> value, bad <code>X-Request-Id<\/code> format, too-short <code>shopId<\/code>), the request will no longer match that stub, and you will either hit no stub or some other fallback stub, which is precisely how you verify each matcher behaves as expected.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>WireMock\u2019s matching model is built around a small set of pattern types that you reuse everywhere: URLs, headers, query parameters, and bodies all use variations of the same abstractions. In this article we\u2019ll build an intuition for those abstractions and then see them in a Java example. The Core Abstractions: ContentPattern and StringValuePattern At the [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[76,96],"tags":[],"_links":{"self":[{"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=\/wp\/v2\/posts\/2162"}],"collection":[{"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2162"}],"version-history":[{"count":1,"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=\/wp\/v2\/posts\/2162\/revisions"}],"predecessor-version":[{"id":2163,"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=\/wp\/v2\/posts\/2162\/revisions\/2163"}],"wp:attachment":[{"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2162"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2162"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2162"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}