Effective RESTful search API in Spring

Flexible RESTful search is very often a must-have for a web application. While the concept is easy and not new, it is very often implemented in a repetitive and tedious way. In this post I will demonstrate an effective way of implementing search API with Spring Data and Specification Argument Resolver.

Naive approach

With Spring Data it is very easy to create Repositories with custom search methods. For example:

Then you can use this repository in your controller as follows:

Thanks to Spring Data support, we can easily map HTTP query parameters into a  Pageable controller parameter. Unfortunately, we have to manually manage other request parameters to determine appropriate repository method. While it is not a big problem for a single request parameter, this approach becomes unacceptable when there are more variables.

Repository method explosion

Let’s assume that we want to filter customers by first and last name as well as their status. All filters are optional. This leads to repository method explosion and nasty ifology in the controller:

This is not an acceptable approach, but fortunately there is an existing solution to these problems.

Using Specifications

Spring Data introduces Specification abstraction, which can be used with a repository. Specification is a simple interface which provides toPredicate method which should return a JPA2 Criteria Predicate:

A sample implementations for our Customer entity may have the form of:

An important part is that our specification returns a dummy predicate  if the filtering value is not available (see highlighted lines above). This will result in a query without any filtering, which is the desired behaviour for this API.

Any Specification can be passed to our repository as long as the repository implements JpaSpecificationExecutor. Multiple specifications can be combined with JPQL and or or. Spring Data provides Specifications  helper class to achieve that:

The last part is resolving our Specification from HTTP query parameters in the controller:

It is much better, but still there is a lot of tedious work to do (with writing custom specifications and then resolving them in the controller). Fortunately Specification Argument Resolver library can do all of that for us!

Automated Specification resolving

With Specification Argument Resolver you don’t have to write any implementations of Specification. It will be generated (at runtime) automatically, based on some annotations which you must add to the controller:

Additional benefit is that you can easily change the filtering logic (e.g. by switching  equal to like or accepting multiple allowed statuses with in keyword). You can also extract annotations to a separate empty interface to keep your controllers simpler:

Much better, isn’t it? In the next sections we will explore other features of the library to further polish the API.

Flexible filtering

Let’s assume, that our Customer entity has multiple names mapped in an embedded Names class:

Let’s also assume, that we want to send a single name HTTP query parameter ( /customers?name=Homer). All three names should be compared against the value of that parameter. We can easily achieve that by providing params value to @Spec annotation:

The above implementation would translate the following request:

into the following query:

Explicitly specifying HTTP parameters is especially useful for date range filtering. Let’s assume that we want to find all customers registered in the given period:

We can achieve that as follows:

Filtering by Join attributes

What if we need to filter by attributes of joined entities? For example, our customers may have multiple addresses associated with them:

We can use @Join  annotation to specify the join and the related alias. Then we can just use the alias in @Spec:

Of course you can use @Join with @And and @Or freely. You can also move annotations to a separate interface. A more complex example could have the form of:

Dealing with soft deletes

It is a common practice for web and enterprise applications to use soft delete, i.e. whenever an entity is supposed to be deleted, it is just marked with a flag (e.g. deleted = true ) instead of being physically removed from the database. This approach has many benefits, but makes querying more complex — we have to add where deleted = false to each search method. It is tedious and error-prone, so let’s see how we can make it easier.

For starters, we prepare the following specification interface:

Then we can use it as a foundation for our search methods. The first option is to use it as a controller method parameter and add additional annotations to it:

And this is it. We just have to use NotDeletedEntity instead of Specification as the controller method parameter type and the and deleted = false clause will be added to the query.

The second option is useful when we want to keep all the annotations on types instead of parameters. We can use interface inheritance as follows:

All specification annotations from parent interfaces are combined with and keyword which is exactly what we need for the API.

Summary

This post presented implementation challenges related to RESTful search API with Spring Web and Spring Data. The naive approach resulted in the following problems:

  • repository method explosion — each combination of HTTP query params needed a separate method in the repository
  • tedious code in the controller — monstrous if-else logic to determine the parameter combination and select appropriate repository method

Spring Data Specification support simplified the implementation. There was no need to write multiple repository methods and the controller code was much simpler. Still, there was a need to write specification classes and combine them manually.

Finally, Specification Argument Resolver library was presented. It generates specifications on the fly, based on annotations. It also provides a simple way to combine different specifications (with or or and logic), resolves joins and handles soft deletes easily. The resulting implementation not only provides a flexible API but is also very concise. Of course there are other libraries which you can use (e.g. Querydsl), but I believe that the approach described here is a very lightweight option which you should consider for your project. Enjoy!

Additional resources

  1. Spring Data Specifications documentation
  2. Specification Argument Resolver documentation
  3. Sample Spring Boot project presenting described techniques
Please follow and like us:

Leave a Reply

Your email address will not be published. Required fields are marked *