The `OpenApiService.rebuild()` method was creating a new `PathItem` for each endpoint and calling `addPathItem()`, which replaced any existing `PathItem` at that path. Reviewed-on: #7 Co-authored-by: nenzen <nenzen@dsv.su.se> Co-committed-by: nenzen <nenzen@dsv.su.se>
APImposter 🎭
A small, flexible API mock service designed with microservice environments in mind.
What is APImposter?
APImposter is an easy-to-use service built with Spring Boot, enabling quick mocking of API responses from various systems. It allows you to organize your mock data in YAML files.
Features
- Structured Mock Data: Organize your mock data in YAML files.
- Automatic URL Mapping: Automatically generates URL structure based on your folder hierarchy and YAML file names.
- Hot Reloading: Automatically reloads mock data files when they are changed.
- Dynamic Responses and Global Placeholders: Supports dynamic values in responses based on URL parameters and you can define global placeholders to reuse values across multiple responses and systems.
- Conditional Responses: Return different mock responses based on request data (headers, query, body, or path).
- Swagger UI: Automatically generated Swagger UI from YAML files.
Mocks Structure
Mocks are defined in YAML files and stored in a directory specified via application.properties. You can organize them in nested folders:
/mocks/
├── globals.yaml
├── system1/
│ ├── users.yaml
│ └── index.yaml
└── another-system/
├── endpoint1.yaml
└── subdir/
├── endpoint2.yaml
└── index.yaml
URL Mapping
/system1/users.yamldefines mocks under/system1/users/.../system1/index.yamldefines mocks under/system1/...)- Multiple endpoints can be grouped in one file
- Folders in the YAML file structure become part of the URL path automatically.
Grouped endpoints in users.yaml
# mocks/system1/users.yaml
- method: GET
path: "/"
response:
status: 200
body:
users:
- id: 1
name: "Alice"
- id: 2
name: "Bob"
- method: GET
path: "/{id}"
response:
status: 200
body:
id: "{id:int}"
name: "User {id}"
- method: POST
path: "/"
response:
status: 200
body:
message: "User created"
Base-level grouping via index.yaml
# mocks/system1/index.yaml
- method: GET
path: "/"
response:
status: 200
body:
message: "Welcome to system1"
- method: GET
path: "/info"
response:
status: 200
body:
name: "System One"
version: "1.0"
Placeholder Type Hints
You can include optional type hints in placeholders to ensure JSON values are rendered with correct types.
Supported types
| Type | Placeholder | Output |
|---|---|---|
| string | {name} |
"Alice" |
| int | {id:int} |
42 (number) |
| float | {price:float} |
99.95 |
| boolean | {enabled:bool} |
true |
If parsing fails (e.g. abc for an int), APImposter falls back to the original string.
Relative Date/Time Placeholders
APImposter supports dynamic date and time placeholders that are calculated relative to the current time. This is perfect for testing calendar applications, scheduling systems, or any time-sensitive mock data.
Supported formats
| Format | Example | Description |
|---|---|---|
{datetime+/-offset} |
{datetime+2d4h30m} |
DateTime with flexible time offset |
{datetime+/-Nd@HH:MM} |
{datetime+1d@13:30} |
DateTime N days from now at specific time |
{date+/-Nd} |
{date-7d} |
Date N days from now |
Format specifiers (optional)
Add a format specifier after a colon to control the output format:
| Specifier | Description | Example Output |
|---|---|---|
iso (default) |
ISO standard format | 2025-09-03T15:30:00 |
rfc3339 |
RFC 3339 with UTC timezone | 2025-09-03T15:30:00Z |
| Custom pattern | Any valid DateTimeFormatter pattern | Sep 03, 2025 |
Examples
# Basic relative dates (ISO format)
- method: GET
path: "/events"
response:
body:
todayMeeting: "{datetime+0d@08:00}" # Today at 8:00 AM
tomorrowLunch: "{datetime+1d@13:00}" # Tomorrow at 1:00 PM
nextWeek: "{date+7d}" # Date 7 days from now
pastEvent: "{datetime-2d4h30m}" # 2 days, 4h, 30m ago
yesterday: "{date-1d}" # Yesterday's date
# Different output formats
- method: GET
path: "/calendar"
response:
body:
# RFC3339 format
eventTimeRfc: "{datetime+1d@15:30:rfc3339}" # "2025-09-03T15:30:00Z"
# Custom formats
eventDate: "{date+1d:MMM dd, yyyy}" # "Sep 03, 2025"
timeOnly: "{datetime+0d@14:30:HH:mm}" # "14:30"
shortDate: "{date+7d:MM/dd/yy}" # "09/09/25"
Time components for offset format
d= daysh= hoursm= minutes
All components are optional and can be combined: {datetime+1d2h}, {datetime+30m}, {datetime-4h15m}
Note: If a custom format is invalid, the system automatically falls back to ISO format to prevent errors.
Example Requests and Responses
| YAML file | Request | Response Example |
|---|---|---|
system1/users.yaml |
GET /system1/users |
{ "users": [{ "id": 1 }, { "id": 2 }] } |
system1/users.yaml |
GET /system1/users/42 |
{ "id": 42, "name": "User 42" } |
system1/users.yaml |
POST /system1/users |
{ "message": "User created" } |
system1/index.yaml |
GET /system1 |
{ "message": "Welcome to system1" } |
system1/index.yaml |
GET /system1/info |
{ "name": "System One", "version": "1.0" } |
another-system/subdir/index.yaml |
GET /another-system/subdir/... |
(Defined response in file) |
Globals
Global values can be defined in a dedicated file globals.yaml:
# mocks/globals.yaml
globals:
currentUserId: "user-123"
You can reference global values using {globals.key} or {globals.key:type} in responses.
Conditional Responses
APImposter supports conditional responses, allowing different mock responses to be returned based on request values - query parameters, headers, path variables, or body fields.
This is useful for simulating realistic API behavior, such as authentication, filtering, or alternate success/error outcomes.
Supported condition types:
path: matches against path variablesquery: matches query parameters (?filter=active)headers: matches HTTP headers (case-insensitive)body: matches top-level JSON body fields (for POST/PUT)
Example: conditionals by path
- method: "GET"
path: "/users/{id}"
conditionalResponses:
- conditions:
path:
id: "1"
response:
status: 200
headers:
Content-Type: application/json
body:
id: 1
name: "Lennart"
- conditions:
path:
id: "2"
response:
status: 200
headers:
Content-Type: application/json
body:
id: 2
name: "Kurre"
response:
status: 404
headers:
Content-Type: application/json
body:
error: "User not found"
Example: conditionals by header
- method: "GET"
path: "/secure"
conditionalResponses:
- conditions:
headers:
authorization: "Bearer abc123"
response:
status: 200
headers:
Content-Type: application/json
body:
message: "Access granted"
response:
status: 401
headers:
Content-Type: application/json
body:
error: "Unauthorized"
Example: conditionals by query parameter
- method: "GET"
path: "/products"
conditionalResponses:
- conditions:
query:
category: "books"
response:
status: 200
headers: { Content-Type: "application/json" }
body:
products:
- id: "b1"
name: "Data Structures and Algorithms in Java"
- conditions:
query:
category: "tech"
response:
status: 200
headers: { Content-Type: "application/json" }
body:
products:
- id: "t1"
name: "Keyboard"
response:
status: 200
headers: { Content-Type: "application/json" }
body:
products: []
Example: conditionals by request body
- method: "POST"
path: "/login"
conditionalResponses:
- conditions:
body:
username: "admin"
password: "secret"
response:
status: 200
headers:
Content-Type: application/json
body:
token: "abc123"
response:
status: 403
headers:
Content-Type: application/json
body:
error: "Invalid credentials"
Testing with curl
curl http://localhost:8080/mocks/users/1
curl -H "Authorization: Bearer abc123" http://localhost:8080/mocks/secure
curl "http://localhost:8080/mocks/products?category=books"
curl -X POST http://localhost:8080/mocks/login \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "secret"}'
Conditions are evaluated in order, and the first matching block wins. If no condition matches, the fallback response: is used.
Configuration
In application.properties you can configure the following properties:
mock.file-path: The path to the directory containing your mock data YAML files.mock.reload-interval-ms: The interval in milliseconds at which the service checks for changes in the mock data files.mock.base-path: The base path for the mock service. This is prepended to all generated URLs. Default is/mocks.mock.default-delay-ms: The default delay in milliseconds for responses. This can be overridden in the YAML files.
Swagger UI
OpenAPI Specification is generated from the mock data YAML files and can be accessed via Swagger UI. You will find it at:
http://localhost:8080/swagger
Quick Start
Clone the project and run it simply with:
./mvnw spring-boot:run
or with Docker:
docker-compose up
🎭 Good luck and happy mocking! 🎭