---
name: api-design
description: "Add OpenAPI docs and REST conventions to an existing Spring Boot 4 Maven project — SpringDoc, API versioning, ProblemDetail error schemas, DTO conventions. Use when asked to add OpenAPI or Swagger, document or version an API, or define REST conventions. Not for auth — use spring-security."
---
# API Design Skill
Adds OpenAPI/SpringDoc documentation and REST conventions to an existing Spring Boot 4 project.
`SKILL_DIR` = directory containing this SKILL.md file.
Load `SKILL_DIR/references/openapi-conventions.md` before annotating controllers — it covers
versioning strategy, annotation style, DTO naming, and the error schema used here.
---
## Step 0 — Gather inputs
| Field | Required | Notes |
|-------|----------|-------|
| `title` | No | API title; defaults to project name from `pom.xml` |
| `version` | No | API version; defaults to `1.0.0` |
| `description` | No | short API description |
| `basePath` | No | `/api` (default) or `/api/v1` |
| `annotateControllers` | No | `false` (default) — only annotate if user explicitly asks |
---
## Step 1 — Read the project
```bash
cat pom.xml
find src/main/java -name '*Controller.java' | head -20
ls src/main/resources/
```
Confirm Spring Boot 4.x and web dependency.
---
## Step 2 — Add dependency
Add to `pom.xml`:
```xml
org.springdoc
springdoc-openapi-starter-webmvc-ui
3.1.1
```
Before writing, verify the latest SpringDoc version:
```bash
curl -s "https://repo1.maven.org/maven2/org/springdoc/springdoc-openapi-starter-webmvc-ui/maven-metadata.xml" \
| python3 -c "import sys,re; print(re.findall(r'(.*?)', sys.stdin.read())[-1])"
```
SpringDoc 2.x is for Spring Boot 3+/SpringDoc 2.x APIs. Do not use `springdoc-openapi-ui` (the 1.x
artifact) on Boot 4.
---
## Step 3 — OpenAPI config
Create `src/main/java//shared/config/OpenApiConfig.java`:
```java
package .shared.config;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.servers.Server;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
@Configuration
public class OpenApiConfig {
@Bean
public OpenAPI openAPI(
@Value("${info.app.title:API}") String title,
@Value("${info.app.version:1.0.0}") String version,
@Value("${info.app.description:}") String description) {
return new OpenAPI()
.info(new Info()
.title(title)
.version(version)
.description(description))
.servers(List.of(
new Server().url("/").description("Default")
));
}
}
```
---
## Step 4 — Update application.yml
Add:
```yaml
springdoc:
api-docs:
path: /api-docs
swagger-ui:
path: /swagger-ui.html
operations-sorter: method
tags-sorter: alpha
default-produces-media-type: application/json
info:
app:
title: ${APP_TITLE:}
version: ${APP_VERSION:1.0.0}
description: ${APP_DESCRIPTION:}
```
If `spring-security` is present, permit `/swagger-ui/**`, `/api-docs/**`, and `/v3/api-docs/**`.
---
## Step 5 — Global error schema
Add `src/main/java//shared/api/ProblemDetailSchema.java` to document the `ProblemDetail`
responses globally:
```java
package .shared.api;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import org.springframework.http.ProblemDetail;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@ApiResponse(responseCode = "400", description = "Bad request",
content = @Content(schema = @Schema(implementation = ProblemDetail.class)))
@ApiResponse(responseCode = "404", description = "Not found",
content = @Content(schema = @Schema(implementation = ProblemDetail.class)))
@ApiResponse(responseCode = "409", description = "Conflict",
content = @Content(schema = @Schema(implementation = ProblemDetail.class)))
@ApiResponse(responseCode = "500", description = "Internal server error",
content = @Content(schema = @Schema(implementation = ProblemDetail.class)))
public @interface ProblemDetailSchema {
}
```
Use it on controllers:
```java
@ProblemDetailSchema
@RestController
@RequestMapping("/api/v1/jobs")
public class JobController {
}
```
---
## Step 6 — API versioning convention
Create `src/main/java//shared/api/ApiVersion.java`:
```java
package .shared.api;
public final class ApiVersion {
private ApiVersion() {}
public static final String V1 = "/api/v1";
}
```
Use `ApiVersion.V1` in `@RequestMapping` on controllers. Do not hardcode `/api/v1` in multiple
controllers.
---
## Step 7 — Annotate controllers (optional)
Only if `annotateControllers` is `true`. Add `@Tag`, `@Operation`, `@Schema`, and example DTO fields.
Example:
```java
@Tag(name = "Jobs", description = "Job listing management")
@RestController
@RequestMapping(ApiVersion.V1 + "/jobs")
public class JobController {
@Operation(summary = "Create a job", description = "Creates a new job listing")
@PostMapping
public ResponseEntity create(@Valid @RequestBody JobRequest request) { ... }
}
```
Never annotate internal/admin-only endpoints in a way that exposes them to public docs if they should
not be public. Use `@Hidden` for internal endpoints.
---
## Step 8 — API conventions doc
Create `docs/api-conventions.md`:
```markdown
# API conventions
## Base path
All endpoints are under `/api/v1` unless marked otherwise.
## Content type
JSON only. Request bodies must include `Content-Type: application/json`.
## Errors
Errors follow RFC 9457 `ProblemDetail`:
```json
{
"type": "about:blank",
"title": "Resource Not Found",
"status": 404,
"detail": "Job not found: 42"
}
```
## Pagination
- Keyset/cursor pagination is the default for user-facing lists.
- Offset pagination is available only for admin/reporting endpoints.
- See `docs/pagination.md` for implementation details.
## Versioning
URL path versioning (`/api/v1/`). A new breaking version gets a new path (`/api/v2/`).
```
---
## Step 9 — Run and report
```bash
./mvnw test
```
Then verify the docs render at:
```
http://localhost:8080/swagger-ui.html
http://localhost:8080/api-docs
```
Report:
- Dependency and version added
- `OpenApiConfig`, `ProblemDetailSchema`, `ApiVersion` created
- `application.yml` updated
- Whether controllers were annotated
- `docs/api-conventions.md` created
- Next step: add `spring-security` to protect documented endpoints