Skip to main content

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 mixins
    • org.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 @Rest resource merged into yours. The OpenAPI document lists the mixins' own endpoints as well, which is why the program filters to /tasks. BasicRestServlet and BasicRestResource include both mixins by default; with a plain RestServlet, as here, you pick the ones you want. See API-Docs Mixin Pack.
  • Two spec versions. /openapi, /openapi.json and /openapi.yaml serve OpenAPI 3.1, and /api serves Swagger v2. /openapi and /api follow the request's Accept header; the .json and .yaml URLs always return that format. See BasicRestServlet Swagger and OpenAPI 3.1 and Swagger.

Next steps​