Repository: jakartaee/data
Pull request #73 (2022-11-07 22:09:12)

combine KeysetPageable into single Pageable interface
After reading #72, I realized we could possibly consolidate `KeysetPageable` and `Pageable` into a single `Pageable` interface, with the presence of a `Cursor` distinguishing whether keyset pagination is to be used.  This pull shows what that could look like.  It does seem to be a good way of simplifying the API.  The user no longer needs to think about there being two different pageables or decide which to put into their repository method signature or worry about type casting from one to the other.

Signed-off-by: Nathan Rauh <nathan.rauh@us.ibm.com>

otaviojava: I don't like this approach, especially, because we put a tight coupling between both and there is a risk of Pageable doing too much.
graemerocher: My feeling is the opposite, seems a nice simplification of the API rather than having an explosion of types which can be more confusing
keilw: Same for me, it goes against the ideas of abstracting the most common aspects I raised in #72. 
otaviojava: I like this point @keilw 
Btw: I saw the methods at `Pageable`

* KeysetPageable afterKeyset(Object... keyset);
* KeysetPageable beforeKeyset(Object... keyset);
* KeysetPageable afterKeysetCursor(Cursor keysetCursor);
* KeysetPageable beforeKeysetCursor(Cursor keysetCursor);

Should we remove it?
njr-11: > Btw: I saw the methods at `Pageable`
    * KeysetPageable afterKeyset(Object... keyset);
    * KeysetPageable beforeKeyset(Object... keyset);
    * KeysetPageable afterKeysetCursor(Cursor keysetCursor);
    * KeysetPageable beforeKeysetCursor(Cursor keysetCursor);
   Should we remove it?

No, removing those would lose out on core scenarios for keyset pagination: requesting keyset pagination from a particular position.
Instead, it might be possible that refactoring could be done to present the capability in a different way, such as in this pull where they are all switched to return `Pageable`, making them consistent in terms of return type with the other fluent-style methods of the interface.
otaviojava: @njr-11 could you rebase it, please?

Patch:
diff --git a/api/src/main/java/jakarta/data/repository/KeysetAwarePage.java b/api/src/main/java/jakarta/data/repository/KeysetAwarePage.java
index 0a855d1c..fd490f0e 100644
--- a/api/src/main/java/jakarta/data/repository/KeysetAwarePage.java
+++ b/api/src/main/java/jakarta/data/repository/KeysetAwarePage.java
@@ -19,7 +19,14 @@ package jakarta.data.repository;
 
 /**
  * <p>A page of results from a repository query that performs
- * {@link KeysetPageable keyset pagination}.</p>
+ * {@link KeysetAwareSlice keyset pagination}.</p>
+ *
+ * <p>The concept of {@link Page} differs from {@link Slice} in that slices
+ * do not have awareness of a total number of pages or results. Page numbers
+ * and total numbers of pages and results cannot be accurately known
+ * with keyset pagination, which allows entities to be added and removed
+ * in between traversal of slices or pages. When keyset pagination is used,
+ * these values should not be relied upon.</p>
  *
  * @param <T> the type of elements in this page 
  */
diff --git a/api/src/main/java/jakarta/data/repository/KeysetAwareSlice.java b/api/src/main/java/jakarta/data/repository/KeysetAwareSlice.java
index 7255363e..c40a277c 100644
--- a/api/src/main/java/jakarta/data/repository/KeysetAwareSlice.java
+++ b/api/src/main/java/jakarta/data/repository/KeysetAwareSlice.java
@@ -18,26 +18,112 @@
 package jakarta.data.repository;
 
 /**
- * <p>A page of results from a repository query that performs
- * {@link KeysetPageable keyset pagination}.</p>
+ * <p>Keyset pagination is a form of pagination that aims to reduce the
+ * possibility of missed or duplicate results by making the request for
+ * each subsequent page relative to the observed values of entity properties
+ * from the current page. This list of values is referred to as the keyset
+ * and only includes values of entity properties that are in the sort criteria
+ * of the repository method. The combination of sort criteria must uniquely
+ * identify each entity. The keyset values can be from the last entity
+ * (for pagination in a forward direction) or first entity on the page
+ * (if requesting pages in a reverse direction),
+ * or can be any other desired list of values which serve as a new starting
+ * point. Keyset pagination also has the potential to improve performance
+ * by avoiding the fetching and ordering of results from prior pages
+ * because these become non-matching.</p>
  *
- * <p>The concept of {@link Slice} differs from {@link Page} in that slices
- * do not have awareness of a total number of pages or results. This fits well
- * with keyset pagination, which allows entities to be added and removed
- * in between traversal of slices or pages and makes estimates of a total
- * inaccurate.</p>
+ * <p>To use keyset pagination, define a repository method with return value of
+ * {@link KeysetAwareSlice} or {@link KeysetAwarePage} and which accepts a
+ * special parameter (after the normal query parameters) that is a
+ * {@link Pageable}. For example,</p>
+ *
+ * <pre>
+ * &#64;OrderBy("lastName")
+ * &#64;OrderBy("firstName")
+ * &#64;OrderBy("id")
+ * KeysetAwareSlice&lt;Employee&gt; findByHoursWorkedGreaterThan(int hours, Pageable pagination);
+ * </pre>
+ *
+ * <p>You can use a normal {@link Pageable} to request an initial page,</p>
+ *
+ * <pre>
+ * page = employees.findByHoursWorkedGreaterThan(1500, Pageable.ofSize(50));
+ * </pre>
+ *
+ * <p>For subsequent pages, you can request pagination relative to the
+ * end of the current page as follows,</p>
+ *
+ * <pre>
+ * page = employees.findByHoursWorkedGreaterThan(1500, page.nextPageable());
+ * </pre>
+ *
+ * <p>Because the page is keyset aware, the {@link Pageable}
+ * that it returns from the call to {@link KeysetAwareSlice#nextPageable}
+ * above is based upon a keyset from that page to use as a starting point
+ * after which the results for the next page are to be found.</p>
+ *
+ * <p>You can also construct a {@link Pageable} with a {@link Pageable.Cursor Cursor} directly, which
+ * allows you to make it relative to a specific list of values. The number and
+ * order of values must match that of the {@link OrderBy} annotations,
+ * {@link Pageable#sortBy(Sort...)} or {@link Pageable#sortBy(Iterable)} parameters,
+ * or <code>OrderBy</code> name pattern of the repository method.
+ * For example,</p>
+ *
+ * <pre>
+ * Employee emp = ...
+ * Pageable pagination = Pageable.ofSize(50).afterKeyset(emp.lastName, emp.firstName, emp.id);
+ * page = employees.findByHoursWorkedGreaterThan(1500, pagination);
+ * </pre>
+ *
+ * <p>By making the query for the next page relative to observed values,
+ * not a numerical position, keyset pagination is less vulnerable to changes
+ * that are made to data in between page requests. Adding or removing entities
+ * is possible without causing unexpected missed or duplicate results.
+ * Keyset pagination does not prevent misses and duplicates if the entity
+ * properties which are the sort criteria for existing entities are modified
+ * or if an entity is re-added with different sort criteria after having
+ * previously been removed.</p>
+ *
+ * <h2>Keyset Pagination with &#64;Query</h2>
+ *
+ * <p>Keyset pagination involves generating and appending to the query
+ * additional query conditions for the keyset properties. In order for that to
+ * be possible, a user-provided
+ * <a href="https://eclipse-ee4j.github.io/jakartaee-tutorial/#full-query-language-syntax">JPQL</a>
+ * query must end with a
+ * <code>WHERE</code> clause to which additional conditions can be appended.
+ * Enclose the entire conditional expression of the <code>WHERE</code> clause
+ * in parenthesis.
+ * Sort criteria must be specified independently from the user-provided query,
+ * either with the {@link OrderBy} annotation or
+ * {@link Pageable#sortBy(Sort...)} or {@link Pageable#sortBy(Iterable)} parameters.
+ * For example,</p>
+ *
+ * <pre>
+ * &#64;Query("SELECT o FROM Customer o WHERE (o.ordersPlaced &gt;= ?1 OR o.totalSpent &gt;= ?2)")
+ * &#64;OrderBy("zipcode")
+ * &#64;OrderBy("birthYear")
+ * &#64;OrderBy("id")
+ * KeysetAwareSlice&lt;Customer&gt; getTopBuyers(int minOrders, float minSpent, Pageable pagination);
+ * </pre>
+ *
+ * <h2>Page Numbers and Totals</h2>
+ *
+ * <p>Page numbers, total numbers of elements across all pages, and total
+ * count of pages are not accurate when keyset pagination is used and should
+ * not be relied upon.</p>
  *
  * @param <T> the type of elements in this slice 
  */
 public interface KeysetAwareSlice<T> extends Slice<T> {
     /**
-     * Returns a {@link KeysetPageable.Cursor Cursor} for keyset values at the
+     * Returns a {@link Pageable.Cursor Cursor} for keyset values at the
      * specified position.
      *
      * @param index position (0 is first) of a result on the page.
      * @return cursor for keyset values at the specified position.
      */
-    KeysetPageable.Cursor getKeysetCursor(int index);
+    Pageable.Cursor getKeysetCursor(int index);
 
     /**
      * <p>Returns pagination information for requesting the next page
@@ -52,7 +138,7 @@ public interface KeysetAwareSlice<T> extends Slice<T> {
      *         or if it is known that there is not a next page.
      */
     @Override
-    KeysetPageable nextPageable();
+    Pageable nextPageable();
 
     /**
      * <p>Returns pagination information for requesting the previous page
@@ -79,5 +165,5 @@ public interface KeysetAwareSlice<T> extends Slice<T> {
      *         <code>null</code> if the current page is empty
      *         or if it is known that there is not a previous page.
      */
-    KeysetPageable previousPageable();
+    Pageable previousPageable();
 }
\ No newline at end of file
diff --git a/api/src/main/java/jakarta/data/repository/KeysetCursor.java b/api/src/main/java/jakarta/data/repository/KeysetCursor.java
new file mode 100644
index 00000000..5351689d
--- /dev/null
+++ b/api/src/main/java/jakarta/data/repository/KeysetCursor.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2022 Contributors to the Eclipse Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ */
+package jakarta.data.repository;
+
+import java.util.Arrays;
+
+    /**
+     * Built-in implementation of Cursor for keyset pagination.
+     */
+class KeysetCursor implements Pageable.Cursor {
+    /**
+     * Keyset values.
+     */
+    private final Object[] keyset;
+
+    /**
+     * Constructs a keyset cursor with the specified values.
+     *
+     * @param keyset keyset values.
+     * @throws IllegalArgumentException if no keyset values are provided.
+     */
+    KeysetCursor(Object... keyset) {
+        this.keyset = keyset;
+        if (keyset == null || keyset.length == 0)
+            throw new IllegalArgumentException("No keyset values were provided.");
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        return this == o || o != null
+                && o.getClass() == getClass()
+                && Arrays.equals(keyset, ((KeysetCursor) o).keyset);
+    }
+
+    public Object getKeysetElement(int index) {
+        return keyset[index];
+    }
+
+    @Override
+    public int hashCode() {
+        return Arrays.hashCode(keyset);
+    }
+
+    public int size() {
+        return keyset.length;
+    }
+
+    @Override
+    public String toString() {
+        return new StringBuilder(27).append("Cursor@").append(Integer.toHexString(hashCode()))
+                        .append(" with ").append(keyset.length).append(" keys")
+                        .toString();
+    }
+}
diff --git a/api/src/main/java/jakarta/data/repository/KeysetPageable.java b/api/src/main/java/jakarta/data/repository/KeysetPageable.java
deleted file mode 100644
index 91a0ffc1..00000000
--- a/api/src/main/java/jakarta/data/repository/KeysetPageable.java
+++ /dev/null
@@ -1,284 +0,0 @@
-/*
- * Copyright (c) 2022 Contributors to the Eclipse Foundation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- *  SPDX-License-Identifier: Apache-2.0
- */
-package jakarta.data.repository;
-
-/**
- * <p>Keyset pagination is a form of pagination that aims to reduce the
- * possibility of missed or duplicate results by making the request for
- * each subsequent page relative to the observed values of entity properties
- * from the current page. This list of values is referred to as the keyset
- * and only includes values of entity properties that are in the sort criteria
- * of the repository method. The combination of sort criteria must uniquely
- * identify each entity. The keyset values can be from the last entity
- * (for pagination in a forward direction) or first entity on the page
- * (if requesting pages in a reverse direction),
- * or can be any other desired list of values which serve as a new starting
- * point. Keyset pagination also has the potential to improve performance
- * by avoiding the fetching and ordering of results from prior pages
- * because these become non-matching.</p>
- *
- * <p>To use keyset pagination, define a repository method with return value of
- * {@link KeysetAwareSlice} or {@link KeysetAwarePage} and which accepts a
- * special parameter (after the normal query parameters) that is a
- * {@link Pageable}. For example,</p>
- *
- * <pre>
- * &#64;OrderBy("lastName")
- * &#64;OrderBy("firstName")
- * &#64;OrderBy("id")
- * KeysetAwareSlice&lt;Employee&gt; findByHoursWorkedGreaterThan(int hours, Pageable pagination);
- * </pre>
- *
- * <p>You can use a normal {@link Pageable} to request an initial page,</p>
- *
- * <pre>
- * page = employees.findByHoursWorkedGreaterThan(1500, Pageable.ofSize(50));
- * </pre>
- *
- * <p>For subsequent pages, you can request pagination relative to the
- * end of the current page as follows,</p>
- *
- * <pre>
- * page = employees.findByHoursWorkedGreaterThan(1500, page.nextPageable());
- * </pre>
- *
- * <p>Because the page is keyset aware, the <code>KeysetPageable</code>
- * that it returns from the call to {@link KeysetAwareSlice#nextPageable}
- * above is based upon a keyset from that page to use as a starting point
- * after which the results for the next page are to be found.</p>
- *
- * <p>You can also construct a <code>KeysetPageable</code> directly, which
- * allows you to make it relative to a specific list of values. The number and
- * order of values must match that of the {@link OrderBy} annotations,
- * {@link Pageable#sortBy(Sort...)} or {@link Pageable#sortBy(Iterable)} parameters,
- * or <code>OrderBy</code> name pattern of the repository method.
- * For example,</p>
- *
- * <pre>
- * Employee emp = ...
- * KeysetPageable pagination = Pageable.ofSize(50).afterKeyset(emp.lastName, emp.firstName, emp.id);
- * page = employees.findByHoursWorkedGreaterThan(1500, pagination);
- * </pre>
- *
- * <p>By making the query for the next page relative to observed values,
- * not a numerical position, keyset pagination is less vulnerable to changes
- * that are made to data in between page requests. Adding or removing entities
- * is possible without causing unexpected missed or duplicate results.
- * Keyset pagination does not prevent misses and duplicates if the entity
- * properties which are the sort criteria for existing entities are modified
- * or if an entity is re-added with different sort criteria after having
- * previously been removed.</p>
- *
- * <h2>Keyset Pagination with &#64;Query</h2>
- *
- * <p>Keyset pagination involves generating and appending to the query
- * additional query conditions for the keyset properties. In order for that to
- * be possible, a user-provided
- * <a href="https://eclipse-ee4j.github.io/jakartaee-tutorial/#full-query-language-syntax">JPQL</a>
- * query must end with a
- * <code>WHERE</code> clause to which additional conditions can be appended.
- * Enclose the entire conditional expression of the <code>WHERE</code> clause
- * in parenthesis.
- * Sort criteria must be specified independently from the user-provided query,
- * either with the {@link OrderBy} annotation or
- * {@link Pageable#sortBy(Sort...)} or {@link Pageable#sortBy(Iterable)} parameters.
- * For example,</p>
- *
- * <pre>
- * &#64;Query("SELECT o FROM Customer o WHERE (o.ordersPlaced &gt;= ?1 OR o.totalSpent &gt;= ?2)")
- * &#64;OrderBy("zipcode")
- * &#64;OrderBy("birthYear")
- * &#64;OrderBy("id")
- * KeysetAwareSlice&lt;Customer&gt; getTopBuyers(int minOrders, float minSpent, Pageable pagination);
- * </pre>
- *
- * <h2>Page Numbers and Totals</h2>
- *
- * <p>Page numbers, total numbers of elements across all pages, and total
- * count of pages are not accurate when keyset pagination is used and should
- * not be relied upon.</p>
- */
-public interface KeysetPageable extends Pageable {
-    /**
-     * Direction of keyset pagination.
-     */
-    enum Mode {
-        /**
-         * Indicates forward keyset pagination, which follows the
-         * direction of the {@link OrderBy} annotations,
-         * {@link Pageable#sortBy(Sort...)} or {@link Pageable#sortBy(Iterable)}
-         * parameters, repository method {@link Sort} parameters,
-         * or <code>OrderBy</code> name pattern of the repository method.
-         */
-        NEXT,
-
-        /**
-         * Indicates a request for a page in the reverse direction of
-         * the {@link OrderBy} annotations,
-         * {@link Pageable#sortBy(Sort...)} or {@link Pageable#sortBy(Iterable)}
-         * parameters, repository method {@link Sort} parameters,
-         * or <code>OrderBy</code> name pattern of the repository method.
-         * The order of results on each page follows the sort criteria
-         * and is not reversed.
-         */
-        PREVIOUS
-    }
-
-    /**
-     * Represents keyset values, which can be a starting point for
-     * requesting a next or previous page.
-     */
-    interface Cursor {
-        /**
-         * Returns whether or not the keyset values of this instance
-         * are equal to those of the supplied instance.
-         * Cursor implementation classes must also match to be equal.
-         *
-         * @return true or false.
-         */
-        @Override
-        boolean equals(Object o);
-
-        /**
-         * Returns the keyset value at the specified position.
-         *
-         * @param  index position (0 is first) of the keyset value to obtain.
-         * @return the keyset value at the specified position.
-         * @throws IndexOutOfBoundsException if the index is negative
-         *         or greater than or equal to the {@link #size}.
-         */
-        Object getKeysetElement(int index);
-
-        /**
-         * Returns a hash code based on the keyset values.
-         *
-         * @return a hash code based on the keyset values.
-         */
-        @Override
-        int hashCode();
-
-        /**
-         * Returns the number of values in the keyset.
-         *
-         * @return the number of values in the keyset.
-         */
-        int size();
-
-        /**
-         * String representation of the keyset cursor, including the number of
-         * key values in the cursor but not the values themselves.
-         *
-         * @return String representation of the keyset cursor.
-         */
-        @Override
-        String toString();
-    }
-
-    /**
-     * Returns the keyset values which are the starting point for
-     * keyset pagination.
-     *
-     * @return the keyset values.
-     */
-    Cursor cursor();
-
-    /**
-     * Returns the direction of keyset pagination.
-     *
-     * @return the direction of keyset pagination.
-     */
-    Mode mode();
-
-    /**
-     * Raises an error because traversal of pages with keyset pagination can
-     * only be done via the {@link KeysetAwareSlice#nextPageable()},
-     * {@link KeysetAwareSlice#previousPageable()}, or
-     * {@link KeysetAwareSlice#getKeysetCursor(int) keyset cursor}.
-     *
-     * @return unreachable
-     * @throws UnsupportedOperationException because this operation is not
-     *         supported for keyset pagination.
-     */
-    @Override
-    KeysetPageable next();
-
-    /**
-     * <p>Creates a new <code>KeysetPageable</code> instance representing the same
-     * pagination information, except with the specified page number.</p>
-     *
-     * @param pageNumber The page number
-     * @return a new instance of <code>KeysetPageable</code>. This method never returns <code>null</code>.
-     */
-    @Override
-    KeysetPageable newPage(long pageNumber);
-
-    /**
-     * <p>Creates a new <code>KeysetPageable</code> instance representing the same
-     * pagination information, except with the specified maximum page size.</p>
-     *
-     * @param maxPageSize the number of query results in a full page.
-     * @return a new instance of <code>KeysetPageable</code>. This method never returns <code>null</code>.
-     */
-    @Override
-    KeysetPageable newSize(int maxPageSize);
-
-    /**
-     * <p>Creates a new <code>KeysetPageable</code> instance representing the same
-     * pagination information, except using the specified sort criteria.
-     * The order of precedence of sort criteria is the order of the
-     * {@link Iterable} that is supplied to this method.</p>
-     *
-     * <p>A repository method will fail if a sort criteria is specified on a
-     * <code>KeysetPageable</code> in combination with any of:</p>
-     * <ul>
-     * <li>an <code>OrderBy</code> keyword</li>
-     * <li>an {@link OrderBy} annotation</li>
-     * <li>a {@link Query} annotation that contains an <code>ORDER BY</code> clause.</li>
-     * <li>{@link Sort} parameters that are specified independently of
-     *     <code>KeysetPageable</code> on a repository method</li>
-     * </ul>
-     *
-     * @param sorts sort criteria to use.
-     * @return a new instance of <code>KeysetPageable</code>. This method never returns <code>null</code>.
-     */
-    @Override
-    KeysetPageable sortBy(Iterable<Sort> sorts);
-
-    /**
-     * <p>Creates a new <code>KeysetPageable</code> instance representing the same
-     * pagination information, except using the specified sort criteria.
-     * The order of precedence of sort criteria is the order in which the
-     * {@link Sort} parameters to this method are listed.</p>
-     *
-     * <p>A repository method will fail if a sort criteria is specified on a
-     * <code>KeysetPageable</code> in combination with any of:</p>
-     * <ul>
-     * <li>an <code>OrderBy</code> keyword</li>
-     * <li>an {@link OrderBy} annotation</li>
-     * <li>a {@link Query} annotation that contains an <code>ORDER BY</code> clause.</li>
-     * <li>{@link Sort} parameters that are specified independently of
-     *     <code>KeysetPageable</code> on a repository method</li>
-     * </ul>
-     *
-     * @param sorts sort criteria to use. This method can be invoked without parameters
-     *        to request a <code>KeysetPageable</code> that does not specify sort criteria.
-     * @return a new instance of <code>KeysetPageable</code>. This method never returns <code>null</code>.
-     */
-    @Override
-    KeysetPageable sortBy(Sort... sorts);
-}
\ No newline at end of file
diff --git a/api/src/main/java/jakarta/data/repository/KeysetPagination.java b/api/src/main/java/jakarta/data/repository/KeysetPagination.java
deleted file mode 100644
index d5c980bc..00000000
--- a/api/src/main/java/jakarta/data/repository/KeysetPagination.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Copyright (c) 2022 Contributors to the Eclipse Foundation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- *  SPDX-License-Identifier: Apache-2.0
- */
-package jakarta.data.repository;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-import java.util.stream.Collectors;
-import java.util.stream.StreamSupport;
-
-/**
- * Built-in implementation of KeysetPageable.
- */
-final class KeysetPagination extends Pagination implements KeysetPageable {
-
-    private final Cursor cursor;
-    private final Mode mode;
-
-    KeysetPagination(long pageNumber, int maxPageSize, List<Sort> sorts, Mode mode, Cursor cursor) {
-        super(pageNumber, maxPageSize, sorts);
-
-        if (cursor == null || cursor.size() == 0)
-            throw new IllegalArgumentException("No keyset values were provided.");
-
-        this.cursor = cursor;
-        this.mode = mode;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        KeysetPagination p;
-        return this == o || o != null
-                && o.getClass() == getClass()
-                && (p = (KeysetPagination) o).size == size
-                && p.page == page
-                && p.mode == mode
-                && p.cursor.equals(cursor)
-                && p.sorts.equals(sorts);
-    }
-
-    @Override
-    public Cursor cursor() {
-        return cursor;
-    }
-
-    @Override
-    public Mode mode() {
-        return mode;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(size, page, sorts, mode, cursor);
-    }
-
-    @Override
-    public KeysetPageable next() {
-        throw new UnsupportedOperationException("Not supported for keyset pagination. Instead use afterKeyset or afterKeysetCursor " +
-                                                "to provide the next keyset values or obtain the nextPageable from a KeysetAwareSlice.");
-    }
-
-    @Override
-    public KeysetPageable newPage(long pageNumber) {
-        return new KeysetPagination(pageNumber, size, sorts, mode, cursor);
-    }
-
-    @Override
-    public KeysetPageable newSize(int maxPageSize) {
-        return new KeysetPagination(page, maxPageSize, sorts, mode, cursor);
-    }
-
-    @Override
-    public KeysetPageable sortBy(Iterable<Sort> sorts) {
-        List<Sort> sortList = sorts == null
-                ? Collections.emptyList()
-                : StreamSupport.stream(sorts.spliterator(), false).collect(Collectors.toUnmodifiableList());
-        return new KeysetPagination(page, size, sortList, mode, cursor);
-    }
-
-    @Override
-    public KeysetPageable sortBy(Sort... sorts) {
-        List<Sort> sortList = sorts == null ? Collections.emptyList() : List.of(sorts);
-        return new KeysetPagination(page, size, sortList, mode, cursor);
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder s = new StringBuilder(150)
-                .append("KeysetPageable{page=").append(page)
-                .append(", size=").append(size)
-                .append(", mode=").append(mode)
-                .append(", ").append(cursor.size()).append(" keys");
-        for (Sort sort : sorts) {
-            s.append(", ").append(sort.property()).append(sort.isAscending() ? " ASC" : " DESC");
-        }
-        return s.append("}").toString();
-    }
-
-    /**
-     * Built-in implementation of Cursor.
-     */
-    static final class CursorImpl implements Cursor {
-        /**
-         * Keyset values.
-         */
-        private final Object[] keyset;
-
-        /**
-         * Constructs a keyset cursor with the specified values.
-         *
-         * @param keyset keyset values.
-         * @throws IllegalArgumentException if no keyset values are provided.
-         */
-        CursorImpl(Object... keyset) {
-            this.keyset = keyset;
-            if (keyset == null || keyset.length == 0)
-                throw new IllegalArgumentException("No keyset values were provided.");
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            return this == o || o != null
-                    && o.getClass() == getClass()
-                    && Arrays.equals(keyset, ((CursorImpl) o).keyset);
-        }
-
-        public Object getKeysetElement(int index) {
-            return keyset[index];
-        }
-
-        @Override
-        public int hashCode() {
-            return Arrays.hashCode(keyset);
-        }
-
-        public int size() {
-            return keyset.length;
-        }
-
-        @Override
-        public String toString() {
-            return new StringBuilder(27).append("Cursor@").append(Integer.toHexString(hashCode()))
-                            .append(" with ").append(keyset.length).append(" keys")
-                            .toString();
-        }
-    }
-}
\ No newline at end of file
diff --git a/api/src/main/java/jakarta/data/repository/Pageable.java b/api/src/main/java/jakarta/data/repository/Pageable.java
index feaa5942..27922066 100644
--- a/api/src/main/java/jakarta/data/repository/Pageable.java
+++ b/api/src/main/java/jakarta/data/repository/Pageable.java
@@ -20,8 +20,6 @@ package jakarta.data.repository;
 import java.util.Collections;
 import java.util.List;
 
-import jakarta.data.repository.KeysetPageable.Cursor;
-
 /**
  * <p>This class represents pagination information.</p>
  *
@@ -61,7 +59,7 @@ public interface Pageable {
      * @throws IllegalArgumentException when the page number is negative or zero.
      */
     static Pageable ofPage(long pageNumber) {
-        return new Pagination(pageNumber, 10, Collections.emptyList());
+        return new Pagination(pageNumber, 10, Collections.emptyList(), Mode.OFFSET, null);
     }
 
     /**
@@ -73,64 +71,64 @@ public interface Pageable {
      * @throws IllegalArgumentException when maximum page size is negative or zero.
      */
     static Pageable ofSize(int maxPageSize) {
-        return new Pagination(1, maxPageSize, Collections.emptyList());
+        return new Pagination(1, maxPageSize, Collections.emptyList(), Mode.OFFSET, null);
     }
 
     /**
-     * <p>Requests {@link KeysetPageable keyset pagination} in the forward direction,
+     * <p>Requests {@link KeysetAwareSlice keyset pagination} in the forward direction,
      * starting after the specified keyset values.</p>
      *
      * @param keyset keyset values, the order and number of which must match the
      *        {@link OrderBy} annotations, {@link Sort} parameters, or
      *        <code>OrderBy</code> name pattern of the repository method to which
      *        this pagination will be supplied.
-     * @return a new instance of <code>KeysetPageable</code> with forward keyset pagination.
+     * @return a new instance of <code>Pageable</code> with forward keyset pagination.
      *         This method never returns <code>null</code>.
      * @throws IllegalArgumentException if no keyset values are provided.
      */
-    KeysetPageable afterKeyset(Object... keyset);
+    Pageable afterKeyset(Object... keyset);
 
     /**
-     * <p>Requests {@link KeysetPageable keyset pagination} in the reverse direction,
+     * <p>Requests {@link KeysetAwareSlice keyset pagination} in the reverse direction,
      * starting after the specified keyset values.</p>
      *
      * @param keyset keyset values, the order and number of which must match the
      *        {@link OrderBy} annotations, {@link Sort} parameters, or
      *        <code>OrderBy</code> name pattern of the repository method to which
      *        this pagination will be supplied.
-     * @return a new instance of <code>KeysetPageable</code> with reverse keyset pagination.
+     * @return a new instance of <code>Pageable</code> with reverse keyset pagination.
      *         This method never returns <code>null</code>.
      * @throws IllegalArgumentException if no keyset values are provided.
      */
-    KeysetPageable beforeKeyset(Object... keyset);
+    Pageable beforeKeyset(Object... keyset);
 
     /**
-     * <p>Requests {@link KeysetPageable keyset pagination} in the forward direction,
+     * <p>Requests {@link KeysetAwareSlice keyset pagination} in the forward direction,
      * starting after the specified keyset values.</p>
      *
      * @param keysetCursor cursor with keyset values, the order and number of which must match the
      *        {@link OrderBy} annotations, {@link Sort} parameters, or
      *        <code>OrderBy</code> name pattern of the repository method to which
      *        this pagination will be supplied.
-     * @return a new instance of <code>KeysetPageable</code> with forward keyset pagination.
+     * @return a new instance of <code>Pageable</code> with forward keyset pagination.
      *         This method never returns <code>null</code>.
      * @throws IllegalArgumentException if no keyset values are provided.
      */
-    KeysetPageable afterKeysetCursor(Cursor keysetCursor);
+    Pageable afterKeysetCursor(Cursor keysetCursor);
 
     /**
-     * <p>Requests {@link KeysetPageable keyset pagination} in the reverse direction,
+     * <p>Requests {@link KeysetAwareSlice keyset pagination} in the reverse direction,
      * starting after the specified keyset values.</p>
      *
      * @param keysetCursor cursor with keyset values, the order and number of which must match the
      *        {@link OrderBy} annotations, {@link Sort} parameters, or
      *        <code>OrderBy</code> name pattern of the repository method to which
      *        this pagination will be supplied.
-     * @return a new instance of <code>KeysetPageable</code> with reverse keyset pagination.
+     * @return a new instance of <code>Pageable</code> with reverse keyset pagination.
      *         This method never returns <code>null</code>.
      * @throws IllegalArgumentException if no keyset values are provided.
      */
-    KeysetPageable beforeKeysetCursor(Cursor keysetCursor);
+    Pageable beforeKeysetCursor(Cursor keysetCursor);
 
     /**
      * Compares with another instance to determine if both represent the same
@@ -142,6 +140,21 @@ public interface Pageable {
     @Override
     boolean equals(Object o);
 
+    /**
+     * Returns the keyset values which are the starting point for
+     * keyset pagination.
+     *
+     * @return the keyset values; <code>null</code> if using offset pagination.
+     */
+    Cursor cursor();
+
+    /**
+     * Returns the type of pagination.
+     *
+     * @return the type of pagination.
+     */
+    Mode mode();
+
     /**
      * Returns the page to be returned.
      *
@@ -164,9 +177,18 @@ public interface Pageable {
     List<Sort> sorts();
 
     /**
-     * Returns the <code>Pageable</code> requesting the next page.
+     * <p>Returns the <code>Pageable</code> requesting the next page
+     * if using offset pagination.</p>
+     *
+     * <p>If using keyset pagination, traversal of pages must only be done
+     * via the {@link KeysetAwareSlice#nextPageable()},
+     * {@link KeysetAwareSlice#previousPageable()}, or
+     * {@link KeysetAwareSlice#getKeysetCursor(int) keyset cursor},
+     * not with this method.</p>
      *
      * @return The next pageable.
+     * @throws UnsupportedOperationException if this <code>Pageable</code> has a
+     *         {@link Pageable.Cursor Cursor}.
      */
     Pageable next();
 
@@ -230,4 +252,88 @@ public interface Pageable {
      * @return a new instance of <code>Pageable</code>. This method never returns <code>null</code>.
      */
     Pageable sortBy(Sort... sorts);
+
+    /**
+     * The type of pagination, which can be offset pagination or
+     * keyset cursor pagination which includes a direction.
+     */
+    enum Mode {
+        /**
+         * Indicates forward keyset pagination, which follows the
+         * direction of the {@link OrderBy} annotations,
+         * {@link Pageable#sortBy(Sort...)} or {@link Pageable#sortBy(Iterable)}
+         * parameters, repository method {@link Sort} parameters,
+         * or <code>OrderBy</code> name pattern of the repository method.
+         */
+        CURSOR_NEXT,
+
+        /**
+         * Indicates a request for a page with keyset pagination
+         * in the reverse direction of the {@link OrderBy} annotations,
+         * {@link Pageable#sortBy(Sort...)} or {@link Pageable#sortBy(Iterable)}
+         * parameters, repository method {@link Sort} parameters,
+         * or <code>OrderBy</code> name pattern of the repository method.
+         * The order of results on each page follows the sort criteria
+         * and is not reversed.
+         */
+        CURSOR_PREVIOUS,
+
+        /**
+         * Indicates a reqeust for a page using offset pagination.
+         * The starting position for pages is computed as an offset from
+         * the first result based on the page number and maximum page size.
+         * Offset pagination is used when a cursor is not supplied.
+         */
+        OFFSET
+    }
+
+    /**
+     * Represents keyset values, which can be a starting point for
+     * requesting a next or previous page.
+     */
+    interface Cursor {
+        /**
+         * Returns whether or not the keyset values of this instance
+         * are equal to those of the supplied instance.
+         * Cursor implementation classes must also match to be equal.
+         *
+         * @return true or false.
+         */
+        @Override
+        boolean equals(Object o);
+
+        /**
+         * Returns the keyset value at the specified position.
+         *
+         * @param  index position (0 is first) of the keyset value to obtain.
+         * @return the keyset value at the specified position.
+         * @throws IndexOutOfBoundsException if the index is negative
+         *         or greater than or equal to the {@link #size}.
+         */
+        Object getKeysetElement(int index);
+
+        /**
+         * Returns a hash code based on the keyset values.
+         *
+         * @return a hash code based on the keyset values.
+         */
+        @Override
+        int hashCode();
+
+        /**
+         * Returns the number of values in the keyset.
+         *
+         * @return the number of values in the keyset.
+         */
+        int size();
+
+        /**
+         * String representation of the keyset cursor, including the number of
+         * key values in the cursor but not the values themselves.
+         *
+         * @return String representation of the keyset cursor.
+         */
+        @Override
+        String toString();
+    }
 }
\ No newline at end of file
diff --git a/api/src/main/java/jakarta/data/repository/Pagination.java b/api/src/main/java/jakarta/data/repository/Pagination.java
index 564b6981..f4a5e670 100644
--- a/api/src/main/java/jakarta/data/repository/Pagination.java
+++ b/api/src/main/java/jakarta/data/repository/Pagination.java
@@ -23,49 +23,57 @@ import java.util.Objects;
 import java.util.stream.Collectors;
 import java.util.stream.StreamSupport;
 
-import jakarta.data.repository.KeysetPageable.Cursor;
-
 /**
  * Built-in implementation of Pageable.
  */
-class Pagination implements Pageable {
+final class Pagination implements Pageable {
+
+    private final Cursor cursor;
 
-    final long page;
+    private final Mode mode;
 
-    final int size;
+    private final long page;
 
-    final List<Sort> sorts;
+    private final int size;
 
-    Pagination(long pageNumber, int maxPageSize, List<Sort> sorts) {
+    private final List<Sort> sorts;
+
+    Pagination(long pageNumber, int maxPageSize, List<Sort> sorts, Mode mode, Cursor cursor) {
         if (pageNumber < 1) {
             throw new IllegalArgumentException("pageNumber: " + pageNumber);
         } else if (maxPageSize < 1) {
             throw new IllegalArgumentException("maxPageSize: " + maxPageSize);
         }
 
+        if (mode != Mode.OFFSET && (cursor == null || cursor.size() == 0)) {
+            throw new IllegalArgumentException("No keyset values were provided.");
+        }
+
         this.page = pageNumber;
         this.size = maxPageSize;
         this.sorts = sorts;
+        this.mode = mode;
+        this.cursor = cursor;
     }
 
     @Override
-    public KeysetPageable afterKeyset(Object... keyset) {
-        return new KeysetPagination(page, size, sorts, KeysetPageable.Mode.NEXT, new KeysetPagination.CursorImpl(keyset));
+    public Pageable afterKeyset(Object... keyset) {
+        return new Pagination(page, size, sorts, Mode.CURSOR_NEXT, new KeysetCursor(keyset));
     }
 
     @Override
-    public KeysetPageable beforeKeyset(Object... keyset) {
-        return new KeysetPagination(page, size, sorts, KeysetPageable.Mode.PREVIOUS, new KeysetPagination.CursorImpl(keyset));
+    public Pageable beforeKeyset(Object... keyset) {
+        return new Pagination(page, size, sorts, Mode.CURSOR_PREVIOUS, new KeysetCursor(keyset));
     }
 
     @Override
-    public KeysetPageable afterKeysetCursor(Cursor keysetCursor) {
-        return new KeysetPagination(page, size, sorts, KeysetPageable.Mode.NEXT, keysetCursor);
+    public Pageable afterKeysetCursor(Cursor keysetCursor) {
+        return new Pagination(page, size, sorts, Mode.CURSOR_NEXT, keysetCursor);
     }
 
     @Override
-    public KeysetPageable beforeKeysetCursor(Cursor keysetCursor) {
-        return new KeysetPagination(page, size, sorts, KeysetPageable.Mode.PREVIOUS, keysetCursor);
+    public Pageable beforeKeysetCursor(Cursor keysetCursor) {
+        return new Pagination(page, size, sorts, Mode.CURSOR_PREVIOUS, keysetCursor);
     }
 
     @Override
@@ -77,24 +85,47 @@ class Pagination implements Pageable {
             return false;
         }
         Pagination pageable = (Pagination) o;
-        return size == pageable.size && page == pageable.page && sorts.equals(pageable.sorts);
+        return size == pageable.size
+                && page == pageable.page
+                && mode == pageable.mode
+                && Objects.equals(cursor, pageable.cursor)
+                && sorts.equals(pageable.sorts);
+    }
+
+    @Override
+    public Cursor cursor() {
+        return cursor;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(page, size, sorts);
+        return Objects.hash(size, page, sorts, mode, cursor);
+    }
+
+    @Override
+    public Mode mode() {
+        return mode;
     }
 
     @Override
     public Pageable next() {
-        return new Pagination((page + 1), this.size, this.sorts);
+        if (mode == Mode.OFFSET) {
+            return new Pagination((page + 1), this.size, this.sorts, Mode.OFFSET, null);
+        } else {
+            throw new UnsupportedOperationException("Not supported for keyset pagination. Instead use afterKeyset or afterKeysetCursor " +
+                    "to provide the next keyset values or obtain the nextPageable from a KeysetAwareSlice.");
+        }
     }
 
     @Override
     public String toString() {
-        StringBuilder s = new StringBuilder(100)
+        StringBuilder s = new StringBuilder(mode == Mode.OFFSET ? 100 : 150)
                 .append("Pageable{page=").append(page)
                 .append(", size=").append(size);
+        if (cursor != null) {
+            s.append(", mode=").append(mode)
+            .append(", ").append(cursor.size()).append(" keys");
+        }
         for (Sort sort : sorts) {
             s.append(", ").append(sort.property()).append(sort.isAscending() ? " ASC" : " DESC");
         }
@@ -118,12 +149,12 @@ class Pagination implements Pageable {
 
     @Override
     public Pageable newPage(long pageNumber) {
-        return new Pagination(pageNumber, size, sorts);
+        return new Pagination(pageNumber, size, sorts, mode, cursor);
     }
 
     @Override
     public Pageable newSize(int maxPageSize) {
-        return new Pagination(page, maxPageSize, sorts);
+        return new Pagination(page, maxPageSize, sorts, mode, cursor);
     }
 
     @Override
@@ -131,11 +162,11 @@ class Pagination implements Pageable {
         List<Sort> sortList = sorts == null
                 ? Collections.emptyList()
                 : StreamSupport.stream(sorts.spliterator(), false).collect(Collectors.toUnmodifiableList());
-        return new Pagination(page, size, sortList);
+        return new Pagination(page, size, sortList, mode, cursor);
     }
 
     @Override
     public Pageable sortBy(Sort... sorts) {
-        return new Pagination(page, size, sorts == null ? Collections.emptyList() : List.of(sorts));
+        return new Pagination(page, size, sorts == null ? Collections.emptyList() : List.of(sorts), mode, cursor);
     }
 }
\ No newline at end of file
diff --git a/spec/src/main/asciidoc/repository.asciidoc b/spec/src/main/asciidoc/repository.asciidoc
index 0f77759f..64f89d1c 100644
--- a/spec/src/main/asciidoc/repository.asciidoc
+++ b/spec/src/main/asciidoc/repository.asciidoc
@@ -279,7 +279,7 @@ first20 = products.findByNameLike(name, pageable);
 
 === Keyset Pagination
 
-Keyset pagination aims to reduce missed and duplicate results across pages by querying relative to the observed values of entity properties that constitute the sorting criteria. Keyset pagination can also offer an improvement in performance because it avoids fetching and ordering results from prior pages by causing those results to be non-matching. A Jakarta Data provider appends additional conditions to the query and tracks keyset values automatically when `KeysetAwareSlice` or `KeysetAwarePage` are used as the repository method return type. The application invokes `nextPageable` or `previousPageable` on the keyset aware slice or page to obtain a `KeysetPageable` which keeps track of the keyset values.
+Keyset pagination aims to reduce missed and duplicate results across pages by querying relative to the observed values of entity properties that constitute the sorting criteria. Keyset pagination can also offer an improvement in performance because it avoids fetching and ordering results from prior pages by causing those results to be non-matching. A Jakarta Data provider appends additional conditions to the query and tracks keyset values automatically when `KeysetAwareSlice` or `KeysetAwarePage` are used as the repository method return type. The application invokes `nextPageable` or `previousPageable` on the keyset aware slice or page to obtain a `Pageable` which keeps track of the keyset values.
 
 For example,
 
@@ -308,7 +308,7 @@ Or you can obtain the next (or previous) page relative to a known entity,
 [source,java]
 ----
 Customer c = ...
-KeysetPageable p = Pageable.ofSize(50).afterKeyset(c.lastName, c.firstName, c.id);
+Pageable p = Pageable.ofSize(50).afterKeyset(c.lastName, c.firstName, c.id);
 page = customers.findByZipcodeOrderByLastNameAscFirstNameAscIdAsc(55902, p);
 ----
 
@@ -330,7 +330,7 @@ SELECT o FROM Customer o WHERE (o.zipCode = ?1)
                          ORDER BY o.lastName ASC, o.firstName ASC, o.id ASC
 ----
 
-When keyset pagination is used, the keyset values from the `KeysetPageable` are available as query parameters, allowing the Jakarta Data provider to append additional query conditions. For example,
+When keyset pagination is used, the keyset values from the `Cursor` of the `Pageable` are available as query parameters, allowing the Jakarta Data provider to append additional query conditions. For example,
 
 [source,jpql]
 ----
@@ -351,12 +351,12 @@ Other types of updates to data, however, will cause duplicate or missed results.
 ==== Restrictions on use of Keyset Pagination
 
 * The repository method signature must return `KeysetAwareSlice` or `KeysetAwarePage`.
-* The repository method signature must accept a `Pageable` or `KeysetPageable` parameter.
+* The repository method signature must accept a `Pageable` parameter.
 * Sort criteria must be provided and should be minimal.
 * The combination of provided sort criteria must uniquely identify each entity.
 * Page numbers for keyset pagination are estimated relative to prior page requests or the observed absence of further results and are not accurate. Page numbers must not be relied upon when using keyset pagination.
 * Page totals and result totals are not accurate for keyset pagination and must not be relied upon.
-* A next or previous page can end up being empty. You cannot obtain a next or previous `KeysetPageable` from an empty page because there are no keyset values relative to which to query.
+* A next or previous page can end up being empty. You cannot obtain a next or previous `Pageable` from an empty page because there are no keyset values relative to which to query.
 * A repository method that is annotated with `@Query` and performs keyset pagination must omit the `ORDER BY` clause from the provided query and instead must supply the sort criteria via `@OrderBy` annotations or `Sort` parameters of `Pageable`. The provided query must end with a `WHERE` clause to which additional conditions can be appended by the Jakarta Data provider. The Jakarta Data provider is not expected to parse query text that is provided by the application.
 
 ==== Keyset Pagination Example with Sorts


Test patch:
diff --git a/api/src/test/java/jakarta/data/repository/KeysetPageableTest.java b/api/src/test/java/jakarta/data/repository/KeysetPageableTest.java
index da199498..ad9410df 100644
--- a/api/src/test/java/jakarta/data/repository/KeysetPageableTest.java
+++ b/api/src/test/java/jakarta/data/repository/KeysetPageableTest.java
@@ -31,14 +31,14 @@ import static org.assertj.core.api.SoftAssertions.assertSoftly;
 class KeysetPageableTest {
 
     @Test
-    @DisplayName("Should include keyset values in next KeysetPageable")
-    void shouldCreateKeysetPageableAfterKeyset() {
-        KeysetPageable pageable = Pageable.ofSize(20).afterKeyset("First", 2L, 3);
+    @DisplayName("Should include keyset values in next Pageable")
+    void shouldCreatePageableAfterKeyset() {
+        Pageable pageable = Pageable.ofSize(20).afterKeyset("First", 2L, 3);
 
         assertSoftly(softly -> {
             softly.assertThat(pageable.size()).isEqualTo(20);
             softly.assertThat(pageable.page()).isEqualTo(1L);
-            softly.assertThat(pageable.mode()).isEqualTo(KeysetPageable.Mode.NEXT);
+            softly.assertThat(pageable.mode()).isEqualTo(Pageable.Mode.CURSOR_NEXT);
             softly.assertThat(pageable.cursor().size()).isEqualTo(3);
             softly.assertThat(pageable.cursor().getKeysetElement(0)).isEqualTo("First");
             softly.assertThat(pageable.cursor().getKeysetElement(1)).isEqualTo(2L);
@@ -47,16 +47,16 @@ class KeysetPageableTest {
     }
 
     @Test
-    @DisplayName("Should include keyset values in next KeysetPageable from Cursor")
-    void shouldCreateKeysetPageableAfterKeysetCursor() {
-        KeysetPageable.Cursor cursor = new KeysetPagination.CursorImpl("me", 200);
-        KeysetPageable pageable = Pageable.ofSize(35).sortBy(Sort.asc("name"), Sort.asc("id")).afterKeysetCursor(cursor);
+    @DisplayName("Should include keyset values in next Pageable from Cursor")
+    void shouldCreatePageableAfterKeysetCursor() {
+        Pageable.Cursor cursor = new KeysetCursor("me", 200);
+        Pageable pageable = Pageable.ofSize(35).sortBy(Sort.asc("name"), Sort.asc("id")).afterKeysetCursor(cursor);
 
         assertSoftly(softly -> {
             softly.assertThat(pageable.size()).isEqualTo(35);
             softly.assertThat(pageable.page()).isEqualTo(1L);
             softly.assertThat(pageable.sorts()).isEqualTo(List.of(Sort.asc("name"), Sort.asc("id")));
-            softly.assertThat(pageable.mode()).isEqualTo(KeysetPageable.Mode.NEXT);
+            softly.assertThat(pageable.mode()).isEqualTo(Pageable.Mode.CURSOR_NEXT);
             softly.assertThat(pageable.cursor().size()).isEqualTo(2);
             softly.assertThat(pageable.cursor().getKeysetElement(0)).isEqualTo("me");
             softly.assertThat(pageable.cursor().getKeysetElement(1)).isEqualTo(200);
@@ -64,15 +64,15 @@ class KeysetPageableTest {
     }
 
     @Test
-    @DisplayName("Should include keyset values in previous KeysetPageable")
-    void shouldCreateKeysetPageableBeforeKeyset() {
-        KeysetPageable pageable = Pageable.ofSize(30).sortBy(Sort.desc("yearBorn"), Sort.asc("ssn")).beforeKeyset(1991, "123-45-6789").newPage(10);
+    @DisplayName("Should include keyset values in previous Pageable")
+    void shouldCreatePageableBeforeKeyset() {
+        Pageable pageable = Pageable.ofSize(30).sortBy(Sort.desc("yearBorn"), Sort.asc("ssn")).beforeKeyset(1991, "123-45-6789").newPage(10);
 
         assertSoftly(softly -> {
             softly.assertThat(pageable.size()).isEqualTo(30);
             softly.assertThat(pageable.page()).isEqualTo(10L);
             softly.assertThat(pageable.sorts()).isEqualTo(List.of(Sort.desc("yearBorn"), Sort.asc("ssn")));
-            softly.assertThat(pageable.mode()).isEqualTo(KeysetPageable.Mode.PREVIOUS);
+            softly.assertThat(pageable.mode()).isEqualTo(Pageable.Mode.CURSOR_PREVIOUS);
             softly.assertThat(pageable.cursor().size()).isEqualTo(2);
             softly.assertThat(pageable.cursor().getKeysetElement(0)).isEqualTo(1991);
             softly.assertThat(pageable.cursor().getKeysetElement(1)).isEqualTo("123-45-6789");
@@ -80,16 +80,16 @@ class KeysetPageableTest {
     }
 
     @Test
-    @DisplayName("Should include keyset values in previous KeysetPageable from Cursor")
-    void shouldCreateKeysetPageableBeforeKeysetCursor() {
-        KeysetPageable.Cursor cursor = new KeysetPagination.CursorImpl(900L, 300, "testing", 120, 'T');
-        KeysetPageable pageable = Pageable.ofPage(8).beforeKeysetCursor(cursor);
+    @DisplayName("Should include keyset values in previous Pageable from Cursor")
+    void shouldCreatePageableBeforeKeysetCursor() {
+        Pageable.Cursor cursor = new KeysetCursor(900L, 300, "testing", 120, 'T');
+        Pageable pageable = Pageable.ofPage(8).beforeKeysetCursor(cursor);
 
         assertSoftly(softly -> {
             softly.assertThat(pageable.size()).isEqualTo(10);
             softly.assertThat(pageable.page()).isEqualTo(8L);
             softly.assertThat(pageable.sorts()).isEqualTo(Collections.EMPTY_LIST);
-            softly.assertThat(pageable.mode()).isEqualTo(KeysetPageable.Mode.PREVIOUS);
+            softly.assertThat(pageable.mode()).isEqualTo(Pageable.Mode.CURSOR_PREVIOUS);
             softly.assertThat(pageable.cursor().size()).isEqualTo(5);
             softly.assertThat(pageable.cursor().getKeysetElement(0)).isEqualTo(900L);
             softly.assertThat(pageable.cursor().getKeysetElement(1)).isEqualTo(300);
@@ -102,12 +102,12 @@ class KeysetPageableTest {
     @Test
     @DisplayName("Should be usable in a hashing structure")
     void shouldHash() {
-        KeysetPageable pageable1 = Pageable.ofSize(15).afterKeyset(1, '1', "1")
+        Pageable pageable1 = Pageable.ofSize(15).afterKeyset(1, '1', "1")
                                            .sortBy(Sort.desc("yearHired"), Sort.asc("lastName"), Sort.asc("id"));
-        KeysetPageable pageable2a = Pageable.ofSize(15).afterKeyset(2, '2', "2");
-        KeysetPageable pageable2b = Pageable.ofSize(15).beforeKeyset(2, '2', "2");
-        KeysetPageable pageable2c = Pageable.ofSize(15).beforeKeyset(2, '2', "2");
-        Map<KeysetPageable, Integer> map = new HashMap<>();
+        Pageable pageable2a = Pageable.ofSize(15).afterKeyset(2, '2', "2");
+        Pageable pageable2b = Pageable.ofSize(15).beforeKeyset(2, '2', "2");
+        Pageable pageable2c = Pageable.ofSize(15).beforeKeyset(2, '2', "2");
+        Map<Pageable, Integer> map = new HashMap<>();
 
         assertSoftly(softly -> {
             softly.assertThat(pageable2b.hashCode()).isEqualTo(pageable2c.hashCode());
@@ -125,34 +125,34 @@ class KeysetPageableTest {
 
     @Test
     @DisplayName("Should be displayable as String with toString")
-    void shouldKeysetPageableDisplayAsString() {
-        KeysetPageable pageable = Pageable.ofSize(200).afterKeyset("value1", 1);
+    void shouldPageableDisplayAsString() {
+        Pageable pageable = Pageable.ofSize(200).afterKeyset("value1", 1);
 
         assertSoftly(softly -> softly.assertThat(pageable.toString())
-              .isEqualTo("KeysetPageable{page=1, size=200, mode=NEXT, 2 keys}"));
+              .isEqualTo("Pageable{page=1, size=200, mode=CURSOR_NEXT, 2 keys}"));
 
-        KeysetPageable pageableWithSorts = Pageable.ofSize(100).sortBy(Sort.desc("name"), Sort.asc("id"))
-                                                   .beforeKeyset("Item1", 3456);
+        Pageable pageableWithSorts = Pageable.ofSize(100).sortBy(Sort.desc("name"), Sort.asc("id"))
+                                             .beforeKeyset("Item1", 3456);
 
         assertSoftly(softly -> softly.assertThat(pageableWithSorts.toString())
-              .isEqualTo("KeysetPageable{page=1, size=100, mode=PREVIOUS, 2 keys, name DESC, id ASC}"));
+              .isEqualTo("Pageable{page=1, size=100, mode=CURSOR_PREVIOUS, 2 keys, name DESC, id ASC}"));
     }
 
     @Test
     @DisplayName("Should return true from equals if keyset values and other properties are equal")
     void shouldBeEqualWithSameKeysetValues() {
-        KeysetPageable pageable25p1s0a1 = Pageable.ofSize(25).afterKeyset("keyval1", '2', 3);
-        KeysetPageable pageable25p1s0b1 = Pageable.ofSize(25).beforeKeyset("keyval1", '2', 3);
-        KeysetPageable pageable25p1s0a1match = Pageable.ofSize(25).afterKeysetCursor(new KeysetPagination.CursorImpl("keyval1", '2', 3));
-        KeysetPageable pageable25p2s0a1 = Pageable.ofPage(2).newSize(25).afterKeysetCursor(new KeysetPagination.CursorImpl("keyval1", '2', 3));
-        KeysetPageable pageable25p1s1a1 = Pageable.ofSize(25).sortBy(Sort.desc("d"), Sort.asc("a"), Sort.asc("id")).afterKeyset("keyval1", '2', 3);
-        KeysetPageable pageable25p1s2a1 = Pageable.ofSize(25).sortBy(Sort.desc("d"), Sort.asc("a"), Sort.desc("id")).afterKeyset("keyval1", '2', 3);
-        KeysetPageable pageable25p1s0a2 = Pageable.ofSize(25).afterKeyset("keyval2", '2', 3);
-
-        KeysetPageable.Cursor cursor1 = new KeysetPagination.CursorImpl("keyval1", '2', 3);
-        KeysetPageable.Cursor cursor2 = new KeysetPagination.CursorImpl("keyval2", '2', 3);
-        KeysetPageable.Cursor cursor3 = new KeysetPagination.CursorImpl("keyval1", '2');
-        KeysetPageable.Cursor cursor4 = new KeysetPagination.Cursor() {
+        Pageable pageable25p1s0a1 = Pageable.ofSize(25).afterKeyset("keyval1", '2', 3);
+        Pageable pageable25p1s0b1 = Pageable.ofSize(25).beforeKeyset("keyval1", '2', 3);
+        Pageable pageable25p1s0a1match = Pageable.ofSize(25).afterKeysetCursor(new KeysetCursor("keyval1", '2', 3));
+        Pageable pageable25p2s0a1 = Pageable.ofPage(2).newSize(25).afterKeysetCursor(new KeysetCursor("keyval1", '2', 3));
+        Pageable pageable25p1s1a1 = Pageable.ofSize(25).sortBy(Sort.desc("d"), Sort.asc("a"), Sort.asc("id")).afterKeyset("keyval1", '2', 3);
+        Pageable pageable25p1s2a1 = Pageable.ofSize(25).sortBy(Sort.desc("d"), Sort.asc("a"), Sort.desc("id")).afterKeyset("keyval1", '2', 3);
+        Pageable pageable25p1s0a2 = Pageable.ofSize(25).afterKeyset("keyval2", '2', 3);
+
+        Pageable.Cursor cursor1 = new KeysetCursor("keyval1", '2', 3);
+        Pageable.Cursor cursor2 = new KeysetCursor("keyval2", '2', 3);
+        Pageable.Cursor cursor3 = new KeysetCursor("keyval1", '2');
+        Pageable.Cursor cursor4 = new Pagination.Cursor() {
             private final Object[] keyset = new Object[] { "keyval1", '2', 3 };
 
             @Override
@@ -185,8 +185,8 @@ class KeysetPageableTest {
             softly.assertThat(pageable25p1s0a1.equals(pageable25p1s1a1)).isFalse(); // with vs without sorting
             softly.assertThat(pageable25p1s2a1.equals(pageable25p1s1a1)).isFalse(); // different sorting
             softly.assertThat(pageable25p1s0a1.equals(pageable25p1s0a2)).isFalse(); // different keyset value
-            softly.assertThat(pageable25p1s0a1.equals(Pageable.ofSize(25))).isFalse(); // KeysetPageable vs Pageable
-            softly.assertThat(Pageable.ofSize(25).equals(pageable25p1s0a1)).isFalse(); // Pageable vs KeysetPageable
+            softly.assertThat(pageable25p1s0a1.equals(Pageable.ofSize(25))).isFalse(); // Pageable with keyset vs Pageable
+            softly.assertThat(Pageable.ofSize(25).equals(pageable25p1s0a1)).isFalse(); // Pageable vs Pageable with keyset
         });
     }
 
@@ -200,19 +200,19 @@ class KeysetPageableTest {
     }
 
     @Test
-    @DisplayName("Keyset should be replaced on new instance of KeysetPageable")
+    @DisplayName("Keyset should be replaced on new instance of Pageable")
     public void shouldReplaceKeyset() {
-        KeysetPageable p1 = Pageable.ofSize(30).sortBy(Sort.asc("lastName"), Sort.asc("firstName"), Sort.asc("id"))
-                                    .afterKeyset("last1", "fname1", 100).newPage(12);
-        KeysetPageable p2 = p1.beforeKeyset("lname2", "fname2", 200);
+        Pageable p1 = Pageable.ofSize(30).sortBy(Sort.asc("lastName"), Sort.asc("firstName"), Sort.asc("id"))
+                                         .afterKeyset("last1", "fname1", 100).newPage(12);
+        Pageable p2 = p1.beforeKeyset("lname2", "fname2", 200);
 
         assertSoftly(softly -> {
-            softly.assertThat(p1.mode()).isEqualTo(KeysetPageable.Mode.NEXT);
+            softly.assertThat(p1.mode()).isEqualTo(Pageable.Mode.CURSOR_NEXT);
             softly.assertThat(p1.cursor().getKeysetElement(0)).isEqualTo("last1");
             softly.assertThat(p1.cursor().getKeysetElement(1)).isEqualTo("fname1");
             softly.assertThat(p1.cursor().getKeysetElement(2)).isEqualTo(100);
 
-            softly.assertThat(p2.mode()).isEqualTo(KeysetPageable.Mode.PREVIOUS);
+            softly.assertThat(p2.mode()).isEqualTo(Pageable.Mode.CURSOR_PREVIOUS);
             softly.assertThat(p2.cursor().getKeysetElement(0)).isEqualTo("lname2");
             softly.assertThat(p2.cursor().getKeysetElement(1)).isEqualTo("fname2");
             softly.assertThat(p2.cursor().getKeysetElement(2)).isEqualTo(200);