eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Course – LJB – NPI EA (cat = Core Java)
announcement - icon

Code your way through and build up a solid, practical foundation of Java:

>> Learn Java Basics

Partner – LambdaTest – NPI EA (cat= Testing)
announcement - icon

Distributed systems often come with complex challenges such as service-to-service communication, state management, asynchronous messaging, security, and more.

Dapr (Distributed Application Runtime) provides a set of APIs and building blocks to address these challenges, abstracting away infrastructure so we can focus on business logic.

In this tutorial, we'll focus on Dapr's pub/sub API for message brokering. Using its Spring Boot integration, we'll simplify the creation of a loosely coupled, portable, and easily testable pub/sub messaging system:

>> Flexible Pub/Sub Messaging With Spring Boot and Dapr

1. Overview

Spring MVC and Spring Data each do a great job simplifying application development in their own right. But, what if we put them together?

In this tutorial, we’ll take a look at Spring Data’s web support and how its resolvers can reduce boilerplate and make our controllers more expressive.

Along the way, we’ll peek at Querydsl and what its integration with Spring Data looks like.

2. A Bit of Background

Spring Data’s web support is a set of web-related features implemented on top of the standard Spring MVC platform, aimed at adding extra functionality to the controller layer.

Spring Data web support’s functionality is built around several resolver classes. Resolvers streamline the implementation of controller methods that interoperate with Spring Data repositories and also enrich them with additional features.

These features include fetching domain objects from the repository layer, without having to explicitly call the repository implementations, and constructing controller responses that can be sent to clients as segments of data that support pagination and sorting.

Also, requests to controller methods that take one or more request parameters can be internally resolved to Querydsl queries.

3. A Demo Spring Boot Project

To understand how we can use Spring Data web support to improve our controllers’ functionality, let’s create a basic Spring Boot project.

Our demo project’s Maven dependencies are fairly standard, with a few exceptions that we’ll discuss later on:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

In this case, we included spring-boot-starter-web, as we’ll use it for creating a RESTful controller, spring-boot-starter-jpa for implementing the persistence layer, and spring-boot-starter-test for testing the controller API.

Since we’ll use H2 as the underlying database, we included com.h2database as well.

Let’s keep in mind that spring-boot-starter-web enables Spring Data web support by default. Hence, we don’t need to create any additional @Configuration classes to get it working within our application.

Conversely, for non-Spring Boot projects, we’d need to define a @Configuration class and annotate it with the @EnableWebMvc and @EnableSpringDataWebSupport annotations.

3.1. The Domain Class

Now, let’s add a simple User JPA entity class to the project, so we can have a working domain model to play with:

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    private final String name;
   
    // standard constructor / getters / toString

}

3.2. The Repository Layer

To keep the code simple, the functionality of our demo Spring Boot application will be narrowed to just fetching some User entities from an H2 in-memory database.

Spring Boot makes it easy to create repository implementations that provide minimal CRUD functionality out-of-the-box. Therefore, let’s define a simple repository interface that works with the User JPA entities:

@Repository
public interface UserRepository extends PagingAndSortingRepository<User, Long> {}

There’s nothing inherently complex in the definition of the UserRepository interface, except that it extends PagingAndSortingRepository.

This signals Spring MVC to enable automatic paging and sorting capabilities on database records.

3.3. The Controller Layer

Now, we need to implement at least a basic RESTful controller that acts as the middle tier between the client and the repository layer.

Therefore, let’s create a controller class, which takes a UserRepository instance in its constructor and adds a single method for finding User entities by id:

@RestController
public class UserController {

    @GetMapping("/users/{id}")
    public User findUserById(@PathVariable("id") User user) {
        return user;
    }
}

3.4.  Running the Application

Finally, let’s define the application’s main class and populate the H2 database with a few User entities:

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    CommandLineRunner initialize(UserRepository userRepository) {
        return args -> {
            Stream.of("John", "Robert", "Nataly", "Helen", "Mary").forEach(name -> {
                User user = new User(name);
                userRepository.save(user);
            });
            userRepository.findAll().forEach(System.out::println);
        };
    }
}

Now, let’s run the application. As expected, we see the list of persisted User entities printed out to the console on startup:

User{id=1, name=John}
User{id=2, name=Robert}
User{id=3, name=Nataly}
User{id=4, name=Helen}
User{id=5, name=Mary}

4. The DomainClassConverter Class

For now, the UserController class only implements the findUserById() method.

At first sight, the method implementation looks fairly simple. But it actually encapsulates a lot of Spring Data web support functionality behind the scenes.

Since the method takes a User instance as an argument, we might end up thinking that we need to explicitly pass the domain object in the request. But, we don’t.

Spring MVC uses the DomainClassConverter class to convert the id path variable into the domain class’s id type and uses it for fetching the matching domain object from the repository layer. No further lookup is necessary.

For instance, a GET HTTP request to the http://localhost:8080/users/1 endpoint will return the following result:

{
  "id":1,
  "name":"John"
}

Hence, we can create an integration test and check the behavior of the findUserById() method:

@Test
public void whenGetRequestToUsersEndPointWithIdPathVariable_thenCorrectResponse() throws Exception {
    mockMvc.perform(MockMvcRequestBuilders.get("/users/{id}", "1")
      .contentType(MediaType.APPLICATION_JSON_UTF8))
      .andExpect(MockMvcResultMatchers.status().isOk())
      .andExpect(MockMvcResultMatchers.jsonPath("$.id").value("1"));
    }
}

Alternatively, we can use a REST API test tool, such as Postman, to test the method.

The nice thing about DomainClassConverter is that we don’t need to explicitly call the repository implementation in the controller method.

By simply specifying the id path variable, along with a resolvable domain class instance, we’ve automatically triggered the domain object’s lookup.

5. The PageableHandlerMethodArgumentResolver Class

Spring MVC supports the use of Pageable types in controllers and repositories.

Simply put, a Pageable instance is an object that holds paging information. Therefore, when we pass a Pageable argument to a controller method, Spring MVC uses the PageableHandlerMethodArgumentResolver class to resolve the Pageable instance into a PageRequest object, which is a simple Pageable implementation.

5.1. Using Pageable as a Controller Method Parameter

To understand how the PageableHandlerMethodArgumentResolver class works, let’s add a new method to the UserController class:

@GetMapping("/users")
public Page<User> findAllUsers(Pageable pageable) {
    return userRepository.findAll(pageable);
}

In contrast to the findUserById() method, here we need to call the repository implementation to fetch all the User JPA entities persisted in the database.

Since the method takes a Pageable instance, it returns a subset of the entire set of entities, stored in a Page<User> object.

A Page object is a sublist of a list of objects that exposes several methods we can use for retrieving information about the paged results, including the total number of result pages, and the number of the page that we’re retrieving.

By default, Spring MVC uses the PageableHandlerMethodArgumentResolver class to construct a PageRequest object, with the following request parameters:

  • page: the index of page that we want to retrieve – the parameter is zero-indexed and its default value is 0
  • size: the number of pages that we want to retrieve – the default value is 20
  • sort: one or more properties that we can use for sorting the results, using the following format: property1,property2(,asc|desc) – for instance, ?sort=name&sort=email,asc

For example, a GET request to the http://localhost:8080/users endpoint will return the following output:

{
  "content":[
    {
      "id":1,
      "name":"John"
    },
    {
      "id":2,
      "name":"Robert"
    },
    {
      "id":3,
      "name":"Nataly"
    },
    {
      "id":4,
      "name":"Helen"
    },
    {
      "id":5,
      "name":"Mary"
    }],
  "pageable":{
    "sort":{
      "sorted":false,
      "unsorted":true,
      "empty":true
    },
    "pageSize":5,
    "pageNumber":0,
    "offset":0,
    "unpaged":false,
    "paged":true
  },
  "last":true,
  "totalElements":5,
  "totalPages":1,
  "numberOfElements":5,
  "first":true,
  "size":5,
  "number":0,
  "sort":{
    "sorted":false,
    "unsorted":true,
    "empty":true
  },
  "empty":false
}

As we can see, the response includes the first, pageSize, totalElements, and totalPages JSON elements. This is really useful since a front-end can use these elements for easily creating a paging mechanism.

In addition, we can use an integration test to check the findAllUsers() method:

@Test
public void whenGetRequestToUsersEndPoint_thenCorrectResponse() throws Exception {
    mockMvc.perform(MockMvcRequestBuilders.get("/users")
      .contentType(MediaType.APPLICATION_JSON_UTF8))
      .andExpect(MockMvcResultMatchers.status().isOk())
      .andExpect(MockMvcResultMatchers.jsonPath("$['pageable']['paged']").value("true"));
}

5.2. Customizing the Paging Parameters

In many cases, we’ll want to customize the paging parameters. The simplest way to accomplish this is by using the @PageableDefault annotation:

@GetMapping("/users")
public Page<User> findAllUsers(@PageableDefault(value = 2, page = 0) Pageable pageable) {
    return userRepository.findAll(pageable);
}

Alternatively, we can use PageRequest‘s of() static factory method to create a custom PageRequest object and pass it to the repository method:

@GetMapping("/users")
public Page<User> findAllUsers() {
    Pageable pageable = PageRequest.of(0, 5);
    return userRepository.findAll(pageable);
}

The first parameter is the zero-based page index, while the second one is the size of the page that we want to retrieve.

In the example above, we created a PageRequest object of User entities, starting with the first page (0), with the page having 5 entries.

Additionally, we can build a PageRequest object using the page and size request parameters:

@GetMapping("/users")
public Page<User> findAllUsers(@RequestParam("page") int page, 
  @RequestParam("size") int size, Pageable pageable) {
    return userRepository.findAll(pageable);
}

Using this implementation, a GET request to the http://localhost:8080/users?page=0&size=2 endpoint will return the first page of User objects, and the size of the result page will be 2:

{
  "content": [
    {
      "id": 1,
      "name": "John"
    },
    {
      "id": 2,
      "name": "Robert"
    }
  ],
   
  // continues with pageable metadata
  
}

6. The SortHandlerMethodArgumentResolver Class

Paging is the de-facto approach for efficiently managing large numbers of database records. But, on its own, it’s pretty useless if we can’t sort the records in some specific way.

To this end, Spring MVC provides the SortHandlerMethodArgumentResolver class. The resolver automatically creates Sort instances from request parameters or from @SortDefault annotations.

6.1. Using the sort Controller Method Parameter

To get a clear idea of how the SortHandlerMethodArgumentResolver class works, let’s add the findAllUsersSortedByName() method to the controller class:

@GetMapping("/sortedusers")
public Page<User> findAllUsersSortedByName(@RequestParam("sort") String sort, Pageable pageable) {
    return userRepository.findAll(pageable);
}

In this case, the SortHandlerMethodArgumentResolver class will create a Sort object by using the sort request parameter.

As a result, a GET request to the http://localhost:8080/sortedusers?sort=name endpoint will return a JSON array, with the list of User objects sorted by the name property:

{
  "content": [
    {
      "id": 4,
      "name": "Helen"
    },
    {
      "id": 1,
      "name": "John"
    },
    {
      "id": 5,
      "name": "Mary"
    },
    {
      "id": 3,
      "name": "Nataly"
    },
    {
      "id": 2,
      "name": "Robert"
    }
  ],
  
  // continues with pageable metadata
  
}

6.2. Using the Sort.by() Static Factory Method

Alternatively, we can create a Sort object by using the Sort.by() static factory method, which takes a non-null, non-empty array of String properties to be sorted.

In this case, we’ll sort the records only by the name property:

@GetMapping("/sortedusers")
public Page<User> findAllUsersSortedByName() {
    Pageable pageable = PageRequest.of(0, 5, Sort.by("name"));
    return userRepository.findAll(pageable);
}

Of course, we could use multiple properties, as long as they’re declared in the domain class.

6.3. Using the @SortDefault Annotation

Likewise, we can use the @SortDefault annotation and get the same results:

@GetMapping("/sortedusers")
public Page<User> findAllUsersSortedByName(@SortDefault(sort = "name", 
  direction = Sort.Direction.ASC) Pageable pageable) {
    return userRepository.findAll(pageable);
}

Finally, let’s create an integration test to check the method’s behavior:

@Test
public void whenGetRequestToSorteredUsersEndPoint_thenCorrectResponse() throws Exception {
    mockMvc.perform(MockMvcRequestBuilders.get("/sortedusers")
      .contentType(MediaType.APPLICATION_JSON_UTF8))
      .andExpect(MockMvcResultMatchers.status().isOk())
      .andExpect(MockMvcResultMatchers.jsonPath("$['sort']['sorted']").value("true"));
}

7. Querydsl Web Support

As we mentioned in the introduction, Spring Data web support allows us to use request parameters in controller methods to build Querydsl‘s Predicate types and to construct Querydsl queries.

To keep things simple, we’ll just see how Spring MVC converts a request parameter into a Querydsl BooleanExpression, which in turn is passed to a QuerydslPredicateExecutor.

To accomplish this, first we need to add the querydsl-apt and querydsl-jpa Maven dependencies to the pom.xml file:

<dependency>
    <groupId>com.querydsl</groupId>
    <artifactId>querydsl-apt</artifactId>
    <version>5.1.0</version>
    <classifier>jakarta</classifier>
</dependency>
<dependency>
    <groupId>com.querydsl</groupId>
    <artifactId>querydsl-jpa</artifactId>
    <version>5.1.0</version>
    <classifier>jakarta</classifier>
</dependency>

 

Next, we need to refactor our UserRepository interface, which must also extend the QuerydslPredicateExecutor interface:

@Repository
public interface UserRepository extends PagingAndSortingRepository<User, Long>,
  QuerydslPredicateExecutor<User> {
}

Finally, let’s add the following method to the UserController class:

@GetMapping("/filteredusers")
public Iterable<User> getUsersByQuerydslPredicate(@QuerydslPredicate(root = User.class) 
  Predicate predicate) {
    return userRepository.findAll(predicate);
}

Although the method implementation looks fairly simple, it actually exposes a lot of functionality beneath the surface.

Let’s say that we want to fetch from the database all the User entities that match a given name. We can achieve this by just calling the method and specifying a name request parameter in the URL:

http://localhost:8080/filteredusers?name=John

As expected, the request will return the following result:

[
  {
    "id": 1,
    "name": "John"
  }
]

As we did before, we can use an integration test to check the getUsersByQuerydslPredicate() method:

@Test
public void whenGetRequestToFilteredUsersEndPoint_thenCorrectResponse() throws Exception {
    mockMvc.perform(MockMvcRequestBuilders.get("/filteredusers")
      .param("name", "John")
      .contentType(MediaType.APPLICATION_JSON_UTF8))
      .andExpect(MockMvcResultMatchers.status().isOk())
      .andExpect(MockMvcResultMatchers.jsonPath("$[0].name").value("John"));
}

This is just a basic example of how Querydsl web support works. But it actually doesn’t reveal all of its power.

Now, let’s say that we want to fetch a User entity that matches a given id. In such a case, we just need to pass an id request parameter in the URL:

http://localhost:8080/filteredusers?id=2

In this case, we’ll get this result:

[
  {
    "id": 2,
    "name": "Robert"
  }
]

It’s clear to see that Querydsl web support is a very powerful feature that we can use to fetch database records matching a given condition.

In all the cases, the whole process boils down to just calling a single controller method with different request parameters.

8. Conclusion

In this tutorial, we took an in-depth look at Spring web support’s key components and learned how to use it within a demo Spring Boot project.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

Course – LS – NPI (cat=REST)
announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

eBook Jackson – NPI EA – 3 (cat = Jackson)