Generate OpenAPI docs for your API
Get an OpenAPI 3.1 document, a Swagger v2 document and two browsable UIs for a REST resource, generated from the resource's own annotations with no separate spec file to maintain.
What you'll build
A small Task API that lists, fetches and creates to-do items. Adding two documentation mixins to it serves the API's contract at /openapi.json, /openapi.yaml and /api, and interactive pages at /redoc and /swagger. A short program fetches the OpenAPI document and prints what it describes:
openapi: 3.1.0
title: Task API
GET /tasks - List tasks
Prerequisites and time
- Java 17 or later and Maven 3.
- These dependencies, shown in the POM in step 1:
org.apache.juneau:juneau-rest-server:10.0.0-SNAPSHOT, which contains the documentation mixinsorg.apache.juneau:juneau-rest-mock:10.0.0-SNAPSHOT, used here to call the API without starting a server
- About 10 minutes.
10.0.0-SNAPSHOT is not on Maven Central, so the POM adds the Apache snapshots repository. See the note in the MCP Quickstart and Install Juneau.
Steps
Put each class in src/main/java/com/example/ and start each file with package com.example;. Any package works if you use it everywhere.
1. Create the project
Create an empty directory with this pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>openapidocs</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.release>17</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<juneau.version>10.0.0-SNAPSHOT</juneau.version>
</properties>
<repositories>
<repository>
<id>apache-snapshots</id>
<url>https://repository.apache.org/snapshots/</url>
<releases><enabled>false</enabled></releases>
<snapshots><enabled>true</enabled></snapshots>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.apache.juneau</groupId>
<artifactId>juneau-rest-server</artifactId>
<version>${juneau.version}</version>
</dependency>
<dependency>
<groupId>org.apache.juneau</groupId>
<artifactId>juneau-rest-mock</artifactId>
<version>${juneau.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.6.4</version>
</plugin>
</plugins>
</build>
</project>
2. Describe the data
@Schema adds descriptions to the bean and its properties. They end up in the generated schema for Task.
import org.apache.juneau.commons.*;
/**
* A to-do item.
*/
@Schema(description="A to-do item.")
public class Task {
@Schema(description="Server-assigned identifier.")
public int id;
@Schema(description="What needs doing.")
public String title;
@Schema(description="Whether the task is finished.")
public boolean done;
public Task() {}
public Task(int id, String title, boolean done) {
this.id = id;
this.title = title;
this.done = done;
}
}
3. Write the resource and add the docs mixins
The title and description in @Rest, and the summary and description on each operation, become the document's info block and the operation summaries. mixins grafts in the documentation endpoints: SwaggerUiMixin brings /swagger and /api, and RedocMixin brings /redoc and the /openapi documents. BasicUniversalConfig adds serializers for HTML as well as JSON, which the two UI pages need.
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import org.apache.juneau.commons.*;
import org.apache.juneau.http.*;
import org.apache.juneau.http.response.*;
import org.apache.juneau.rest.server.*;
import org.apache.juneau.rest.server.config.*;
import org.apache.juneau.rest.server.docs.*;
import org.apache.juneau.rest.server.servlet.*;
/**
* A small task API that publishes its own Swagger 2.0 and OpenAPI 3.1 documents.
*/
@Rest(
title="Task API",
description="Create and list to-do items.",
mixins={SwaggerUiMixin.class, RedocMixin.class}
)
public class TaskResource extends RestServlet implements BasicUniversalConfig {
private static final long serialVersionUID = 1L;
private final Map<Integer,Task> tasks = new ConcurrentSkipListMap<>();
private final AtomicInteger nextId = new AtomicInteger(1);
@RestGet(path="/tasks", summary="List tasks", description="Returns every task, oldest first.")
public Collection<Task> list() {
return tasks.values();
}
@RestGet(path="/tasks/{id}", summary="Get a task")
public Task get(@Path("id") @Schema(description="Task identifier.") int id) {
var t = tasks.get(id);
if (t == null)
throw new NotFound("No task with id {0}", id);
return t;
}
@RestPost(path="/tasks", summary="Create a task")
public Task create(@Content Task task) {
task.id = nextId.getAndIncrement();
tasks.put(task.id, task);
return task;
}
}
4. Read the generated document
MockRestClient runs the resource in-process. The program fetches /openapi.json and prints its version, title and the task operations.
import java.util.*;
import org.apache.juneau.marshall.collections.*;
import org.apache.juneau.rest.mock.classic.*;
/**
* Starts {@link TaskResource} in-process, fetches /openapi.json and prints the task operations it documents.
*/
public class TaskDocsDemo {
public static void main(String[] args) throws Exception {
System.out.println(run());
}
public static String run() throws Exception {
try (var client = MockRestClient.buildJson(TaskResource.class)) {
var json = client.get("/openapi.json").run().assertStatus(200).getContent().asString();
var doc = JsonMap.ofString(json);
var lines = new ArrayList<String>();
lines.add("openapi: " + doc.getString("openapi"));
lines.add("title: " + doc.getMap("info").getString("title"));
var paths = doc.getMap("paths");
for (var path : new TreeSet<>(paths.keySet())) {
if (! path.startsWith("/tasks"))
continue; // Skip the documentation endpoints, which document themselves too.
var ops = paths.getMap(path);
for (var method : new TreeSet<>(ops.keySet()))
lines.add(method.toUpperCase() + " " + path + " - " + ops.getMap(method).getString("summary"));
}
return String.join("\n", lines);
}
}
}
Run it
From the project directory:
mvn -q compile exec:java -Dexec.mainClass=com.example.TaskDocsDemo
Expected output:
openapi: 3.1.0
title: Task API
GET /tasks - List tasks
POST /tasks - Create a task
GET /tasks/{id} - Get a task
To browse the UIs, deploy TaskResource like any Juneau servlet, for example with Build a REST API on Spring Boot, then open /redoc or /swagger under its path.
How it works
- The document comes from your code. Juneau builds it from the resource and operation annotations, the Java signatures (
@Path,@Content, the return types) and the bean schemas, so it changes when the code does. - Mixins choose the endpoints. Each mixin is a small
@Restresource merged into yours. The OpenAPI document lists the mixins' own endpoints as well, which is why the program filters to/tasks.BasicRestServletandBasicRestResourceinclude both mixins by default; with a plainRestServlet, as here, you pick the ones you want. See API-Docs Mixin Pack. - Two spec versions.
/openapi,/openapi.jsonand/openapi.yamlserve OpenAPI 3.1, and/apiserves Swagger v2./openapiand/apifollow the request'sAcceptheader; the.jsonand.yamlURLs always return that format. See BasicRestServlet Swagger and OpenAPI 3.1 and Swagger.
Next steps
- Test the API and its documentation endpoints in JUnit: Test an endpoint with MockRestClient.
- Call the API from Java through a typed interface: Call an existing REST API with a typed proxy.
- Require a bearer token on the API: Secure an endpoint with JWT and OAuth.