java 129 lines · 4 tabs

Paginated Product Search Endpoint with Spring Data JPA Specifications

Shared by codesnips Jul 2026
4 tabs
package com.example.catalog.web;

import com.example.catalog.domain.Product;
import java.math.BigDecimal;

public record ProductResponse(
        Long id,
        String name,
        String category,
        BigDecimal price) {

    public static ProductResponse from(Product product) {
        return new ProductResponse(
                product.getId(),
                product.getName(),
                product.getCategory(),
                product.getPrice());
    }
}
4 files · java Explain with highlit

This snippet shows a focused slice of a Spring Boot service: a REST endpoint that returns a filtered, paginated page of products backed by a Spring Data JPA repository. The pattern is common whenever a catalog or list view needs server-side filtering and paging so the client never pulls the whole table into memory.

The Product entity maps a table with Jakarta Persistence annotations. It uses a Long surrogate key with GenerationType.IDENTITY, which delegates key generation to the database (a natural fit for Postgres serial/identity columns). The price field uses BigDecimal with an explicit precision and scale so money is stored exactly rather than as a lossy floating point value, and category is indexed implicitly through the filter query below.

In ProductRepository, the interface extends JpaRepository to inherit CRUD and paging. The key method, search, is a derived-ish @Query that accepts nullable filter parameters and short-circuits each predicate with (:name IS NULL OR ...). This idiom lets a single query serve any combination of filters — passing null simply disables that clause — which avoids the combinatorial explosion of writing one method per filter permutation. Crucially the method takes a Pageable and returns a Page<Product>; Spring Data automatically appends LIMIT/OFFSET and issues a second count query so the returned Page knows the total element count.

The ProductController wires it together. @ParameterizedRequest-style binding is avoided in favor of Spring's automatic Pageable argument resolution: @PageableDefault sets a sane default page size and sort so an unbounded request can't ask for the entire table. Filters arrive as optional @RequestParam values that default to null, matching the repository's null-tolerant query. The controller maps each Product to a ProductResponse DTO via map(ProductResponse::from) on the Page, keeping the JPA entity out of the serialized contract — this prevents accidental lazy-loading during JSON rendering and lets the API shape evolve independently of the schema.

The main trade-off is that the null-guard query relies on the planner to optimize dead branches; for very hot paths a Specification or Querydsl build would generate tighter SQL. For most catalog endpoints, though, this approach is readable, cacheable, and safe against unbounded result sets.


Related snips

Share this code

Here's the card — post it anywhere.

Paginated Product Search Endpoint with Spring Data JPA Specifications — share card
Link copied