Skip to content

Building an API To List All Endpoints Exposed by Spring Boot

No longer get lost in your backend

Working with backends, I learned how you can easily lose sight of what is going on or what is currently deployed. This might become a problem, especially when dealing with production environments.

There are many libraries to tackle this question, and I personally recommend Spring Boot Actuator.

“Spring Boot includes a number of additional features to help you monitor and manage your application when you push it to production. You can choose to manage and monitor your application by using HTTP endpoints or with JMX. Auditing, health, and metrics gathering can also be automatically applied to your application.” — Spring Documentation

The spring-boot-actuator module provides all of the aforementioned Spring Boot’s production-ready features to help you monitor and manage your application.

However, the goal of this tutorial is not to show you how to harness the Spring Boot Actuator. In fact, for simple applications, a single monitoring API should be enough.

Based on my experience, the first and most important thing you can lose track of is the list of all exposed endpoints. There might be APIs you do not remember or use that are still online and potentially available to anyone. This should be avoided for security reasons, especially if these APIs are deprecated.

Let’s see how to define a custom API returning a list of all endpoints exposed using the Spring Boot application.

Building the API

Looking online for a way to retrieve all deployed endpoints in a Spring Boot application, I discovered the existence of RequestMappingHandlerMapping.

This class is used by Spring Boot to execute every method annotated with @RequestMapping and contain a list of all of them, specifically, a list of all exposed endpoints by your application.

This is why implementing such an API is easier than you may think. This can be achieved as follows:

Java

@RestController
@RequestMapping("/monitoring/")
public class MonitoringController {
    @Autowired
    private RequestMappingHandlerMapping requestMappingHandlerMapping;

    @GetMapping("endpoints")
    public ResponseEntity<List<String>> getEndpoints() {
        return new ResponseEntity<>(
                requestMappingHandlerMapping
                        .getHandlerMethods()
                        .keySet()
                        .stream()
                        .map(RequestMappingInfo::toString)
                        .collect(Collectors.toList()),
                HttpStatus.OK
        );
    }
}

Kotlin

@RestController
@RequestMapping("/monitoring/")
class MonitoringController {
    @Autowired
    private lateinit var requestMappingHandlerMapping : RequestMappingHandlerMapping

    @GetMapping("endpoints")
    fun getEndpoints() : ResponseEntity<List<String>> {
        return ResponseEntity(
            requestMappingHandlerMapping
                .handlerMethods
                .map {
                    it.key.toString()
                },
            HttpStatus.OK
        )
    }
}

In both cases, I used the handlerMethods attribute, which stores a read-only map with all mappings and HandlerMethod’s. The mappings are the keys of the entries and are represented by RequestMappingInfo objects. Their toString() method returns all you need to reach the endpoint associated with a HandlerMethod object, specifically, the method annotated with @RequestMapping.

This is what an example of a response from this API looks like:

[
   "{GET /monitoring/endpoints}",      
   "{GET /v1/genres}",    
   "{GET /v1/genres/{id}}",
   "{POST /dex/v1/genres}", 
   "{PUT /v1/genres/{id}}",
   "{DELETE /v1/genres/{id}}",     
   "{PATCH /v1/genres/{id}}", 
   "{GET /v1/books}",    
   "{GET /v1/books/{id}}",
   "{POST /dex/v1/books}", 
   "{PUT /v1/books/{id}}",
   "{DELETE /v1/books/{id}}",     
   "{PATCH /v1/books/{id}}", 
   "{GET /v1/authors}",    
   "{GET /v1/authors/{id}}",
   "{POST /dex/v1/authors}", 
   "{PUT /v1/authors/{id}}",
   "{DELETE /v1/authors/{id}}",     
   "{PATCH /v1/authors/{id}}", 
   "{GET /v1/authors/{id}/books}"    
]

Conclusion

Getting lost in the complexity or vastness of your backend is common. This is exactly why you should protect yourself by adding some monitoring tools. As I have shown, implementing an API to list all endpoints is not complex, and for simple systems, it may be enough.

Thanks for reading! I hope that you found this article helpful.

nv-author-image

Antonello Zanini

I'm a software engineer, but I prefer to call myself a Technology Bishop. Spreading knowledge through writing is my mission.View Author posts

Want technical content like this in your blog?