This guide walks you through the process of generating documentation for the HTTP endpoints in a Spring application.

What You Will Build

You will build a simple Spring application with some HTTP endpoints that expose an API. You will test only the web layer by using JUnit and Spring’s MockMvc. Then you will use the same tests to generate documentation for the API by using Spring REST Docs.

What You Need

How to complete this guide

Like most Spring Getting Started guides, you can start from scratch and complete each step or you can bypass basic setup steps that are already familiar to you. Either way, you end up with working code.

To start from scratch, move on to Starting with Spring Initializr.

To skip the basics, do the following:

When you finish, you can check your results against the code in gs-testing-restdocs/complete.

Starting with Spring Initializr

You can use this pre-initialized project and click Generate to download a ZIP file. This project is configured to fit the examples in this tutorial.

To manually initialize the project:

  1. Navigate to https://start.spring.io. This service pulls in all the dependencies you need for an application and does most of the setup for you.

  2. Choose either Gradle or Maven and the language you want to use. This guide assumes that you chose Java.

  3. Click Dependencies and select Spring Web.

  4. Click Generate.

  5. Download the resulting ZIP file, which is an archive of a web application that is configured with your choices.

If your IDE has the Spring Initializr integration, you can complete this process from your IDE.
You can also fork the project from Github and open it in your IDE or other editor.

Create a Simple Application

Create a new controller for your Spring application. The following listing (from src/main/java/com/example/testingrestdocs/HomeController.java) shows how to do so:

package com.example.testingrestdocs;

import java.util.Collections;
import java.util.Map;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HomeController {

	@GetMapping("/")
	public Map<String, Object> greeting() {
		return Collections.singletonMap("message", "Hello, World");
	}

}

Run the Application

The Spring Initializr creates a main class that you can use to launch the application. The following listing (from src/main/java/com/example/testingrestdocs/TestingRestdocsApplication.java) shows the application class that the Spring Initializr created:

package com.example.testingrestdocs;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class TestingRestdocsApplication {

	public static void main(String[] args) {
		SpringApplication.run(TestingRestdocsApplication.class, args);
	}
}

@SpringBootApplication is a convenience annotation that adds all of the following:

  • @Configuration: Tags the class as a source of bean definitions for the application context.

  • @EnableAutoConfiguration: Tells Spring Boot to start adding beans based on classpath settings, other beans, and various property settings.

  • @EnableWebMvc: Flags the application as a web application and activates key behaviors, such as setting up a DispatcherServlet. Spring Boot adds it automatically when it sees spring-webmvc on the classpath.

  • @ComponentScan: Tells Spring to look for other components, configurations, and services in the the com.example.testingrestdocs package, letting it find the HelloController class.

The main() method uses Spring Boot’s SpringApplication.run() method to launch an application. Did you notice that there is not a single line of XML? There is no web.xml file, either. This web application is 100% pure Java and you did not have to deal with configuring any plumbing or infrastructure. Spring Boot handles all of that for you.

Logging output is displayed. The service should be up and running within a few seconds.

Test the Application

Now that the application is running, you can test it. You can load the home page at http://localhost:8080. However, to give yourself more confidence that the application works when you make changes, you want to automate the testing. You also want to publish documentation for the HTTP endpoint. You can generate the dynamic parts of that testing as part of the tests by using Spring REST Docs.

The first thing you can do is write a simple sanity check test that fails if the application context cannot start. To do so, add Spring Test and Spring REST Docs as dependencies to your project, in the test scope. The following listing shows what to add if you use Maven:

<dependency>
	<groupId>org.springframework.restdocs</groupId>
	<artifactId>spring-restdocs-mockmvc</artifactId>
	<scope>test</scope>
</dependency>

The following listing shows the completed pom.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.7.1</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>testing-restdocs-complete</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>testing-restdocs-complete</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<!-- tag::test[] -->
		<dependency>
			<groupId>org.springframework.restdocs</groupId>
			<artifactId>spring-restdocs-mockmvc</artifactId>
			<scope>test</scope>
		</dependency>
		<!-- end::test[] -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
			<!-- tag::asciidoc[] -->
			<plugin>
				<groupId>org.asciidoctor</groupId>
				<artifactId>asciidoctor-maven-plugin</artifactId>
				<executions>
					<execution>
						<id>generate-docs</id>
						<phase>prepare-package</phase>
						<goals>
							<goal>process-asciidoc</goal>
						</goals>
						<configuration>
							<sourceDocumentName>index.adoc</sourceDocumentName>
							<backend>html</backend>
							<attributes>
								<snippets>${project.build.directory}/snippets</snippets>
							</attributes>
						</configuration>
					</execution>
				</executions>
			</plugin>
			<!-- end::asciidoc[] -->
		</plugins>
	</build>

</project>

The following example shows what to add if you use Gradle:

testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'

The following listing shows the completed build.gradle file:

plugins {
	id 'org.springframework.boot' version '2.7.1'
	id 'io.spring.dependency-management' version '1.0.11.RELEASE'
	id 'java'
	id 'org.asciidoctor.jvm.convert' version '3.3.2'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

asciidoctor {
	sourceDir 'src/main/asciidoc'
	attributes \
		'snippets': file('target/snippets')
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	// tag::test[]
	testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
	// end::test[]
	testImplementation('org.springframework.boot:spring-boot-starter-test')
}

test {
	useJUnitPlatform()
}
You can ignore the comments in the build files. They are there to let us pick up parts of the files for inclusion in this guide.
You have included the mockmvc flavor of REST Docs, which uses Spring MockMvc to capture the HTTP content. If your own application does not use Spring MVC, you can also use a restassured flavor, which works with full stack integration tests.

Now create a test case with the @RunWith and @SpringBootTest annotations and an empty test method, as the following example (from src/test/java/com/example/testingrestdocs/TestingRestdocsApplicationTests.java) shows:

package com.example.testingrestdocs;

import org.junit.jupiter.api.Test;

import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class TestingRestdocsApplicationTests {

	@Test
	public void contextLoads() throws Exception {
	}
}

You can run this test in your IDE or on the command line (by running ./mvnw test or ./gradlew test).

It is nice to have a sanity check, but you should also write some tests that assert the behavior of our application. A useful approach is to test only the MVC layer, where Spring handles the incoming HTTP request and hands it off to your controller. To do that, you can use Spring’s MockMvc and ask for that to be injected for you by using the @WebMvcTest annotation on the test case. The following example (from src/test/java/com/example/testingrestdocs/WebLayerTest.java) shows how to do so:

@RunWith(SpringRunner.class)
@WebMvcTest(HomeController.class)
public class WebLayerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void shouldReturnDefaultMessage() throws Exception {
        this.mockMvc.perform(get("/"))
            .andExpect(status().isOk())
            .andExpect(content().string(containsString("Hello, World")));
    }
}

Generate Snippets for Documentation

The test in the preceding section makes (mock) HTTP requests and asserts the responses. The HTTP API that you have created has dynamic content (at least in principle), so it would be really nice to be able to spy on the tests and siphon off the HTTP requests for use in the documentation. Spring REST Docs lets you do so by generating “snippets”. You can get this working by adding an annotation to your test and an extra “assertion”. The following example (from src/test/java/com/example/testingrestdocs/WebLayerTest.java) shows the complete test:

package com.example.testingrestdocs;

import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.web.servlet.MockMvc;

import static org.hamcrest.Matchers.containsString;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@WebMvcTest(HomeController.class)
@AutoConfigureRestDocs(outputDir = "target/snippets")
public class WebLayerTest {

	@Autowired
	private MockMvc mockMvc;

	@Test
	public void shouldReturnDefaultMessage() throws Exception {
		this.mockMvc.perform(get("/")).andDo(print()).andExpect(status().isOk())
				.andExpect(content().string(containsString("Hello, World")))
				.andDo(document("home"));
	}
}

The new annotation is @AutoConfigureRestDocs (from Spring Boot), which takes an argument for a directory location for the generated snippets. And the new assertion is MockMvcRestDocumentation.document, which takes an argument for a string identifier for the snippets.

Gradle users might prefer to use build instead of target as an output directory. However, it does not matter. Use whichever you prefer.

Run the test and then look in target/snippets. You should find a directory called home (the identifier) that contains Asciidoctor snippets, as follows:

└── target
    └── snippets
        └── home
            └── curl-request.adoc
            └── http-request.adoc
            └── http-response.adoc
            └── httpie-request.adoc
            └── request-body.adoc
            └── response-body.adoc

The default snippets are in Asciidoctor format for the HTTP request and response. There are also command line examples for curl and httpie (two common and popular command line HTTP clients).

You can create additional snippets by adding arguments to the document() assertion in the test. For example, you can document each of the fields in a JSON response by using the PayloadDocumentation.responseFields() snippet, as the following example (from src/test/java/com/example/testingrestdocs/WebLayerTest.java) shows:

this.mockMvc.perform(get("/"))
    ...
    .andDo(document("home", responseFields(
        fieldWithPath("message").description("The welcome message for the user.")
    ));

If you run the test, you should find an additional snippet file called response-fields.adoc. It contains a table of field names and descriptions. If you omit a field or get its name wrong, the test fails. This is the power of REST Docs.

You can create custom snippets and change the format of the snippets and customize values, such as the hostname. See the documentation for Spring REST Docs for more detail.

Using the Snippets

To use the generated snippets, you want to have some Asciidoctor content in the project and then include the snippets at build time. To see this work, create a new file called src/main/asciidoc/index.adoc and include the snippets as desired. The following example (from src/main/asciidoc/index.adoc) shows how to do so:

= Getting Started With Spring REST Docs

This is an example output for a service running at http://localhost:8080:

.request
include::{snippets}/home/http-request.adoc[]

.response
include::{snippets}/home/http-response.adoc[]

As you can see the format is very simple, and in fact you always get the same message.

The main feature of this Asciidoc file is that it includes two of the snippets, by using the Asciidoctor include directive (the colons and the trailing brackets tell the parser to do something special on those lines). Notice that the path to the included snippets is expressed as a placeholder (an attribute in Asciidoctor) called {snippets}. The only other markup in this simple case is the = at the top (which is a level-1 section heading) and the . before the captions (“request” and “response”) on the snippets. The . turns the text on that line into a caption.

Then, in the build configuration, you need to process this source file into your chosen documentation format. For example, you can use Maven to generate HTML (target/generated-docs is generated when you do ./mvnw package). The following listing shows the Asciidoc portion of the pom.xml file:

<plugin>
	<groupId>org.asciidoctor</groupId>
	<artifactId>asciidoctor-maven-plugin</artifactId>
	<executions>
		<execution>
			<id>generate-docs</id>
			<phase>prepare-package</phase>
			<goals>
				<goal>process-asciidoc</goal>
			</goals>
			<configuration>
				<sourceDocumentName>index.adoc</sourceDocumentName>
				<backend>html</backend>
				<attributes>
					<snippets>${project.build.directory}/snippets</snippets>
				</attributes>
			</configuration>
		</execution>
	</executions>
</plugin>

If you use Gradle, build/asciidoc is generated when you run ./gradlew asciidoctor. The following listing shows the Asciidoctor-related parts of the build.gradle file:

plugins {
	...
	id 'org.asciidoctor.convert' version '1.5.6'
}

...

asciidoctor {
    sourceDir 'src/main/asciidoc'
    attributes \
      'snippets': file('target/snippets')
}
The default location for Asciidoctor sources in Gradle is src/doc/asciidoc. We set the sourceDir to match the default for Maven.

Summary

Congratulations! You have just developed a Spring application and documented it by using Spring Restdocs. You could publish the HTML documentation you created to a static website or package it up and serve it from the application itself. Your documentation will always be up to date, and tests will fail your build if it is not.

See Also

The following guides may also be helpful:

Want to write a new guide or contribute to an existing one? Check out our contribution guidelines.

All guides are released with an ASLv2 license for the code, and an Attribution, NoDerivatives creative commons license for the writing.