Validation is a fundamental part of API developement which can often be complex and difficult to implement consistently and comprehensively across HTTP services. A large factor of post development bugs result from misconfigured or miscommunicated validation of both request and response entities, queries and paths to resources. Implementation must be comprehensive, validate all aspects of the request and response if required and return consistent error messages and statuses.
A wealth of validation frameworks are availble within the Java and Spring space. javax.validation is probably the most obvisous, which utilizes java model annoation sets to apply patterns and rules to data at the point of serialisation. SpringMVC builds on both javax.validation and Hibernate Validator to validate request objects.
Request entity data is not the only place we should apply validation. Query parameters, path variables and headers are also important and validation can be applied using similar pattern matching techniques from JSR 380 implementations. Validating these requires code implementation at different places in the architecture. We need annotations and error handling in the the controller and also on the model. We may also want to validate parts of the request which aren't modeled, for example: what if a request contains a query parameter not defined on the api spec'. Should the request be rejected?
A simpler and more consistent approach is to validate requests, prior to hitting our functional controllers, using a comprehensive schema for all aspects of the request. The Open API standard is used to document and define API specifications. Once we have a spec' for a our API we can use it to validate entire request at one common point prior to our controllers or object serialisation using a nifty framework from Atlassian.
In this example I will create a simple REST API which exposes read and write end points for a resource, automatically define an Open API Spec' document for the service and use it to apply comprehensive validation across all endpoint exposed by the service.
Build the API
This example uses Springboot web to quickly set up a Controller which exposes a GET and a POST API that consumes and produces a simple Java Model object which is serialised into JSON. Here's the Maven build file containing all the Springboot starters and dependencies we need to create the api.
Here's the model
and the the Controller
Create the OpenAPI Document
Now we have defined our system we need an OpenAPI json document against which requests to the API can be validated. Creating this is a simple case of including the OpenAPI dependency in the build, running the application and then visting the path to the resource where the document has been automatically generated.
Visting localhost:8080/api-docs returns the following api document which can be copied into the resources directory of the application.
Aside from using the document for validation we can also drop it into the online Swagger Editor which creates a more appealing UI for browsing and testing the service. Copy the generated OpenAPI document and paste it into the editor at
https://editor.swagger.io/
Configure the Validator
Firstly, we need our config to provide the OpenAPIValidationFilter into Springboot's servlet engine filter chain.
Here we can tell the system to validate either the request, response or both, according to the specification.
We then inject another bean into Spring containing reference to the OpenAPI document.
Handling Errors and Customising Error Responses
The standard way of handling exceptions in Java and transforming them into an HTTP response is a Controller Advice annotated Exception Handler class. The OpenAPIValidator will throw an InvalidRequestException when validation of any request component fails. We then implement code in the ExceptionHandler to translate that into a response status code and body.
Making requests
Run the application using the Springboot maven plugin run goal and hit the API endpoints with the invalid requests. The OpenAPIValidator validates the request against the full OpenAPI specification document we generated. This includes JSON schema's for the entities, patterns for values in the entity or the queries and paths and also the query and header labels. The validation is comprehensive and covers the entire API, not just the entity. The error messages and responses statuses are consistent across the service.