Skip to content
Back to skills

Openapi Contract First

ASecurity

Contract-first OpenAPI workflow — author or update the spec, run the diff gate, regenerate types, write the controller. Use when adding or changing an HTTP endpoint.

  • 62 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 7, 2026
developmentjavaspringgitapi

Works with

  • cli
  • api

Security analysis

A100/100

Scanned November 7, 2026

npx -y skills add loiane/specs-driven-development-spring-angular --skill openapi-contract-first --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Openapi Contract First?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Openapi Contract First
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/loiane-openapi-contract-first/badge)](https://www.skillsdirectory.com/skills/loiane-openapi-contract-first)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: openapi-contract-first
description: Contract-first OpenAPI workflow — author or update the spec, run the diff gate, regenerate types, write the controller. Use when adding or changing an HTTP endpoint.
when_to_use:
  - Phase 3 (Plan) — sketching the OpenAPI delta in `03-design.md`.
  - Phase 4 (Build) — generating types and writing the controller.
  - Phase 6 (Validate) — running `openapi-diff` and reading its report.
authoritative_references:
  - https://springdoc.org/
  - https://github.com/OpenAPITools/openapi-generator
  - https://github.com/OpenAPITools/openapi-diff
---

# OpenAPI contract-first

## Workflow

1. Edit `src/main/resources/openapi/openapi.yaml` (or whatever the project's path is — detect via `detect-stack.sh`).
2. Run the **OpenAPI diff** against the previous version (committed in `_baseline.json`).
3. If breaking, either:
   - Pick a non-breaking alternative (additive field, new endpoint, version the path), OR
   - File an ADR (`adr/NNN-breaking-api-change.md`) and proceed.
4. Generate DTOs (records) via `openapi-generator` Maven plugin.
5. Write the controller; the slice test (`@WebMvcTest`) consumes the generated request/response records.

## Breaking vs non-breaking

Non-breaking (no ADR required):

- Adding a new endpoint.
- Adding an **optional** request field.
- Adding a response field (clients should ignore unknown).
- Loosening a constraint (`min: 1` → `min: 0`).
- Adding a new enum value, **only if** the consumer contract documents that they tolerate unknowns.

Breaking (ADR required):

- Removing or renaming any field.
- Changing a type (`integer` → `string`).
- Tightening a constraint.
- Changing a path or HTTP method.
- Changing an HTTP status for an existing condition.
- Adding a required request field.
- Removing an enum value.

## Springdoc check

If springdoc is on the classpath, also run a **runtime vs static** check: start the app, fetch `/v3/api-docs`, diff against `openapi.yaml`. They must match. The skill's hook for this is `openapi-runtime-vs-static`.

## Generation hint

Configure `openapi-generator` to:

- `generatorName: spring`
- `library: spring-boot`
- `useSpringBoot3: true` (works for Boot 4 too at the time of writing)
- `interfaceOnly: true` (we write the controller; generator only produces interface + DTOs)
- `useTags: true`
- `dateLibrary: java8` (i.e. `java.time`)
- `useJakartaEe: true`
- DTOs as records: `serializationLibrary: jackson`, `additionalModelTypeAnnotations: ""`, and prefer Java records via the `useRecord` flag where supported.

## Worked example

Adding `POST /checkout/{orderId}/gift-card`:

```yaml
paths:
  /checkout/{orderId}/gift-card:
    post:
      operationId: applyGiftCard
      tags: [Checkout]
      parameters:
        - name: orderId
          in: path
          required: true
          schema: { type: string, format: uuid }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ApplyGiftCardRequest' }
      responses:
        '200':
          description: Applied
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ApplyGiftCardResponse' }
        '404': { $ref: '#/components/responses/NotFound' }
        '409': { $ref: '#/components/responses/Conflict' }
components:
  schemas:
    ApplyGiftCardRequest:
      type: object
      required: [code, orderTotalCents]
      properties:
        code: { type: string, minLength: 1 }
        orderTotalCents: { type: integer, format: int32, minimum: 0 }
```

## Anti-patterns

- Hand-writing DTOs that drift from the spec.
- Using `application/x-www-form-urlencoded` for new APIs.
- `additionalProperties: true` on response schemas (loses type safety on the client).
- Returning 200 for errors with a status field in the body.
- One giant `OpenAPI.yaml` with no tags or grouping.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.