API documentation is an indispensable part of developing web applications. Luckily, there are solutions that make meticulous manual documentation crafting a thing of the past.
This step-by-step tutorial will guide you through integrating Swagger (based on OpenAPI 3.0 specification) into a Spring Boot project.
Swagger is a set of tools that help developers to create, edit, and use API documentation according to the OpenAPI specification. With Swagger, it is no longer necessary to manually write lengthy API docs: the solution is capable of reading the structure of the API you defined in the annotations of your code and automatically converting it into API specification.
What is more, Swagger provides a user interface that generates interactive API documentation that lets users test the API calls in the browser.
Main Swagger components are:
- Swagger Editor for writing and editing API specs,
- Swagger UI for creating interactive API documentation,
- Swagger Codegen for generating server stubs or client libraries for your API.
Both terms, Swagger and OpenAPI, are used in the context of API documentation, but they are not the same. OpenAPI is a standard specification for describing API, and Swagger helps to create API docs in line with this specification.
- JDK 17 or later
- Maven
- Your favorite IDE (preferably IntelliJ IDEA)
Skip this step if you want to use your own project. You can follow along or implement the examples from the tutorial into your application accordingly.
The demo Spring Boot application will expose REST APIs for managing employees. The Employee will have id, first name, and last name. Our APIs will allow for getting a list of all employees, getting one employee, adding a new employee, updating the existing employee, and deleting an employee according to the following schema:
Method | URL | Action |
---|---|---|
GET | /employees | Get a list of all employees |
GET | /employees/{employeeId} | Get one employee by id |
POST | /employees | Add an employee |
PUT | /employees | Update an employee |
DELETE | /employees/{employeeId} | Delete an employee |
Head to Spring Initializr to create a skeleton of your project. Select Java, Maven, define a project name and choose Java 17.
We will also need Spring Web, Lombok, Spring Data JDBC, H2 Database Driver, and DevTools dependencies. DevTools is a great time saver because you don’t have to restart the application every time you introduce changes, the recompilation is performed on the fly.
Generate the project and open it in your IDE.
Let’s keep the structure super simple. We will need only two classes, Employee
and EmployeeController
, and an EmployeeRepository
interface.
Populate the classes as shown below.
@Getter
@Setter
@NoArgsConstructor
public class Employee {
@Id
private int id;
private String firstName;
private String lastName;
public Employee(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
public interface EmployeeRepository extends ListCrudRepository<Employee, Integer> { }
@RestController
public class EmployeeController {
@Autowired
private EmployeeRepository repository;
public EmployeeController(EmployeeRepository repository) {
this.repository = repository;
}
@GetMapping("/employees")
public List<Employee> findAllEmployees() {
return repository.findAll();
}
@GetMapping("/employees/{employeeId}")
public Employee getEmployee(@PathVariable int employeeId) {
Employee employee = repository.findById(employeeId)
.orElseThrow(
() -> new RuntimeException("Employee id not found - " + employeeId));
return employee;
}
@PostMapping("/employees")
public Employee addEmployee(@RequestBody Employee employee) {
employee.setId(0);
Employee newEmployee = repository.save(employee);
return newEmployee;
}
@PutMapping("/employees")
public Employee updateEmployee(@RequestBody Employee employee) {
Employee theEmployee = repository.save(employee);
return theEmployee;
}
}
@DeleteMapping("/employees/{employeeId}")
public String deleteEmployee(@PathVariable int employeeId) {
Employee employee = repository.findById(employeeId)
.orElseThrow(
() -> new RuntimeException("Employee id not found - " + employeeId));
repository.delete(employee);
return "Deleted employee with id: " + employeeId;
}
Now, let’s provide a database schema. Create a schema.sql file in the resources directory with the following content:
CREATE TABLE IF NOT EXISTS employee (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
first_name VARCHAR(255) NOT NULL,
last_name VARCHAR(255) NOT NULL
);
Next, let’s populate our database instance with some data (that’s optional, we don’t need to work with DB data when developing APIs, I just don’t like to see the ugly error page upon starting the app in the browser). Create the data.sql file in resources with the following content:
delete from employee;
insert into employee (first_name, last_name) values ('John', 'Doe');
insert into employee (first_name, last_name) values ('Jane', 'Smith');
The last thing to do is to configure your application.properties
file.
spring.sql.init.mode=always
because we are performing a script-based initialization.
Finally, let’s verify that the app is functioning as desired. Run it from the TestOpenapiApplication
class
You should see the following result at http://localhost:8080/employees
[
{
"id": 1,
"firstName": "John",
"lastName": "Doe"
},
{
"id": 2,
"firstName": "Jane",
"lastName": "Smith"
}
]
That’s it! Our minimalistic CRUD application is ready for experiments.
To work with Swagger, we need the springdoc-api library that helps to generate OpenAPI-compliant API documentation for Spring Boot projects. The library supports Swagger UI and other useful features such as OAuth2.
Add the following dependency for springdoc-api to your pom.xml file:
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.6.0</version>
</dependency>
That’s all, no additional configuration is required!
The OpenAPI documentation is generated when we build our project. So let’s verify that everything is working correctly. Run your application and go to the default page where the API documentation is located: http://localhost:8080/v3/api-docs.
You should see the data on your endpoints in JSON format. You can also access the .yaml file at http://localhost:8080/v3/api-docs.yaml.
It is possible to change the default path in the application.properties
file. For example:
springdoc.api-docs.path=/api-docs
Now the documentation is available at http://localhost:8080/api-docs.
The beauty about springdoc-openapi library dependency is that it already includes Swagger UI, so we don’t have to configure the tool separately!
You can access Swagger UI at http://localhost:8080/swagger-ui/index.html, where you will see a beautiful user interface to interact with your endpoints.
It is also possible to change the default path for Swagger UI. In the application.properties
if needed.
springdoc.swagger-ui.path=/swagger-ui.html
Right now, our API documentation is not very informative. We can extend it with the help of annotations added to the application code. Below is the summary of the most common ones.
First of all, let’s include some essential data about the API, such as name, description, and author contacts. For that purpose, create an OpenAPIConfiguration class and fill in the following code:
@Configuration
public class OpenAPIConfiguration {
@Bean
public OpenAPI defineOpenApi() {
Server server = new Server()
.url("http://localhost:8080")
.description("Development Server");
Contact contact = new Contact()
.name("Simon")
.email("[email protected]");
Info info = new Info()
.title("Employee Management API")
.version("1.0")
.description("API to manage employees")
.contact(contact);
return new OpenAPI().info(info).servers(List.of(server));
}
}
You can also fill in information about applicable License and some other data, but code above is enough for demonstration. Run the app and verify that the main API page includes provided information:
The springdoc-openapi library supports JSR 303: Bean Validation (@NotNull
, @Min
, @Max
, and @Size
), so when we add these annotations to our code, the additional schema documentation will be automatically generated.
Let’s specify them in Employee
class:
public class Employee {
@Id
@NotNull
private int id;
@NotNull
@Size(min = 1, max = 20)
private String firstName;
@NotNull
@Size(min = 1, max = 50)
private String lastName;
}
When you recompile your app, you will see that the Schemas section contains the specified info:
The @Tag
annotation can be applied at class or method level and is used to group the APIs in a meaningful way.
For instance, let’s add this annotation to our GET methods:
@Tag(name = "get", description = "GET methods of Employee APIs")
@GetMapping("/employees")
public List<Employee> findAllEmployees() {
return repository.findAll();
}
@Tag(name = "get", description = "GET methods of Employee APIs")
@GetMapping("/employees/{employeeId}")
public Employee getEmployee(@PathVariable int employeeId) {
Employee employee = repository.findById(employeeId)
.orElseThrow(() -> new RuntimeException("Employee id not found - " + employeeId));
return employee;
}
You will see that APIs are now grouped differently:
The @Operation
annotation enables the developers to provide additional information about a method, such as summary and description.
Let’s update our updateEmployee()
method:
@Operation(summary = "Update an employee",
description = "Update an existing employee. The response is updated Employee object with id, first name, and last name.")
@PutMapping("/employees")
public Employee updateEmployee(@RequestBody Employee employee) {
Employee theEmployee = repository.save(employee);
return theEmployee;
}
The API description in Swagger UI is now a little more informative:
The @ApiResponses
annotation helps to add information about responses available for the given method. Each response is specified with @ApiResponse
, for instance
@ApiResponses({
@ApiResponse(responseCode = "200",
content = { @Content(mediaType = "application/json",
schema = @Schema(implementation = Employee.class)) }),
@ApiResponse(responseCode = "404",
description = "Employee not found",
content = @Content) })
@DeleteMapping("/employees/{employeeId}")
public String deleteEmployee(@PathVariable int employeeId) {
Employee employee = repository.findById(employeeId)
.orElseThrow(
() -> new RuntimeException("Employee id not found - " + employeeId));
repository.delete(employee);
return "Deleted employee with id: " + employeeId;
}
After you recompile the Controller class, the data on responses will be automatically generated:
The @Parameter
annotation can be used on a method parameter to define parameters for the operation. For example,
public Employee getEmployee(
@Parameter(description = "ID of employee to be retrieved", required = true)
@PathVariable int employeeId) {
Employee employee = repository.findById(employeeId)
.orElseThrow(
() -> new RuntimeException("Employee id not found - " + employeeId));
return employee;
}
Here, the description element provides additional data on parameter purpose, and required is set to true signifying that this parameter is mandatory.
And here’s how it looks in Swagger UI:
As you can see, writing APIs with Swagger is extremely convenient. The solution enables declarative and consistent API documentation without laborious manual effort. As a result, it accelerates the development process and minimizes the risk of errors, so you should definitely integrate it into your workflow!