Have you ever installed Swagger UI, only to find that the documentation wasn’t updated after you fixed the implementation? There are two main approaches to auto-generating API documentation: Spring REST Docs and Springdoc OpenAPI, each with different strengths. This article organizes how both work and presents the criteria for choosing between them.
Differences Between the Two Generation Approaches
Let’s start with the fundamental mechanics.
Spring REST Docs is test-driven. It generates request/response snippets (Asciidoc fragments) from the execution results of tests written with MockMvc, WebTestClient, or REST Assured. If a test fails, the build fails, and the documentation isn’t updated.
Springdoc OpenAPI is annotation-driven. At runtime, it uses reflection to analyze controller annotations and method signatures, generating JSON that conforms to the OpenAPI 3 specification. A key feature is being able to interactively call APIs from Swagger UI.
As a side note, SpringFox, which was once mainstream, doesn’t support Spring Boot 3.x and is effectively no longer maintained. You can safely exclude it from consideration for new projects.
Risk of Drift Between Documentation and Implementation
This is probably the biggest pain point when choosing between the two.
REST Docs documents “what was actually called in tests” as-is, so lies aren’t possible. The structure makes it hard to forget reflecting an additional response field in the documentation. If field definitions are incomplete, document() will fail the test for you.
Springdoc, on the other hand, is convenient but can’t detect drift such as missing @Schema annotations or outdated @Operation descriptions until runtime. Since it operates independently of tests, the accuracy of documentation ends up relying on reviewers’ visual inspection.
That said, for teams where test culture hasn’t matured yet, the “you get nothing unless you write tests” nature of REST Docs is a hurdle. In terms of immediate effectiveness, Springdoc wins by a landslide.
Minimal Configuration for Spring REST Docs
Let’s look at actual code. The Gradle setup looks like this.
plugins {
id 'org.asciidoctor.jvm.convert' version '3.3.2'
}
ext {
snippetsDir = file('build/generated-snippets')
}
dependencies {
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor'
}
test {
outputs.dir snippetsDir
}
asciidoctor {
inputs.dir snippetsDir
dependsOn test
}
On the test side, just insert document() into MockMvc.
@WebMvcTest(UserController.class)
@AutoConfigureRestDocs
class UserControllerDocsTest {
@Autowired MockMvc mockMvc;
@Test
void getUser() throws Exception {
mockMvc.perform(get("/users/{id}", 1))
.andExpect(status().isOk())
.andDo(document("users/get",
pathParameters(
parameterWithName("id").description("ユーザーID")
),
responseFields(
fieldWithPath("id").description("ID"),
fieldWithPath("name").description("氏名")
)
));
}
}
If you include the snippets in src/docs/asciidoc/index.adoc using include::, running ./gradlew asciidoctor will generate HTML. If you’re shaky on MockMvc fundamentals, see Testing Controllers with Spring Boot’s MockMvc as well.
Minimal Configuration for Springdoc OpenAPI
For this one, you just add a single dependency.
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.5.0</version>
</dependency>
That’s it — /swagger-ui.html already works. Let’s enrich the information with annotations.
@RestController
@RequestMapping("/users")
@Tag(name = "User", description = "ユーザー管理API")
public class UserController {
@Operation(summary = "ユーザー取得")
@GetMapping("/{id}")
public UserResponse get(
@Parameter(description = "ユーザーID") @PathVariable Long id
) {
return service.find(id);
}
}
Global configuration is defined as a Bean.
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info().title("User API").version("v1"))
.components(new Components().addSecuritySchemes("bearer",
new SecurityScheme().type(SecurityScheme.Type.HTTP).scheme("bearer")));
}
The basics of Swagger UI introduction are covered in detail at Using OpenAPI/Swagger UI with Spring Boot.
Organized in a Comparison Table
Here are the criteria laid out side by side for decision-making.
| Criterion | Spring REST Docs | Springdoc OpenAPI |
|---|---|---|
| Generation timing | Build time (test execution) | Runtime (reflection) |
| Match with implementation | Strong (linked to tests) | Weak (relies on annotations) |
| Initial setup cost | Medium (requires tests) | Low (just add dependency) |
| Learning cost | Somewhat high | Low |
| Output format | Asciidoc/HTML | OpenAPI spec/Swagger UI |
| Try it out | None (static HTML) | Available |
| OpenAPI spec output | Not by default (possible with extensions) | Standard |
| CI integration | Auto-detected when tests break | Requires separate detection mechanism |
| Public-facing use | Distributed as static documentation | Developer portals, SDK generation |
How to Choose Based on Use Case
If you need to publish external APIs or distribute SDKs, OpenAPI specifications are practically mandatory, so Springdoc-centric or the combined approach described below is realistic. Client code generation and API Gateway integration also work on a spec basis.
Internal APIs requiring strict spec compliance, such as in finance or healthcare, are suited for REST Docs. The strong property that “what’s written in documentation is guaranteed by tests” is effective here.
Cases where you want to set up a developer portal for microservices are easy to handle with Springdoc, on the premise that each service outputs an OpenAPI specification. It also pairs well with IDPs like Backstage.
Small teams with weak test culture should start with Springdoc to ship something that works, then gradually shift toward REST Docs as needed.
Combined Pattern - restdocs-api-spec
For the luxurious requirement of “I want the accuracy of REST Docs but also want to distribute an OpenAPI specification,” restdocs-api-spec is an option. It’s a third-party extension maintained by epages-de that can output an OpenAPI 3.0 specification directly from REST Docs tests.
plugins {
id 'com.epages.restdocs-api-spec' version '0.19.4'
}
dependencies {
testImplementation 'com.epages:restdocs-api-spec-mockmvc:0.19.4'
}
openapi3 {
title = 'User API'
version = '1.0.0'
format = 'yaml'
outputDirectory = 'build/api-spec'
}
Running ./gradlew openapi3 outputs openapi.yaml, which you can feed into Swagger UI or Redoc to get interactive documentation. It’s a best-of-both-worlds setup where tests guarantee quality and the output is OpenAPI.
That said, the learning cost is non-trivial. It’s better to consider this on the premise that multiple people on the team can write REST Docs.
Pitfalls and CI Caveats
Here are some problems that are surprisingly easy to step on in real projects.
A surprisingly common issue with REST Docs is running only ./gradlew asciidoctor locally and skipping tests. The HTML gets assembled from old snippets, so always run test first in CI. Adding dependsOn test to the asciidoctor task is a safe practice.
With Springdoc, /swagger-ui.html often returns 401 when combined with Spring Security. You need to explicitly allow /swagger-ui/** and /v3/api-docs/** in SecurityFilterChain. In production, conversely, don’t forget the setting to close these off.
Because it heavily uses reflection, schema generation can fail in native image (GraalVM) or proxy environments. Trying a build in the target environment before adoption gives peace of mind.
Summary
The decision criteria are simple. If you think about it along the two axes of “do you have a test culture” and “are you publishing externally,” the choice mostly decides itself.
- Test culture present × Internal API → REST Docs
- Test culture present × External publication → restdocs-api-spec combined
- Weak test culture × either → Start with Springdoc
If you’re undecided, I think a realistic approach is to start small with Springdoc and partially introduce REST Docs once drift becomes a real problem. First, try both on a small API like the one built in the CRUD tutorial, and pick whichever feels right.