java 127 lines · 3 tabs

Consume a Paginated REST API by Following Next-Page Links in Java

Shared by codesnips Sep 2026
3 tabs
package com.example.paging;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

import java.util.List;

public record PageResponse<T>(List<T> items, String nextUrl) {

    @JsonCreator
    public PageResponse(
            @JsonProperty("data") List<T> items,
            @JsonProperty("next") String nextUrl) {
        this.items = items == null ? List.of() : items;
        this.nextUrl = nextUrl;
    }

    public boolean hasNext() {
        return nextUrl != null && !nextUrl.isBlank();
    }
}
3 files · java Explain with highlit

This snippet shows how to consume a cursor-style paginated third-party API in Java by following the next link each page returns, exposing the whole result set as a lazy Stream so callers never have to think about page boundaries. It splits into three collaborating files: a typed page model, an HTTP client that fetches one page at a time, and an Iterator/Spliterator that walks the pages on demand.

In PageResponse, a single page is modeled as an immutable record with the items payload and a nullable nextUrl. This mirrors how many APIs work: rather than exposing total counts or numeric offsets, they hand back an opaque link to the next slice, which is more robust when the underlying data changes between requests. The hasNext() helper and the Jackson @JsonProperty on the next field keep the JSON contract explicit while leaving the record clean.

PagedApiClient owns the low-level concern of turning a URL into a PageResponse. It uses the built-in java.net.http.HttpClient, sends a synchronous GET, checks the status, and deserializes the body with a shared ObjectMapper. The important design choice is that fetchPage takes an absolute URL — the same method retrieves the first page and every subsequent one, because the API itself supplies the follow-up links. A non-2xx response is turned into an IOException so failures surface loudly instead of silently yielding empty pages.

PageIterator is where laziness lives. It holds only the URL of the next page to fetch, not the whole dataset. Each call to next() calls back into the client, buffers that page's items into a Deque, and remembers nextUrl for later. hasNext() drives the fetch, so pages are pulled only as the consumer asks for them. The static streamAll wraps the iterator in a Spliterator via Spliterators.spliteratorUnknownSize and flatMaps each page's items into one flat Stream<T>.

The payoff is that a caller can write streamAll(...).limit(50) and the client stops making HTTP calls as soon as fifty elements are produced, never fetching the tail of a huge result set. The main trade-offs are that network errors are wrapped in an unchecked UncheckedIOException to fit the Iterator contract, and that the stream is single-pass. This pattern fits any API using link-based or cursor-based pagination where prefetching everything would be wasteful.


Related snips

typescript
export interface RetryOptions {
  retries: number;
  baseMs: number;
  maxMs: number;
  signal?: AbortSignal;
  onRetry?: (attempt: number, delay: number, err: unknown) => void;

Exponential backoff with jitter for retries

typescript reliability retry
by codesnips 2 tabs
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
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
typescript
import React from "react";

type FallbackProps = {
  error: Error;
  reset: () => void;
};

React Error Boundary + error reporting hook

react frontend error-boundary
by codesnips 3 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.

Consume a Paginated REST API by Following Next-Page Links in Java — share card
Link copied