Skip to content

easy-paging

Annotation-driven pagination for Spring Boot + MyBatis. Offset (PageHelper) and keyset/cursor in one starter.

Get Started GitHub Maven Central


At a glance

Drop one annotation on a controller method and get a JSON-ready paginated response. With the standard Controller → Service → Mapper layering, the controller stays thin:

@RestController
class ReportController {

    private final ReportService reports;

    ReportController(ReportService reports) {
        this.reports = reports;
    }

    @GetMapping("/reports")
    @AutoPaginate(maxSize = 50)                          // (1)!
    public PageResponse<Report> list(Pageable pageable) {
        return PageResponse.from(reports.findAll(), pageable);
    }
}
  1. The aspect sets up PageHelper's per-thread state, validates the sort parameter, clamps the page size, then cleans up after the mapper call returns.

A request to GET /reports?page=0&size=20&sort=createdAt,desc returns:

{
  "content": [ /* 20 rows */ ],
  "page": 0,
  "size": 20,
  "totalElements": 137,
  "totalPages": 7,
  "first": true,
  "last": false,
  "empty": false
}

No PageHelper.startPage(...) calls scattered through your codebase, no ThreadLocal cleanup to remember, no per-controller boilerplate.

What you get

  • Safe by default

    ?sort=name;DROP TABLE rejected with HTTP 400 before reaching the database. Page size clamped at endpoint + global level.

  • Spring Data shaped

    JSON response matches Spring Data Page for client compatibility. Drop-in for teams already using Pageable.

  • Offset and keyset

    @AutoPaginate for traditional lists, @KeysetPaginate for time-series and unbounded tables where OFFSET/COUNT(*) start to hurt.

  • 0-based, consistent

    Spring Data convention throughout — request, response, and internal Pageable. PageHelper's 1-based indexing handled transparently.

  • Pluggable response shape

    Replace the default envelope with your company's standard response type via PageResponseFactory, or just define your own type with a static from() method.

  • Virtual Threads safe

    Internal state cleaned up on every request, regardless of exception path. No ThreadLocal leaks.

Quick install

build.gradle.kts
dependencies {
    implementation("kr.devslab:easy-paging-spring-boot-starter:0.1.1")
}

See Installation for prerequisites and full setup, or jump to the Tutorial for a 5-minute walkthrough.

Where to go next