-
Notifications
You must be signed in to change notification settings - Fork 44
Cursor based pagination (RFC9865)
Starting with release 1.33.0, SCIM-SDK supports RFC 9865 cursor-based pagination in addition to the existing index-based pagination mechanism. Thanks to @papegaaij.
The implementation was designed to remain fully backwards compatible with existing SCIM integrations.
Existing clients using startIndex and count continue to work without any changes.
Clients that support RFC 9865 may switch to cursor-based pagination by using the cursor query parameter.
Traditional SCIM pagination is index-based.
Example:
GET /Users?startIndex=1&count=100
GET /Users?startIndex=101&count=100
GET /Users?startIndex=201&count=100This approach works well for small datasets but becomes problematic for larger collections.
Typical problems:
- Databases often need to skip increasingly large result sets
- Inserts and deletes between requests may shift pages
- Deep pagination becomes increasingly expensive
- Performance degrades with large offsets
Cursor-based pagination solves this by continuing from a stable position instead of using offsets.
Example:
GET /Users?count=100
GET /Users?cursor=eyJvZmZzZXQiOjEwMH0
GET /Users?cursor=eyJvZmZzZXQiOjIwMH0Cursor pagination is configured through paginationConfig.
Example:
.paginationConfig(
PaginationConfig.builder()
.cursor(true)
.index(true)
.defaultPaginationMethod(PaginationConfig.METHOD_INDEX)
.defaultPageSize(10)
.maxPageSize(50)
.cursorTimeout(3600)
.build()
)| Property | Description |
|---|---|
cursor |
Enables RFC 9865 cursor-based pagination |
index |
Enables classic SCIM index pagination |
defaultPaginationMethod |
Defines which pagination mode is used if the client does not specify one |
defaultPageSize |
Default page size |
maxPageSize |
Upper limit for page size |
cursorTimeout |
Cursor validity duration in seconds |
SCIM-SDK supports operating both pagination mechanisms simultaneously.
Request:
GET /Users?startIndex=1&count=10Response:
{
"totalResults": 100,
"itemsPerPage": 10,
"startIndex": 1,
"Resources": []
}Request:
GET /Users?count=10Response:
{
"totalResults": 100,
"itemsPerPage": 10,
"nextCursor": "eyJvZmZzZXQiOjEw",
"Resources": []
}Next request:
GET /Users?cursor=eyJvZmZzZXQiOjEwNo additional implementation is required when using SDK auto-processing.
If:
- auto filtering is enabled
- auto sorting is enabled
- auto pagination is enabled
cursor handling works automatically.
Example:
@Override
public PartialListResponse<User> listResources(
long startIndex,
int count,
FilterNode filter,
Sort sort)
{
return PartialListResponse.of(users);
}Internally, SCIM-SDK translates cursor requests into internal pagination windows.
The default cursor implementation currently uses a base64url encoded offset representation.
ListResponse was extended with optional RFC 9865 fields.
Example:
{
"nextCursor": "...",
"previousCursor": "..."
}Rules:
-
startIndexis omitted for cursor-only responses -
nextCursoris omitted if no next page exists -
previousCursoris omitted if no previous page exists
SCIM-SDK provides a generic RFC 9865 implementation that works automatically with the built-in pagination support.
However, users should understand an important limitation.
The default SCIM-SDK implementation does not generate stable cursors.
When the internal implementation is used, the SDK generates cursors by encoding the internally calculated startIndex value using base64url encoding.
Example:
startIndex=101
↓
cursor=MTAx
When a subsequent request is executed:
GET /Users?cursor=MTAxthe cursor is decoded again and internally translated into:
GET /Users?startIndex=101This means that the internal cursor implementation effectively acts as a compatibility layer on top of the existing index-based pagination mechanism.
Because cursors are translated back into offsets:
- cursors are not stable
- inserts and deletes between requests may shift result windows
- clients may observe duplicates or skipped resources
- deep pagination performance characteristics remain similar to index-based pagination
Therefore, enabling cursor support alone does not automatically improve performance or consistency.
Applications that require true cursor semantics should implement their own pagination strategy.
Examples include:
- database continuation tokens
- keyset pagination
- datastore-specific cursors
- snapshot-based pagination
Custom implementations can provide:
- stable page traversal
- better consistency
- improved performance on large datasets
The built-in implementation primarily exists to provide RFC 9865 compatibility and an easy migration path for existing SCIM implementations.