Introduction
Hello, I am Kuruba Ramesh, a Full Stack Developer specialising in the MERN Stack and Java Spring Boot. As part of the Specmatic Full Stack AI Engineering Internship Assessment, I completed a hands-on assignment focused on Spec-First Engineering and contract-driven API development.
The assessment required me to:
- Complete the Specademy course on Spec-First Engineering.
- Integrate Specmatic into a real-world Spring Boot application.
- Configure automated contract testing and CI.
- Document my implementation, challenges, and learnings.
For this assignment, I integrated Specmatic into my Banking API project (ValueMeters) and automated provider contract testing using GitHub Actions.
Portfolio: https://krameshdev.vercel.app/
This article explains my implementation journey, the challenges I encountered, how I solved them, and the practical lessons I learned while working with Specmatic.
What is Specmatic?
Before taking the Specademy course, I was familiar with unit testing and integration testing, but contract testing was completely new to me.
One concept from the course immediately stood out:
Contract testing is compiler safety for API calls.
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.
Specmatic solves this problem by turning API specifications into executable contracts.
Using an OpenAPI specification, Specmatic automatically validates:
- Request structures
- Response schemas
- Status codes
- Headers
- Endpoint implementations
If the implementation does not match the contract, the test fails immediately.
Project Overview
For this assessment, I used my Banking API project called ValueMeters.
Technology Stack
Backend
- Java 17
- Spring Boot 2.7.18
- Spring Security
- JWT Authentication
- Spring Data JPA
- MySQL
- Specmatic 2.48.0
Frontend
- React 18
- Vite
- Tailwind CSS
Documentation
- SpringDoc OpenAPI 3.0
The application exposes 13 REST APIs:
| Endpoint | Method | Description |
|---|---|---|
| /auth/register | POST | Register new user |
| /auth/login | POST | Login and get JWT token |
| /account/user/{userId} | GET | Get account by user ID |
| /account/{accountNumber} | GET | Get account by account number |
| /transaction/deposit/{accountId} | POST | Deposit money |
| /transaction/withdraw/{accountId} | POST | Withdraw money |
| /transaction/transfer/{fromAccountId} | POST | Transfer money |
| /transaction/history/{accountId} | GET | Get transaction history |
| /expense/add/{accountId} | POST | Add expense |
| /expense/list/{accountId} | GET | Get all expenses |
| /expense/summary/{accountId} | GET | Get expense summary |
| /budget/set/{accountId} | POST | Set budget limits |
| /budget/get/{accountId} | GET | Get budget limits |
Since the project already used SpringDoc OpenAPI, it was an ideal candidate for Specmatic integration.
Setting Up Specmatic
Adding the Dependency
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.
Creating the OpenAPI Specification
I created an OpenAPI specification describing:
- Request schemas
- Response schemas
- Path parameters
- Status codes (200, 400, 404)
- Error responses
This specification became the single source of truth for contract validation.
Configuring specmatic.yaml
version: 3
systemUnderTest:
service:
definitions:
- definition:
source:
filesystem:
directory: .
specs:
- spec:
path: openapi.json
type: openapi
runOptions:
openapi:
type: test
baseUrl: "http://localhost:9000"
actuatorUrl: "http://localhost:9000/actuator/mappings"
filter: "PATH!='/api-docs,/swagger-ui'"
data:
examples:
- directories:
- examples
specmatic:
settings:
test:
schemaResiliencyTests: all
governance:
successCriteria:
maxMissedOperationsInSpec: 0
minCoveragePercentage: 100
enforce: true
Key additions:
-
actuatorUrl— Specmatic discovers all registered endpoints via Spring Actuator -
examplesdirectory — externalized test examples for deterministic data -
governance— enforces 100% coverage and zero missed operations
The Biggest Challenge: JWT Authentication
My Banking API uses JWT authentication. When Specmatic generated requests, Spring Security rejected them before they reached the controllers.
Solution: Separate Test Security Configuration
@Profile("!test")
@Configuration
@EnableWebSecurity
public class SecurityConfig {
// Production JWT configuration
}
@Configuration
@Profile("test")
public class TestSecurityConfig {
@Bean
public SecurityFilterChain testFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth.anyRequest().permitAll());
return http.build();
}
}
Creating the Contract Test
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@ActiveProfiles("test")
@Sql(scripts = "/data.sql", executionPhase = ExecutionPhase.BEFORE_TEST_METHOD)
public class BankingContractTest extends SpecmaticJUnitSupport {
}
Important Lessons
Use DEFINED_PORT instead of RANDOM_PORT — With RANDOM_PORT, Spring Boot starts on a random port while Specmatic expects the port defined in the OpenAPI specification.
Use @Sql to reseed test data before each test method — this ensures deterministic state for all provider test scenarios.
The Most Interesting Debugging Challenge: Resiliency Test Failures
The most valuable learning from this assignment came from debugging Specmatic's positive resiliency test failures.
The Problem
My first positive provider test passed successfully. However, the second positive resiliency execution sent a randomly generated email such as ahgtg@vismx.com instead of test@example.com, causing the provider to correctly return 400 Invalid credentials instead of the expected 200.
Root Cause
Specmatic's positive resiliency tests don't just replay example values — they actively generate randomised but schema-valid data for each field. Since my email field had "format": "email" in the schema, Specmatic generated random but structurally valid emails. My database only contained the seeded test@example.com, so login correctly returned 400, but Specmatic expected 200.
The Fix
Before:
"email": {
"type": "string",
"format": "email",
"example": "test@example.com"
}
After:
"email": {
"type": "string",
"example": "test@example.com"
}
Removing "format": "email" stopped Specmatic from generating random email-format values during resiliency runs. The example value was then used instead, which matched the seeded test data.
Important: This did not weaken validation. Spring's @Email and @Valid annotations still enforce email format at runtime. The schema change only affects how Specmatic generates test data.
Register Endpoint Fix
The register endpoint also needed a dedicated externalized example file (examples/auth_register_success.json). Without it, Specmatic logged a warning that it was ignoring the inline example, and fell back to fully random schema-based data.
Expanding to Full API Coverage
After the initial fixes, I expanded the OpenAPI specification to cover all 13 endpoints — including account, transaction, expense, and budget APIs.
For each endpoint I:
- Defined correct request schemas (removing optional fields that caused resiliency mutation failures)
- Added appropriate response codes (200, 400, 404)
- Created externalized positive and negative example files in
examples/ - Fixed service layer exceptions to return correct HTTP status codes (
AccountNotFoundException→ 404,InsufficientBalanceException→ 400)
Final result: 96/96 tests passing, 100% API Coverage
GitHub Actions CI Integration
After completing the local integration, I automated contract testing using GitHub Actions.
The workflow:
- Builds the Spring Boot application
- Starts the application in the test profile
- Executes all contract and resiliency tests
- Enforces governance — 0 missed operations, 100% coverage
- Generates Specmatic HTML reports
- Publishes reports to GitHub Pages
Key Learnings
1. Schema Constraints Drive Resiliency Test Data
format: email 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.
2. Every Endpoint Needs Its Own Positive Example
Without an externalized example, Specmatic falls back to fully random schema-based data that may not match seeded test data.
3. Correct Exception Types Matter
RuntimeException → 500, AccountNotFoundException → 404, IllegalArgumentException → 400. Using the correct exception types is essential for contract test accuracy.
4. Governance Enforces Quality Gates
Specmatic's governance configuration enforces 100% coverage and zero missed operations, making it impossible to accidentally leave endpoints untested.
5. Provider Testing Requires Deterministic Test Data
Provider tests are highly dependent on predictable application state. @Sql reseeding before each test method is essential.
Future Improvements
- Consumer-driven contract testing
- Advanced Specmatic provider examples
- Mock server generation for frontend development
- Expanded error scenario coverage
Conclusion
This assignment gave me practical experience with Spec-First Engineering and contract-driven API development.
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.
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.
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.
Final Resources
Portfolio: https://krameshdev.vercel.app/
GitHub Repository: https://github.com/KRameshr/valuemeters-specmatic
GitHub Actions: https://github.com/KRameshr/valuemeters-specmatic/actions
Top comments (0)