java 41 lines · 8 steps

How a paginated REST controller works in Spring

A Spring REST controller exposes paginated, filterable product listings and maps entities to clean response DTOs.

Explained by highlit
1@RestController
2@RequestMapping("/api/products")
3public class ProductController {
4 
5 private final ProductRepository productRepository;
6 
7 public ProductController(ProductRepository productRepository) {
8 this.productRepository = productRepository;
9 }
10 
11 @GetMapping
12 public Page<ProductResponse> listProducts(
13 @RequestParam(required = false) String category,
14 @PageableDefault(size = 20, sort = "createdAt", direction = Sort.Direction.DESC)
15 Pageable pageable) {
16 
17 Page<Product> products = (category == null || category.isBlank())
18 ? productRepository.findAll(pageable)
19 : productRepository.findByCategoryIgnoreCase(category, pageable);
20 
21 return products.map(ProductResponse::from);
22 }
23 
24 @GetMapping("/{id}")
25 public ResponseEntity<ProductResponse> getProduct(@PathVariable Long id) {
26 return productRepository.findById(id)
27 .map(ProductResponse::from)
28 .map(ResponseEntity::ok)
29 .orElseGet(() -> ResponseEntity.notFound().build());
30 }
31 
32 public record ProductResponse(Long id, String name, String category, BigDecimal price) {
33 static ProductResponse from(Product product) {
34 return new ProductResponse(
35 product.getId(),
36 product.getName(),
37 product.getCategory(),
38 product.getPrice());
39 }
40 }
41}
01 / 01
STEP 01

Walkthrough

Space play ←→ step click any line
Three takeaways
  1. 1Mapping entities to dedicated response records keeps your API contract decoupled from your persistence model.
  2. 2Spring's Pageable and @PageableDefault hand you paging and sorting from query parameters with almost no code.
  3. 3Returning ResponseEntity lets a single handler express both success and 404 outcomes cleanly.

Related explainers

typescript
import { registerLocaleData } from '@angular/common';
import localeFr from '@angular/common/locales/fr';
import localeFrExtra from '@angular/common/locales/extra/fr';
import localeDe from '@angular/common/locales/de';

Locale-aware bootstrapping in Angular

i18n localization dependency-injection
Intermediate 8 steps
typescript
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import * as Joi from 'joi';
 

Validating env config at boot in NestJS

configuration schema-validation environment-variables
Intermediate 8 steps
java
@Component
@Converter
public class EncryptedStringConverter implements AttributeConverter<String, String> {
 

Transparent column encryption in Spring & JPA

encryption aes-gcm jpa-converter
Advanced 10 steps
typescript
import { Inject, Injectable, Logger } from '@nestjs/common';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';
import { InjectRepository } from '@nestjs/typeorm';

A cache-aside country lookup in NestJS

cache-aside dependency-injection batch-lookup
Intermediate 8 steps
java
package com.acme.billing.config;
 
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;

Feature-flagged beans with Spring @ConditionalOnProperty

feature-flags conditional-beans strategy-pattern
Intermediate 5 steps
typescript
import { Injectable, effect, signal, computed } from '@angular/core';
 
interface Preferences {
  theme: 'light' | 'dark';

A signal-based preferences store in Angular

signals state-management persistence
Intermediate 7 steps

Share this explainer

Here's the card — post it anywhere.

How a paginated REST controller works in Spring — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code