<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kuruba Ramesh</title>
    <description>The latest articles on DEV Community by Kuruba Ramesh (@krameshr).</description>
    <link>https://dev.to/krameshr</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3981848%2F7a8f30d7-1044-4f8c-9275-09e699536538.png</url>
      <title>DEV Community: Kuruba Ramesh</title>
      <link>https://dev.to/krameshr</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/krameshr"/>
    <language>en</language>
    <item>
      <title>Integrating Specmatic Contract Testing into Spring PetClinic REST: What a "Simple" DELETE Test Taught Me About Test Architecture</title>
      <dc:creator>Kuruba Ramesh</dc:creator>
      <pubDate>Mon, 13 Jul 2026 05:24:42 +0000</pubDate>
      <link>https://dev.to/krameshr/-integrating-specmatic-contract-testing-into-spring-petclinic-rest-what-a-simple-delete-test-5he1</link>
      <guid>https://dev.to/krameshr/-integrating-specmatic-contract-testing-into-spring-petclinic-rest-what-a-simple-delete-test-5he1</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hello, I am Kuruba Ramesh, As part of my ongoing work with the Specmatic Full Stack AI Engineering Internship, I picked a second, well-known project for contract testing integration: &lt;strong&gt;spring-petclinic-rest&lt;/strong&gt;, the official Spring team's REST version of the classic Petclinic sample, built with an API-first approach and a complete OpenAPI 3.0 spec.&lt;/p&gt;

&lt;p&gt;Where my first project (a banking API) taught me how Specmatic's resiliency tests generate schema-valid data, this project taught me something more architectural: &lt;strong&gt;contract testing can expose limitations in your test infrastructure itself, not just your API.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Repository: &lt;a href="https://github.com/KRameshr/spring-petclinic-rest" rel="noopener noreferrer"&gt;https://github.com/KRameshr/spring-petclinic-rest&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Technology Stack&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Java 17&lt;/li&gt;
&lt;li&gt;Spring Boot&lt;/li&gt;
&lt;li&gt;Spring Data JPA&lt;/li&gt;
&lt;li&gt;H2 (in-memory database)&lt;/li&gt;
&lt;li&gt;Specmatic 2.50.0 (JUnit 5 integration)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Spring PetClinic REST exposes a full CRUD API for owners, pets, vets, visits, and specialties, 18 paths, 36 operations. It already ships with an OpenAPI spec, external example files, and a &lt;code&gt;specmatic.yaml&lt;/code&gt; (Config V3), so unlike a greenfield integration, my job was to get the existing setup fully working, find real gaps, and document them honestly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# specmatic.yaml&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
&lt;span class="na"&gt;specmatic&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;settings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;schemaResiliencyTests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;all&lt;/span&gt;
      &lt;span class="na"&gt;maxTestRequestCombinations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="na"&gt;systemUnderTest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#/components/services/petclinicService"&lt;/span&gt;
    &lt;span class="na"&gt;runOptions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#/components/runOptions/petclinicServiceTest"&lt;/span&gt;
&lt;span class="na"&gt;components&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;petclinicService&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Spring Petclinic REST Service&lt;/span&gt;
      &lt;span class="na"&gt;definitions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;definition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;specs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;openapi.yml&lt;/span&gt;
            &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;filesystem&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;src/main/resources&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;examples&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;directories&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;src/main/resources/openapi_examples&lt;/span&gt;
  &lt;span class="na"&gt;runOptions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;petclinicServiceTest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;openapi&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
        &lt;span class="na"&gt;baseUrl&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://localhost:9966/petclinic/api&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;schemaResiliencyTests: all&lt;/code&gt; is the setting that generates both the positive and negative schema variations, not just the hand-written examples — the same feature that ended up exposing the DELETE issue below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting a Real Baseline
&lt;/h2&gt;

&lt;p&gt;Running the existing contract test suite (&lt;code&gt;SpecmaticContractTest&lt;/code&gt; via JUnit 5 and Maven):&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;221 scenarios generated, 214 passing, 7 failing.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug 1: A Missing Field That Broke Every Visit Creation
&lt;/h2&gt;

&lt;p&gt;The first real failure was &lt;code&gt;POST /visits&lt;/code&gt;. Every generated request failed with a &lt;code&gt;DataIntegrityViolationException&lt;/code&gt;. The request schema (&lt;code&gt;VisitFields&lt;/code&gt;) only declared &lt;code&gt;date&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt; — but the &lt;code&gt;Visit&lt;/code&gt; entity has a &lt;strong&gt;required foreign key to a pet&lt;/strong&gt; (&lt;code&gt;petId&lt;/code&gt;). Every contract-valid request Specmatic generated was, by definition, missing a field the database required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; introduced a request-only &lt;code&gt;VisitCreate&lt;/code&gt; schema (date + description + petId) in the OpenAPI spec, wired through a new MapStruct mapping:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// VisitMapper.java&lt;/span&gt;
&lt;span class="nd"&gt;@Mapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ignore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@Mapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"petId"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"pet.id"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nc"&gt;Visit&lt;/span&gt; &lt;span class="nf"&gt;toVisit&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;VisitCreateDto&lt;/span&gt; &lt;span class="n"&gt;visitCreateDto&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;id&lt;/code&gt; is deliberately ignored (it's server-generated), and &lt;code&gt;petId&lt;/code&gt; on the incoming DTO maps straight onto the JPA relationship (&lt;code&gt;pet.id&lt;/code&gt;) MapStruct needs to build the entity. Once &lt;code&gt;petId&lt;/code&gt; flowed through the request correctly, this failure disappeared for good — replaced by a far more interesting one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug 2: DELETE Never Succeeds
&lt;/h2&gt;

&lt;p&gt;Six DELETE operations were failing with 404 instead of the expected 200. Here's the actual output from one of them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Testing scenario "Delete an owner by ID. Response: Owner details found and returned."
API: DELETE /owners/(ownerId:number) -&amp;gt; 200

  &amp;gt;&amp;gt; RESPONSE.STATUS

      R0002: HTTP status mismatch
      Documentation: https://docs.specmatic.io/rules#r0002
      Summary: The HTTP status code does not match the expected status code
      defined in the specification

      Specification expected status 200 but response contained status 404
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The obvious diagnosis: their ID path parameters had no &lt;code&gt;minimum&lt;/code&gt;/&lt;code&gt;maximum&lt;/code&gt; bounds in the spec — unlike their sibling GET/PUT operations for the same resource — so Specmatic generated a fully random &lt;code&gt;int32&lt;/code&gt;, which almost never matched a real seeded row.&lt;/p&gt;

&lt;p&gt;The fix looked trivial: add the same bounds the GET/PUT operations already had. I tried it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It made things worse.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With valid bounds, Specmatic now generated an ID that &lt;em&gt;did&lt;/em&gt; exist in the database — and the DELETE call &lt;em&gt;actually succeeded&lt;/em&gt;, permanently removing a real row (and its dependent pets/visits). That cascaded into 20+ unrelated failures later in the same test run, because other scenarios still expected that data to exist.&lt;/p&gt;

&lt;p&gt;So I tried a second approach: a named example tying one specific ID to the DELETE scenario. Same cascade — the delete still went through for real.&lt;/p&gt;

&lt;p&gt;Third attempt: dedicated "throwaway" seed rows that no other test depended on, specifically for DELETE to target. By the time the DELETE scenario ran, an earlier &lt;code&gt;POST&lt;/code&gt; scenario in the &lt;em&gt;same test run&lt;/em&gt; had already filled that "safe" slot with real data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Three independent experiments, three identical failure modes.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Actual Root Cause
&lt;/h2&gt;

&lt;p&gt;None of this was a schema bug. &lt;code&gt;ContractTest&lt;/code&gt; runs its dynamically generated scenarios against a &lt;strong&gt;live, already-running Spring Boot instance over plain HTTP&lt;/strong&gt;, with no reset between scenarios. Any test that successfully mutates data can affect any other test that runs later in the same suite — and the open-source &lt;code&gt;SpecmaticContractTest&lt;/code&gt; JUnit 5 interface has no documented hook to reset application/database state between individual generated scenarios.&lt;/p&gt;

&lt;p&gt;I confirmed this wasn't fixable at the schema level. It required test-infrastructure changes (per-scenario database reset) that are out of scope for a contract-testing pass — this is a genuine, separate piece of work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verifying the Setup Was Actually Correct
&lt;/h2&gt;

&lt;p&gt;Along the way, a peer review caught two real issues worth mentioning:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;schemaResiliencyTests&lt;/code&gt; had drifted between local and committed config&lt;/strong&gt; — my local file correctly had &lt;code&gt;all&lt;/code&gt;, but the committed &lt;code&gt;specmatic.yaml&lt;/code&gt; on GitHub still said &lt;code&gt;none&lt;/code&gt;. That one config file had simply never been committed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Six Java files with real exception-handling fixes&lt;/strong&gt; (returning proper 4xx instead of 500 for malformed requests) had also been sitting uncommitted locally for a while — this was silently causing CI to report 125 failures instead of the expected 7, because CI only sees what's actually pushed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both were fixed and pushed, bringing CI back in line with local results.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Learnings
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. A "simple" schema fix can hide a cascading side effect.&lt;/strong&gt;&lt;br&gt;
Adding &lt;code&gt;minimum&lt;/code&gt;/&lt;code&gt;maximum&lt;/code&gt; bounds looked like the obvious, safe fix for random-ID failures. It was correct in isolation — but the moment a DELETE could succeed for real, it exposed the lack of test isolation. The fix wasn't wrong; the environment it ran in wasn't ready for it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Repeat the experiment before you trust the result.&lt;/strong&gt;&lt;br&gt;
The DELETE-cascade problem wasn't obvious after one attempt — it took three independent approaches (schema bounds, named examples, dedicated seed rows) hitting the identical failure mode before I was confident it was architectural, not incidental.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Local ≠ committed ≠ CI.&lt;/strong&gt;&lt;br&gt;
The &lt;code&gt;schemaResiliencyTests&lt;/code&gt; drift is a reminder that "it works on my machine" isn't the same as "it's actually in the repository." Regularly diffing local state against &lt;code&gt;git status&lt;/code&gt; and CI output catches this kind of silent gap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Contract testing can find bugs in the test suite, not just the API.&lt;/strong&gt;&lt;br&gt;
This was the most valuable lesson from this project. Specmatic didn't just validate the OpenAPI contract — running it honestly, without forcing a green build, surfaced a structural limitation in how the tests themselves are built.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current Status
&lt;/h2&gt;

&lt;p&gt;221 tests, 214 passing, 7 known and root-cause-documented failures — CI now consistently matches local results (it still reports failure because of those 7 documented cases, which is expected, not a regression). Full write-up, including the three-experiment DELETE investigation, in the &lt;a href="https://github.com/KRameshr/spring-petclinic-rest#contract-testing-with-specmatic" rel="noopener noreferrer"&gt;repository README&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Where my first Specmatic project taught me that schema constraints are testing instructions, this one taught me that a contract test suite is only as reliable as the infrastructure it runs on. Fixing the app's contract violations was straightforward; discovering — and clearly documenting, rather than papering over — a genuine gap in test isolation was the harder and more useful part of the work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a href="https://github.com/KRameshr/spring-petclinic-rest" rel="noopener noreferrer"&gt;https://github.com/KRameshr/spring-petclinic-rest&lt;/a&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>architecture</category>
      <category>springboot</category>
      <category>testing</category>
    </item>
    <item>
      <title>Integrating Specmatic Contract Testing into a Spring Boot Banking API: My Experience</title>
      <dc:creator>Kuruba Ramesh</dc:creator>
      <pubDate>Fri, 12 Jun 2026 19:37:56 +0000</pubDate>
      <link>https://dev.to/krameshr/how-i-integrated-specmatic-contract-testing-into-a-real-banking-api-valuemeters-cak</link>
      <guid>https://dev.to/krameshr/how-i-integrated-specmatic-contract-testing-into-a-real-banking-api-valuemeters-cak</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;Hello, I am &lt;strong&gt;Kuruba Ramesh&lt;/strong&gt;, a Full Stack Developer specialising in the MERN Stack and Java Spring Boot. As part of the &lt;strong&gt;Specmatic Full Stack AI Engineering Internship Assessment&lt;/strong&gt;, I completed a hands-on assignment focused on &lt;strong&gt;Spec-First Engineering&lt;/strong&gt; and contract-driven API development.&lt;/p&gt;

&lt;p&gt;The assessment required me to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Complete the Specademy course on Spec-First Engineering.&lt;/li&gt;
&lt;li&gt;Integrate Specmatic into a real-world Spring Boot application.&lt;/li&gt;
&lt;li&gt;Configure automated contract testing and CI.&lt;/li&gt;
&lt;li&gt;Document my implementation, challenges, and learnings.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For this assignment, I integrated &lt;strong&gt;Specmatic&lt;/strong&gt; into my Banking API project (ValueMeters) and automated provider contract testing using GitHub Actions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Portfolio:&lt;/strong&gt; &lt;a href="https://krameshdev.vercel.app/" rel="noopener noreferrer"&gt;https://krameshdev.vercel.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This article explains my implementation journey, the challenges I encountered, how I solved them, and the practical lessons I learned while working with Specmatic.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is Specmatic?
&lt;/h2&gt;

&lt;p&gt;Before taking the Specademy course, I was familiar with unit testing and integration testing, but contract testing was completely new to me.&lt;/p&gt;

&lt;p&gt;One concept from the course immediately stood out:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Contract testing is compiler safety for API calls.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In a monolithic application, a compiler catches type mismatches before the application runs. In distributed systems and microservices, applications communicate over HTTP. If one service changes a request or response structure, another service may break without warning. These issues often appear only during runtime.&lt;/p&gt;

&lt;p&gt;Specmatic solves this problem by turning API specifications into executable contracts.&lt;/p&gt;

&lt;p&gt;Using an OpenAPI specification, Specmatic automatically validates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Request structures&lt;/li&gt;
&lt;li&gt;Response schemas&lt;/li&gt;
&lt;li&gt;Status codes&lt;/li&gt;
&lt;li&gt;Headers&lt;/li&gt;
&lt;li&gt;Endpoint implementations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the implementation does not match the contract, the test fails immediately.&lt;/p&gt;




&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;For this assessment, I used my Banking API project called ValueMeters.&lt;/p&gt;

&lt;h3&gt;
  
  
  Technology Stack
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Backend&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Java 17&lt;/li&gt;
&lt;li&gt;Spring Boot 2.7.18&lt;/li&gt;
&lt;li&gt;Spring Security&lt;/li&gt;
&lt;li&gt;JWT Authentication&lt;/li&gt;
&lt;li&gt;Spring Data JPA&lt;/li&gt;
&lt;li&gt;MySQL&lt;/li&gt;
&lt;li&gt;Specmatic 2.48.0&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Frontend&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React 18&lt;/li&gt;
&lt;li&gt;Vite&lt;/li&gt;
&lt;li&gt;Tailwind CSS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Documentation&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SpringDoc OpenAPI 3.0&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The application exposes 13 REST APIs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;/auth/register&lt;/td&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;Register new user&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/auth/login&lt;/td&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;Login and get JWT token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/account/user/{userId}&lt;/td&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;Get account by user ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/account/{accountNumber}&lt;/td&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;Get account by account number&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/transaction/deposit/{accountId}&lt;/td&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;Deposit money&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/transaction/withdraw/{accountId}&lt;/td&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;Withdraw money&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/transaction/transfer/{fromAccountId}&lt;/td&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;Transfer money&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/transaction/history/{accountId}&lt;/td&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;Get transaction history&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/expense/add/{accountId}&lt;/td&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;Add expense&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/expense/list/{accountId}&lt;/td&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;Get all expenses&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/expense/summary/{accountId}&lt;/td&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;Get expense summary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/budget/set/{accountId}&lt;/td&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;Set budget limits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/budget/get/{accountId}&lt;/td&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;Get budget limits&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Since the project already used SpringDoc OpenAPI, it was an ideal candidate for Specmatic integration.&lt;/p&gt;




&lt;h2&gt;
  
  
  Setting Up Specmatic
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Adding the Dependency
&lt;/h3&gt;

&lt;p&gt;I added the Specmatic JUnit support dependency (version 2.48.0) and Spring Boot Actuator, which Specmatic uses to discover all registered endpoints and enforce coverage governance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the OpenAPI Specification
&lt;/h3&gt;

&lt;p&gt;I created an OpenAPI specification describing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Request schemas&lt;/li&gt;
&lt;li&gt;Response schemas&lt;/li&gt;
&lt;li&gt;Path parameters&lt;/li&gt;
&lt;li&gt;Status codes (200, 400, 404)&lt;/li&gt;
&lt;li&gt;Error responses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This specification became the single source of truth for contract validation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuring specmatic.yaml
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
&lt;span class="na"&gt;systemUnderTest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;definitions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;definition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;filesystem&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
          &lt;span class="na"&gt;specs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;openapi.json&lt;/span&gt;
                &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;openapi&lt;/span&gt;
    &lt;span class="na"&gt;runOptions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;openapi&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
        &lt;span class="na"&gt;baseUrl&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:9000"&lt;/span&gt;
        &lt;span class="na"&gt;actuatorUrl&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:9000/actuator/mappings"&lt;/span&gt;
        &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PATH!='/api-docs,/swagger-ui'"&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;examples&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;directories&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;examples&lt;/span&gt;

&lt;span class="na"&gt;specmatic&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;settings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;schemaResiliencyTests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;all&lt;/span&gt;
  &lt;span class="na"&gt;governance&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;successCriteria&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;maxMissedOperationsInSpec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
      &lt;span class="na"&gt;minCoveragePercentage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;
      &lt;span class="na"&gt;enforce&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key additions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;actuatorUrl&lt;/code&gt; — Specmatic discovers all registered endpoints via Spring Actuator&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;examples&lt;/code&gt; directory — externalized test examples for deterministic data&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;governance&lt;/code&gt; — enforces 100% coverage and zero missed operations&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Biggest Challenge: JWT Authentication
&lt;/h2&gt;

&lt;p&gt;My Banking API uses JWT authentication. When Specmatic generated requests, Spring Security rejected them before they reached the controllers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution: Separate Test Security Configuration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Profile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"!test"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="nd"&gt;@EnableWebSecurity&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SecurityConfig&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Production JWT configuration&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="nd"&gt;@Profile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"test"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestSecurityConfig&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;SecurityFilterChain&lt;/span&gt; &lt;span class="nf"&gt;testFilterChain&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpSecurity&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;http&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;csrf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;csrf&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;csrf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;disable&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;authorizeHttpRequests&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;anyRequest&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;permitAll&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Creating the Contract Test
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@SpringBootTest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;webEnvironment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SpringBootTest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;WebEnvironment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;DEFINED_PORT&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@ActiveProfiles&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"test"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@Sql&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scripts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/data.sql"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;executionPhase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ExecutionPhase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BEFORE_TEST_METHOD&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BankingContractTest&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;SpecmaticJUnitSupport&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Important Lessons
&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;DEFINED_PORT&lt;/code&gt; instead of &lt;code&gt;RANDOM_PORT&lt;/code&gt; — With RANDOM_PORT, Spring Boot starts on a random port while Specmatic expects the port defined in the OpenAPI specification.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;@Sql&lt;/code&gt; to reseed test data before each test method — this ensures deterministic state for all provider test scenarios.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Most Interesting Debugging Challenge: Resiliency Test Failures
&lt;/h2&gt;

&lt;p&gt;The most valuable learning from this assignment came from debugging Specmatic's positive resiliency test failures.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;My first positive provider test passed successfully. However, the second positive resiliency execution sent a randomly generated email such as &lt;code&gt;ahgtg@vismx.com&lt;/code&gt; instead of &lt;code&gt;test@example.com&lt;/code&gt;, causing the provider to correctly return &lt;code&gt;400 Invalid credentials&lt;/code&gt; instead of the expected &lt;code&gt;200&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Root Cause
&lt;/h3&gt;

&lt;p&gt;Specmatic's positive resiliency tests don't just replay example values — they actively generate randomised but schema-valid data for each field. Since my &lt;code&gt;email&lt;/code&gt; field had &lt;code&gt;"format": "email"&lt;/code&gt; in the schema, Specmatic generated random but structurally valid emails. My database only contained the seeded &lt;code&gt;test@example.com&lt;/code&gt;, so login correctly returned 400, but Specmatic expected 200.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"format"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"example"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"test@example.com"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"example"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"test@example.com"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Removing &lt;code&gt;"format": "email"&lt;/code&gt; stopped Specmatic from generating random email-format values during resiliency runs. The example value was then used instead, which matched the seeded test data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; This did not weaken validation. Spring's &lt;code&gt;@Email&lt;/code&gt; and &lt;code&gt;@Valid&lt;/code&gt; annotations still enforce email format at runtime. The schema change only affects how Specmatic generates test data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Register Endpoint Fix
&lt;/h3&gt;

&lt;p&gt;The register endpoint also needed a dedicated externalized example file (&lt;code&gt;examples/auth_register_success.json&lt;/code&gt;). Without it, Specmatic logged a warning that it was ignoring the inline example, and fell back to fully random schema-based data.&lt;/p&gt;




&lt;h2&gt;
  
  
  Expanding to Full API Coverage
&lt;/h2&gt;

&lt;p&gt;After the initial fixes, I expanded the OpenAPI specification to cover all 13 endpoints — including account, transaction, expense, and budget APIs.&lt;/p&gt;

&lt;p&gt;For each endpoint I:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Defined correct request schemas (removing optional fields that caused resiliency mutation failures)&lt;/li&gt;
&lt;li&gt;Added appropriate response codes (200, 400, 404)&lt;/li&gt;
&lt;li&gt;Created externalized positive and negative example files in &lt;code&gt;examples/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Fixed service layer exceptions to return correct HTTP status codes (&lt;code&gt;AccountNotFoundException&lt;/code&gt; → 404, &lt;code&gt;InsufficientBalanceException&lt;/code&gt; → 400)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Final result: 96/96 tests passing, 100% API Coverage&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  GitHub Actions CI Integration
&lt;/h2&gt;

&lt;p&gt;After completing the local integration, I automated contract testing using GitHub Actions.&lt;/p&gt;

&lt;p&gt;The workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Builds the Spring Boot application&lt;/li&gt;
&lt;li&gt;Starts the application in the test profile&lt;/li&gt;
&lt;li&gt;Executes all contract and resiliency tests&lt;/li&gt;
&lt;li&gt;Enforces governance — 0 missed operations, 100% coverage&lt;/li&gt;
&lt;li&gt;Generates Specmatic HTML reports&lt;/li&gt;
&lt;li&gt;Publishes reports to GitHub Pages&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Key Learnings
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Schema Constraints Drive Resiliency Test Data
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;format: email&lt;/code&gt; in OpenAPI tells Specmatic to generate random valid-looking emails during resiliency tests. Removing the format constraint pins generation to the example value. Schema constraints and application-level validation serve different purposes.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Every Endpoint Needs Its Own Positive Example
&lt;/h3&gt;

&lt;p&gt;Without an externalized example, Specmatic falls back to fully random schema-based data that may not match seeded test data.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Correct Exception Types Matter
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;RuntimeException&lt;/code&gt; → 500, &lt;code&gt;AccountNotFoundException&lt;/code&gt; → 404, &lt;code&gt;IllegalArgumentException&lt;/code&gt; → 400. Using the correct exception types is essential for contract test accuracy.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Governance Enforces Quality Gates
&lt;/h3&gt;

&lt;p&gt;Specmatic's governance configuration enforces 100% coverage and zero missed operations, making it impossible to accidentally leave endpoints untested.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Provider Testing Requires Deterministic Test Data
&lt;/h3&gt;

&lt;p&gt;Provider tests are highly dependent on predictable application state. &lt;code&gt;@Sql&lt;/code&gt; reseeding before each test method is essential.&lt;/p&gt;




&lt;h2&gt;
  
  
  Future Improvements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Consumer-driven contract testing&lt;/li&gt;
&lt;li&gt;Advanced Specmatic provider examples&lt;/li&gt;
&lt;li&gt;Mock server generation for frontend development&lt;/li&gt;
&lt;li&gt;Expanded error scenario coverage&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This assignment gave me practical experience with &lt;strong&gt;Spec-First Engineering&lt;/strong&gt; and contract-driven API development.&lt;/p&gt;

&lt;p&gt;Before working with Specmatic, I viewed OpenAPI primarily as documentation. Through this project, I learned how an OpenAPI specification can become an executable contract that continuously validates API behaviour throughout development and CI/CD.&lt;/p&gt;

&lt;p&gt;I successfully integrated Specmatic 2.48.0 into a Spring Boot application, achieved 96/96 tests passing with 100% API coverage across 13 endpoints, and implemented governance rules that enforce quality gates on every push.&lt;/p&gt;

&lt;p&gt;The most valuable lesson was understanding how Specmatic's resiliency tests work — they actively mutate data based on schema constraints, not just examples. This insight transformed how I think about OpenAPI specifications: every constraint is a testing instruction, not just documentation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Resources
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Portfolio:&lt;/strong&gt; &lt;a href="https://krameshdev.vercel.app/" rel="noopener noreferrer"&gt;https://krameshdev.vercel.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a href="https://github.com/KRameshr/valuemeters-specmatic" rel="noopener noreferrer"&gt;https://github.com/KRameshr/valuemeters-specmatic&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub Actions:&lt;/strong&gt; &lt;a href="https://github.com/KRameshr/valuemeters-specmatic/actions" rel="noopener noreferrer"&gt;https://github.com/KRameshr/valuemeters-specmatic/actions&lt;/a&gt;&lt;/p&gt;

</description>
      <category>specmatic</category>
      <category>java</category>
      <category>springboot</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
