Skip to main content

OpenAPI Specification

Complete Code
The end result of the code developed in this document can be found in the GitHub monorepo springboot-demo-projects, under the tag openapi-spec.

Given a Film with the following structure:

{
"id": 42,
"title": "ACADEMY DINOSAUR",
"description": "A Epic Drama of a Feminist And a Mad Scientist who must Battle a Teacher in The Canadian Rockies",
"releaseYear": 2006,
"rating": "PG",
"lengthMinutes": 86,
"language": "English"
}

We want our Spring Boot application to expose these endpoints:

  • /api/films list all films.
  • /api/films/{id} get film by identifier.
Files to Create/Modify
File Tree
├── ...
└── src
├── main
│ ├── ...
│ └── resources
│ ├── ...
│ └── openapi.yaml
└── ...

OpenAPI Specification

The OpenAPI Specification (OAS) defines a standard, language-agnostic interface to HTTP APIs which allows both humans and computers to discover and understand the capabilities of the service without access to source code, documentation, or through network traffic inspection. When properly defined, a consumer can understand and interact with the remote service with a minimal amount of implementation logic.

For better visualization you can copy-paste the OpenAPI Specification YAML file from the codebase into Swagger Editor or use OpenAPI (Swagger) Editor in IntelliJ IDEA.

Preview Browser 53739d4afeda849a502ca156fa53e9fb
  • All error responses would look like this:

    {
    "instance": "/api/something",
    "timestamp": "2026-01-03T17:11:50.826722328Z",
    "trace": "9482c151-b417-43ff-9dbb-ee12b84e5d99",
    "status": 404,
    "title": "Not Found",
    "detail": "No static resource for request '/'."
    }
  • /api/films responses would look like this:

    {
    "instance": "/api/films",
    "timestamp": "2026-01-03T17:11:50.826722328Z",
    "trace": "9482c151-b417-43ff-9dbb-ee12b84e5d99",
    "status": 200,
    "data": [
    {
    "id": 42,
    "title": "ACADEMY DINOSAUR",
    "description": "An Epic Drama of a Feminist And a Mad Scientist",
    "releaseYear": 2006,
    "rating": "PG",
    "lengthMinutes": 86,
    "language": "English"
    }
    ]
    }
  • /api/films/{id} responses would look like this:

    {
    "instance": "/api/films/42",
    "timestamp": "2026-01-03T17:11:50.826722328Z",
    "trace": "9482c151-b417-43ff-9dbb-ee12b84e5d99",
    "status": 200,
    "data": {
    "id": 42,
    "title": "ACADEMY DINOSAUR",
    "description": "An Epic Drama of a Feminist And a Mad Scientist",
    "releaseYear": 2006,
    "rating": "PG",
    "lengthMinutes": 86,
    "language": "English"
    }
    }

The Envelope Pattern

You might be looking at the response examples and thinking, "What's with all that extra fluff? Why wrap the actual film data inside a data object?"

{
"instance": "/api/films/42",
"timestamp": "...",
"trace": "...",
"status": 200,
"data": {
"id": 42,
"title": "ACADEMY DINOSAUR",
//...
}
}

That is a deliberate choice called the Envelope Pattern. And once you start using it, you'll never go back.

Think of it like physical mail. Every piece of mail you get, whether it's a birthday card or a bill, comes in an envelope. The envelope has standard information on it: a return address, a destination address, a stamp. The actual message is inside.

Our API responses work the same way. The ResponseMetadata schema is our envelope.

# Base metadata for all responses
ResponseMetadata:
type: object
properties:
instance:
# ...
status:
# ...
timestamp:
# ...
trace:
# ...

Every single response from the API, whether it's a success (200 OK) or an error (404 Not Found, 500 Internal Server Error, etc.), will have this same top-level structure.

  • The actual payload goes inside the data property for successful responses.
  • For errors, we swap data for title and detail so we keep adhering to Problem Details for HTTP APIs.

Why Bother?

Consistency. That's the whole game.

  1. Client side sanity: The developer building the frontend or mobile app can write one piece of code to handle all API responses. They always know where to find the trace ID to show a user reporting a bug. They can build a generic error handler that always looks for title and detail. They aren't guessing whether the payload is the object itself, an array, or some weird error shape.
  2. Future proofing: What if we need to add some other metadata? No problem. We just add a new field to the envelope level. The data part remains untouched, and we don't break the client's parsing logic for the actual film data.
  3. It just looks professional: An API with a consistent response structure feels solid and well-thought-out. An inconsistent one feels amateurish and is a pain to work with.

Pagination

The/api/films endpoint "lists all films." But if you take that literally in the real world, you're gonna have a bad time.

Imagine your database has a million films. Trying to pull all of them in one go is a recipe for disaster. Your database will crash, your server's memory will evaporate, and the poor user's browser will crash trying to render a colossal JSON payload.

This is where pagination comes in. It's the simple idea of breaking a large result set into smaller, manageable "pages." Instead of asking for all the films, you ask for "page 1 with 20 films," then "page 2 with 20 films," and so on.

This is standard practice everywhere. Think about Google search results or browsing products on Amazon. You never get everything at once; you get a page and a "Next" button.

For the sake of keeping things simple right now, the specification doesn't include pagination parameters. We'll add proper pagination later.