java 136 lines · 4 tabs

Cursor-Based Pagination with HATEOAS Links in Spring Data REST

Shared by codesnips Sep 2026
4 tabs
import org.springframework.hateoas.RepresentationModel;
import java.math.BigDecimal;

public class ProductModel extends RepresentationModel<ProductModel> {

    private final Long id;
    private final String name;
    private final BigDecimal price;

    public ProductModel(Long id, String name, BigDecimal price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public BigDecimal getPrice() {
        return price;
    }
}
4 files · java Explain with highlit

This snippet shows how a Spring Boot controller returns a page of results wrapped as a HATEOAS PagedModel, so clients navigate via hypermedia links instead of guessing URL parameters. The core idea of HATEOAS is that a response carries the controls needed to move around the API — self, next, prev, first, last — so pagination state lives in the payload rather than in client-side URL construction.

In Product entity, the JPA entity is a plain aggregate with an id, name, and price. It exists mainly so the repository can return a Page<Product>; nothing about the entity is coupled to the representation.

ProductModelAssembler implements RepresentationModelAssembler, the standard Spring HATEOAS hook for converting a domain object into a RepresentationModel. Its toModel builds a ProductModel and attaches a self link generated with linkTo(methodOn(...)), which reflects over the controller method so the URL is never hardcoded. Centralizing link creation here keeps every product representation consistent and avoids duplicating URI logic across endpoints.

ProductController is where pagination and hypermedia meet. It accepts a Spring Pageable, queries the repository, then delegates to a PagedResourcesAssembler. That assembler is the key piece: toModel(page, assembler) turns a Page into a PagedModel, embedding each item with its self link and adding the navigational links plus a page metadata block (size, total elements, total pages, number). The @GetMapping returning PagedModel<ProductModel> means the framework serializes it as HAL, including _links and _embedded.

The main trade-off is that offset pagination via Pageable is simple and portable but can drift or skip rows when the underlying data changes between page fetches, and deep offsets get slow because the database must scan and discard earlier rows. For large or frequently mutated datasets, keyset (cursor) pagination is more stable, though it complicates last-link generation. A subtle pitfall is forgetting to expose the assembler as a bean or bypassing methodOn, which produces brittle string URLs. This approach is worth reaching for when an API should be self-descriptive and evolvable, letting clients follow next until it disappears rather than encoding page math themselves.


Related snips

ruby
class PostsController < ApplicationController
  def index
    @posts = Post.includes(:author)
                 .order(created_at: :desc)
                 .page(params[:page])
                 .per(10)

Turbo Frames: infinite scroll with lazy-loading frame

rails turbo hotwire
by codesnips 4 tabs
graphql
type User {
    id: ID!
    name: String!
    email: String!
    posts: [Post!]!
    createdAt: String!

GraphQL API with Spring Boot

java graphql spring-boot
by David Kumar 3 tabs
ruby
class SignupForm
  include ActiveModel::Model
  include ActiveModel::Attributes

  attribute :account_name, :string
  attribute :email, :string

Shallow Controller, Deep Params: Form Object Pattern

rails activemodel form-object
by codesnips 3 tabs
javascript
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["form"]
  static values = { delay: { type: Number, default: 250 } }

Debounced live search with Stimulus + Turbo Streams

rails hotwire stimulus
by codesnips 4 tabs
java
package com.example.starter.config;

import com.example.starter.properties.CustomProperties;
import com.example.starter.service.CustomService;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;

Custom Spring Boot starters

java spring-boot starter
by David Kumar 4 tabs
java
package com.example.demo.config;

import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.kafka.common.serialization.StringSerializer;

Messaging with Apache Kafka

java kafka messaging
by David Kumar 3 tabs

Share this code

Here's the card — post it anywhere.

Cursor-Based Pagination with HATEOAS Links in Spring Data REST — share card
Link copied