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.


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:

Related Post

17 response to "Effective RESTful search API in Spring"

  1. By: Karllos Ernnesto Posted: September 21, 2018

    First of all, i would like to congratulate you for this post .
    So i have a situation, in this type of approach with only Controller + Repo if i have a need to make some business calculation or some data manipulation, where can i put this? in the controller or i have to create another layer such as service?
    thx for support.

  2. By: BioDread Posted: February 20, 2019

    Great! You eliminated bunch of redundancy code from this world)
    Thank You.

  3. By: Ziggi Zagga Posted: March 18, 2019

    Mr. Tomasz Kaczmarzyk, may God bless you !!

  4. By: Srinivas Pakala Posted: April 3, 2019

    Mr. Tomasz Kaczmarzyk, the post is really admirable… Got high level confidence in writing complex search queries including joins..Hats off to you…

  5. By: rawn Posted: June 25, 2019

    Hi Tomasz,

    I got error below while hitting the request.

    java.lang.IllegalArgumentException: Invoked method public abstract javax.persistence.criteria.Predicate

    Can you please have a look.

  6. By: rawn Posted: June 25, 2019

    I am using Specification Argument Resolver

  7. By: Tomasz Kaczmarzyk Posted: June 25, 2019

    Hi Rawn, can you provide some test project / sample code to reproduce this issue?

  8. By: rawn Posted: June 25, 2019

    Hi Tomasz,

    Declared below in controller:

    @And({ @Spec(path = “firstName”, spec = LikeIgnoreCase.class), @Spec(path = “lastName”, spec = LikeIgnoreCase.class),
    @Spec(path = “status”, spec = LikeIgnoreCase.class) })
    interface RoomSpec extends Specification {

    and then the request handler method:

    public List searchRooms(RoomSpec rmSpec){

    return studentDAO.detailSearchRooms(rmSpec);


  9. By: rawn Posted: June 25, 2019

    Initially i was getting below error:
    “timestamp”: 1561475862565,
    “status”: 500,
    “error”: “Internal Server Error”,
    “exception”: “org.springframework.beans.BeanInstantiationException”,
    “message”: “Failed to instantiate [org.springframework.data.jpa.domain.Specification]: Specified class is an interface”,
    “path”: “/hostel/searchdetail”

    code was:

    /* Detail Search rooms */

    public List searchRooms(


    @Spec(path = “room_status”,params = “room_status”, spec =

    @Spec(path = “room_catgry”,params = “room_catgry”, spec =

    @Spec(path = “room_type”,params = “room_type”, spec = LikeIgnoreCase.class)
    }) Specification roomSpec ) {

    return studentDAO.detailSearchRooms(roomSpec);

    Then i changed.

  10. By: rawn Posted: June 26, 2019

    yes already enabled.

    public class StudentApplication {

    public void addArgumentResolvers(List argumentResolvers) {
    argumentResolvers.add(new SpecificationArgumentResolver());
    argumentResolvers.add(new PageableHandlerMethodArgumentResolver());

    public static void main(String[] args) throws Exception {
    SpringApplication.run(StudentApplication.class, args);

    static class Config {

    public void addArgumentResolvers(List argumentResolvers) {
    argumentResolvers.add(new SpecificationArgumentResolver());




  11. By: Agata Posted: July 2, 2019

    I tried combining @Join, @And with GreaterThan, ex.:

    @Join(path = “children”, alias = “c”)
    @Spec(path = “c.price”, params = “priceFrom”, spec = GreaterThan.class)

    It does not work, keeps throwing QuerySyntaxException – it tries to filter ‘price’ field but in parent class, not child. When I change spec to Equal, it does not throw an exception.

  12. By: Marat Posted: July 3, 2019

    Hey. I want to implement the methods in the search controller, but it does not work. I implement the hetal method getAll (), by default:
    on empty parameters on return:
    / rest / ships
    @RequestMapping (“/ rest”)
    public class ShipController {
    @GetMapping (value = “/ ships”, produces = {MediaType.APPLICATION_JSON_UTF8_VALUE})
        public Iterable getAll (@RequestParam (value = “pageNumber”, defaultValue = “0”) int page,
                                     @RequestParam (value = “pageSize”, defaultValue = “3”) int limit,
                                     @RequestParam (value = “order”, defaultValue = “id”) String orderBy) {

            Page list = service.getAll (page, limit, orderBy);
    This method works and the data is returned.
    Further, when filling in the search form, you must optionally add parameters, they may be in different combinations.
    For example: /rest/ships?&minSpeed=0.5&maxCrewSize=5000&pageNumber=0&pageSize=3&order=ID
    I did it like this:
    @GetMapping (value = “/ ships”, produces = {MediaType.APPLICATION_JSON_UTF8_VALUE}, params = {“pageNumber”, “pageSize”, “order”})
        public Iterable getAllWithFilter (
                @And ({
                        @Spec (path = “pageNumber”, defaultVal = “0”, spec = Equal.class),
                        @Spec (path = “pageSize”, defaultVal = “3”, spec = Equal.class),
                        @Spec (path = “order”, defaultVal = “id”, spec = Equal.class),
                        @Spec (path = “name”, spec = Like.class),
                        @Spec (path = “planet”, spec = Like.class),

                        @Spec (path = “after”, spec = DateAfter.class),
                        @Spec (path = “before”, spec = DateBefore.class),
                        @Spec (path = “minCrewSize”, spec = GreaterThanOrEqual.class),
                        @Spec (path = “maxCrewSize”, spec = LessThanOrEqual.class),
                        @Spec (path = “minSpeed”, spec = GreaterThanOrEqual.class),
                        @Spec (path = “maxSpeed”, spec = LessThanOrEqual.class),
                        @Spec (path = “minRating”, spec = GreaterThanOrEqual.class),
                        @Spec (path = “maxRating”, spec = LessThanOrEqual.class),
                        @Spec (path = “shipType”, spec = Equal.class),
                        @Spec (path = “isUsed”, spec = Equal.class, defaultVal = “false”)
                        Specification customerSpec) {

            return service.getAllWithFilter (customerSpec);
    This method does not work. I tried many different options, but, they do not work. Tell me how to properly implement the method.

  13. By: WALID Posted: April 29, 2020

    You saved my day 😀

Leave a Reply

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