Spring REST OpenAPI Tutorial
This tutorial demonstrates that OfficeFloor REST endpoints declared as YAML files appear automatically in the OpenAPI specification generated by springdoc-openapi. No additional configuration is needed — adding the springdoc dependency is sufficient.
The tutorial exposes a simple product catalogue with two endpoints:
GET /product— returns a list of all productsGET /product/{id}— returns a single product by identifier
Maven dependency
Add springdoc-openapi-starter-webmvc-ui alongside the OfficeFloor starter:
<dependency>
<groupId>net.officefloor.springboot</groupId>
<artifactId>officefloor-rest-spring-boot-4-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.8.6</version>
</dependency>
That is the only change required — OfficeFloor's built-in SpringDoc integration registers all YAML-declared endpoints with the OpenApiCustomizer at start-up.
Application class
@SpringBootApplication
public class SpringRestOpenApiApplication {
public static void main(String[] args) {
SpringApplication.run(SpringRestOpenApiApplication.class, args);
}
}
Response DTO with @Schema annotations
Swagger annotations on the response class populate the component schema in the generated spec:
@Data
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "A product in the catalogue")
public class Product {
@Schema(description = "Unique product identifier")
private Long id;
@Schema(description = "Product name")
private String name;
@Schema(description = "Product price in cents")
private int priceCents;
}
Service beans
A plain Spring @Service holds the in-memory catalogue:
@Service
public class ProductCatalogueService {
private final List<Product> products = List.of(
new Product(1L, "Widget", 999),
new Product(2L, "Gadget", 1999),
new Product(3L, "Doohickey", 4999));
public List<Product> findAll() {
return products;
}
public Product findById(long id) {
return products.stream()
.filter(p -> p.getId() == id)
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Product not found: " + id));
}
}
Service methods with @Operation
@Operation on the service method populates the operation summary in the spec. The ProductCatalogueService is injected by type — no annotation needed on that parameter:
public class ListProductsService {
@Operation(summary = "List all products")
public void service(ProductCatalogueService catalogue, ObjectResponse<List<Product>> response) {
response.send(catalogue.findAll());
}
}
public class GetProductService {
@Operation(summary = "Get product by ID")
public void service(@PathVariable(name = "id") Long id,
ProductCatalogueService catalogue,
ObjectResponse<Product> response) {
response.send(catalogue.findById(id));
}
}
YAML endpoint files
Each endpoint is declared as a YAML file under officefloor/rest/:
officefloor/rest/
├── product.GET.yml → GET /product
└── product/
└── {id}.GET.yml → GET /product/{id}
service:
class: net.officefloor.tutorial.springrestopenapi.ListProductsService
service:
class: net.officefloor.tutorial.springrestopenapi.GetProductService
Testing
Tests verify both endpoint behaviour and that the paths appear in the live OpenAPI JSON at GET /v3/api-docs:
@SpringBootTest
@AutoConfigureMockMvc
public class SpringRestOpenApiTest {
@Autowired
private MockMvc mvc;
@Autowired
private ObjectMapper mapper;
@Test
public void listProducts() throws Exception {
mvc.perform(get("/product").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].name").value("Widget"))
.andExpect(jsonPath("$[1].name").value("Gadget"));
}
@Test
public void getProductById() throws Exception {
mvc.perform(get("/product/1").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("Widget"))
.andExpect(jsonPath("$.priceCents").value(999));
}
@Test
public void openApiSpecIncludesListEndpoint() throws Exception {
mvc.perform(get("/v3/api-docs"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("/product")));
}
@Test
public void openApiSpecIncludesGetByIdEndpoint() throws Exception {
mvc.perform(get("/v3/api-docs"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("/product/{id}")));
}
}
The Swagger UI is available at http://localhost:8080/swagger-ui.html when the application is running.
Next
The Spring REST Thymeleaf tutorial demonstrates server-side HTML rendering with Thymeleaf from OfficeFloor REST service methods.

