Repository: jakartaee/data
Pull request #432 (2024-01-30 23:04:54)

Sort with type parameter
To help evaluate the proposal to have Sort with a type parameter #430, I created this pull to demonstrate what that would look like.  As expected, it spills over into Pageable, which is constructed from Sorts, and then to Page and Slice which require a Pageable, although it gets more complicated at this point because Page and Slice might not return the same type (a repository method that is annotated `@Query` and uses query language can return a different type than the entity with the attributes that are the sort criteria), meaning that now Page and Slice methods that return Pageable need a way of returning that Pageable as one for the entity class rather than the result type.

To get rid of warnings and avoid the need to suppress them, we need to eliminate usage of variable arguments (...) in a number of places. I first tried replacing that with `Iterable<Sort<MyEntityClass>>`, but it wasn't a very distinguishable as a special parameter type, so I added a Sorts class for that purpose.  Initially constructing Sort instances with static methods also becomes a bit awkward because the entity class isn't naturally supplied yet, requiring usage like `Sort.<MyEntityClass>asc("myAttributeName")` or it could have been done as`Sort.asc(MyEntityClass.class, "myAttributeName")` (This pull first went with the former, then switched to a different approach). Using the static metamodel avoids that problem.  Preserving the type on Pageable was another problem.  I moved where sorts are supplied to a Pageable to the initial static method, from which the entity type is inferred from the Sort instances, and that helps in some cases, but in other cases there is still a need to clarify the type with `<MyEntityClass>`, which could be annoying to the user.  This was later changed to using a separate `Order<MyEntityClass>` to collect parameterized sorts and created a parameterized page request from that.

otaviojava: Instead of breaking the whole API, I would create a wrapper of this sort.

Also, I would create it as an extension in a vendor first.
njr-11: > Also, I would create it as an extension in a vendor first.
The whole reason I am trying this out is because Gavin pointed out that Hibernate already has the ability to parameterize their equivalent of Sort with the entity type. So I want to explore what it would look like in Jakarta Data.

> Instead of breaking the whole API, I would create a wrapper of this sort.
You make that sound so easy, but in practice I do not see how what you suggest is even possible. What the exploration under this pull has already shown is that Sort is tightly coupled to Pageable, and in turn to Slice, and Page, and so forth.  I think it was important to illustrate that as we evaluate whether or not it's a good idea.
njr-11: > It is not trivial, but we can do it with a wrapper, I would start with:
> 
> ```java
> interface ParametrizedSorted<T> extends Supplier<Sort>{
> }
> ```
> 
> Mainly because we want to use if static model.

That isn't something I can prototype for you because I don't have an understanding of how that would fit into Pageable and the existing API.  If you want to write a pull of your own that illustrates your idea and shows how it fits in, go for it.
KyleAure: I'm not sure the type safety in this case is worth it.
It seems to me that the static metamodel already gives us a pretty strong coupling between entity and attribute, such that, the following code seems natural to write: 

``` java
employees.findByYearHired(2024, Limit.of(5), _Employee.lastName.asc(), _Employee.firstName.asc());
```

Also I didn't even know that this was possible or what the meaning of it is in java:
```java
Sorts.<Employee> of(Sort.asc("id"))
```
It seems unreasonable for us to require users to write code this way, and worse if they aren't using an IDE they might not even see the type safety warning until they compile anyway.

If we do go down this route, however, I think we need a new name for `Sorts.of()`. 
The word `sorts` has an alternative meaning in English that refers to a general collection, and not one that is sorted. 
It would be natural to say "I would like to buy long sorts of pants."

njr-11: Excellent comments Kyle - thanks!
I'm not sure either if the extra type safety is going to be worth the trade-off, but I want to be sure it is something we have at least made a good effort on when considering it, so I have added in all of your feedback.

> ```java
> Sorts.<Employee> of(Sort.asc("id"))
> ```
> 
> It seems unreasonable for us to require users to write code this way, and worse if they aren't using an IDE they might not even see the type safety warning until they compile anyway.

Good point - I had been wondering if that would be too unfamiliar. I switched the approach to follow the style of providing the class instead:

```java
Pageable<Employee> pageRequest = Pageable.of(Employee.class).page(1).size(20).sortBy(
                                   Sort.asc("lastName"),
                                   Sort.asc("firstName"),
                                   Sort.asc("id"));
```

In the above case, `.of(Employee.class)` is the additional code that assigns a type if you prefer to supply string values or constants.

> If we do go down this route, however, I think we need a new name for `Sorts.of()`. The word `sorts` has an alternative meaning in English that refers to a general collection, and not one that is sorted. It would be natural (yet weird) to say "I would like to buy long sorts of pants."

That's another good point: `Sorts.of` could be misread as "types of".  I switched it to `Sorting.by(...)`.

```java
List<Employee> found = findByYearHired(2024, Sorting.by(Sort.asc("lastName"),
                                                        Sort.asc("firstName"),
                                                        Sort.asc("id")));
```

In the above case, `Sorting.by(` is the additional code that enables the type to be inferred from the repository method signature `List<Employee> findByYearHired(int yearHired, Sorting<Employee> sortBy)` if you prefer to supply string values or constants.

Here are some other examples where the static metamodel is used instead:

```java
Pageable<Employee> pageRequest = Sorting.by(_Employee.lastName.asc(),
                                            _Employee.firstName.asc(),
                                            _Employee.id.asc()),
                                        .page(1)
                                        .size(20);

In the above case, this ends up being just as concise as currently, but does involve starting from the `Sorting.by` rather than `Pageable.of...`.

List<Employee> found = findByYearHired(2024, Sorting.by(_Employee.lastName.asc(),
                                                        _Employee.firstName.asc(),
                                                        _Employee.id.asc()));
```

The above very closely matches the corresponding scenario with string values/constants.  `Sorting.by(` is still needed to supply multiple `Sort` instances because the variable args `Sort...` approach isn't possible anymore.
gavinking: Sorry for not responding sooner. So here's what the API looks like in Hibernate. First, the usage:

```java
// query method returning an entity type
repo.findBooksByTitle("%Hibernate%", first(10), asc(Book_.isbn))
repo.findBooksByTitle("%Hibernate%", first(10), asc(Book_.isbn), desc(Book_.publicationDate))

// query method returning a projection
repo.findBookAttributesByTitle("%Hibernate%", first(10), asc(1), asc(2))
```

where the query methods are declared as follows:

```java
@HQL("where title like :title")
List<Book> findBooksByTitle(String title, Page page, Order<? super Book>... ordering);

@HQL("select title, isbn from Book where title like :title")
List<Object[]> findBookAttributesByTitle(String title, Page page, Order<Object[]>... ordering);
```

A couple of observations:

1. My `Page` class is not generic, but OTOH it doesn't do everything that Jakarta Data's `Pageable` stuff does.
2. My `Order` is the equivalent of your `Sort`.
gavinking: [Of course, you're also allowed to write `asc(Book.class, "isbn")`, but that's sort of "clearly worse".]
gavinking: Oh and if you're concerned about the stupid and useless "heap pollution" warning, this is also allowed:


```java
@HQL("where title like ?1")
repo.findBooksByTitle("%Hibernate%", first(10), asList(asc(Book_.isbn), desc(Book_.title)))

List<Book> findBooksByTitle(String title, Page page, List<Order<? super Book>> ordering);
```
njr-11: > Oh and if you're concerned about the stupid and useless "heap pollution" warning, this is also allowed:

Thanks for responding. Yes, most of the experimentation I've been doing under this pull is aimed at trying to figure out how to spare customers from that annoying warning and the other warning you get when trying to use the repository method.  Your strategy of using `Arrays.asList` to avoid the warning is very similar to the `Sorting.by` approach that I tried - they basically do the same thing in allowing multiple to be specified without the warning.  Maybe `Order.by` would be an even better name.

@otaviojava @gavinking @KyleAure will it be possible for all of you to make it to the [February 7th Jakarta Data meeting](https://calendar.google.com/calendar/u/0/embed?src=eclipse-foundation.org_e9ki8t2gc75sh07qdh95c8ofvc@group.calendar.google.com&ctz=Europe/Lisbon) so that we can all discuss/decide whether or not to add the type parameter?

gavinking: > Your strategy of using `Arrays.asList` to avoid the warning is very similar to the `Sorting.by` approach that I tried - they basically do the same thing in allowing multiple to be specified without the warning. Maybe `Order.by` would be an even better name.

Yep, I agree, they're the same.

Another possibility would be to always declare the methods as:

```java
@Find
List<Book> getBooksByTitle(String title, Sort<Book> ordering);
```

But let you call it as:

```java
getBooksByTitle("Jakarta Data", _Book.isbn.asc())
```


or:

```java
getBooksByTitle("Jakarta Data", order(_Book.title.asc(), _Book.isbn.asc()))
```

Where the static method `order()` is defined as follows:

```java
	@SafeVarargs
	public static <T> Sort<T> order(Sort<T>... orders) {
		return .... ;
	}
```

That is, it aggregates multiple `Sort`s into a single one.

This is quite clean, it seems to me.

Patch:
diff --git a/api/src/main/java/jakarta/data/Limit.java b/api/src/main/java/jakarta/data/Limit.java
index f142df5a..ed6c5ec2 100644
--- a/api/src/main/java/jakarta/data/Limit.java
+++ b/api/src/main/java/jakarta/data/Limit.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022,2023 Contributors to the Eclipse Foundation
+ * Copyright (c) 2022,2024 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.
@@ -30,7 +30,7 @@ import jakarta.data.page.Pageable;
  * query parameters. For example,</p>
  *
  * <pre>
- * Product[] findByNameLike(String namePattern, Limit limit, Sort... sorts);
+ * Product[] findByNameLike(String namePattern, Limit limit, {@code Sort<?>...} sorts);
  * 
  * ...
  * mostExpensive50 = products.findByNameLike(pattern, Limit.of(50), Sort.desc("price"));
diff --git a/api/src/main/java/jakarta/data/Order.java b/api/src/main/java/jakarta/data/Order.java
new file mode 100644
index 00000000..390a813f
--- /dev/null
+++ b/api/src/main/java/jakarta/data/Order.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2024 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;
+
+import java.util.Iterator;
+import java.util.List;
+
+import jakarta.data.metamodel.StaticMetamodel;
+import jakarta.data.page.Pageable;
+import jakarta.data.repository.OrderBy;
+import jakarta.data.repository.Query;
+
+/**
+ * <p>Requests sorting on various entity attributes.</p>
+ *
+ * <p><code>Order</code> can be optionally specified as a
+ * parameter to a repository find method in any of the positions
+ * that are after the query parameters, or it can be used
+ * to obtain a {@link Pageable page request} that is similarly
+ * specified as a parameter to a repository find method.</p>
+ *
+ * <p>The {@code Order} class is useful in combination with the
+ * {@link StaticMetamodel} for helping to enforce type safety of
+ * sort criteria during development. For example,</p>
+ *
+ * <pre>
+ * {@code Page<Employee>} findByYearHired(int year, {@code Pageable<Employee>} pageRequest);
+ * ...
+ * page1 = employees.findByYearHired(Year.now(),
+ *                                   Order.by(_Employee.salary.desc(),
+ *                                            _Employee.lastName.asc(),
+ *                                            _Employee.firstName.asc())
+ *                                        .page(1)
+ *                                        .size(10));
+ * </pre>
+ *
+ * <p>When combined on a method with static sort criteria
+ * (<code>OrderBy</code> keyword or {@link OrderBy} annotation or
+ * {@link Query} with an <code>ORDER BY</code> clause), the static
+ * sort criteria is applied first, followed by the dynamic sort criteria
+ * that is defined by {@link Sort} instances in the order listed.</p>
+ *
+ * <p>In the example above, the matching employees are sorted first by salary
+ * from highest to lowest. Employees with the same salary are then sorted
+ * alphabetically by last name. Employees with the same salary and last name
+ * are then sorted alphabetically by first name.</p>
+ *
+ * <p>A repository method will fail with a
+ * {@link jakarta.data.exceptions.DataException DataException}
+ * or a more specific subclass if</p>
+ * <ul>
+ * <li>an <code>Order</code> parameter is
+ *     specified in combination with a {@link Pageable} parameter with
+ *     {@link Pageable#sorts()}.</li>
+ * <li>the database is incapable of ordering with the requested
+ *     sort criteria.</li>
+ * </ul>
+ *
+ * @param <T> entity class of the attributes that are used as sort criteria.
+ */
+public class Order<T> implements Iterable<Sort<T>> {
+
+    private final List<Sort<T>> sorts;
+
+    private Order(List<Sort<T>> sorts) {
+        this.sorts = sorts;
+    }
+
+    /**
+     * <p>Defines a list of {@link Sort} criteria, ordered from highest precedence
+     * to lowest precedence.</p>
+     *
+     * @param <T>   entity class of the attributes that are used as sort criteria.
+     * @param sorts sort criteria to use, ordered from highest precedence to lowest precedence.
+     * @return a new instance indicating the order of precedence for sort criteria.
+     *         This method never returns <code>null</code>.
+     */
+    @SafeVarargs
+    public static final <T> Order<T> by(Sort<T>... sorts) {
+        return new Order<T>(List.of(sorts));
+    }
+
+    /**
+     * Determines whether this instance specifies matching {@link Sort} criteria
+     * in the same order of precedence as another instance.
+     *
+     * @return true if the other instance is an {@code Order} that specifies
+     *         the same ordering of sort criteria as this instance.
+     */
+    @Override
+    public boolean equals(Object other) {
+        return this == other
+            || other instanceof Order s && sorts.equals(s.sorts);
+    }
+
+    /**
+     * Computes a hash code for this instance.
+     *
+     * @return hash code.
+     */
+    @Override
+    public int hashCode() {
+        return sorts.hashCode();
+    }
+
+    /**
+     * Returns an iterator that follows the order of precedence for the
+     * {@link Sort} criteria, from highest precedence to lowest.
+     *
+     * @return iterator over the sort criteria.
+     */
+    @Override
+    public Iterator<Sort<T>> iterator() {
+        return sorts.iterator();
+    }
+
+    /**
+     * Create a {@link Pageable page request} for the specified page number
+     * of page size 10 (the default for {@code Pageable})
+     * of the query results sorted according to any static sort criteria that
+     * is specified and the ordered list of {@link Sort} criteria
+     * that is represented by this instance.
+     *
+     * @param pageNumber requested page number.
+     * @return a request for a page of results that are sorted based on the sort criteria represented by this instance
+     *         and with the specified page number. This method never returns <code>null</code>.
+     */
+    public Pageable<T> page(long pageNumber) {
+        return Pageable.<T>ofPage(pageNumber).sortBy(sorts);
+    }
+
+    /**
+     * Create a {@link Pageable page request} for the first page of the specified page size
+     * of the query results sorted according to any static sort criteria that
+     * is specified and the ordered list of {@link Sort} criteria
+     * that is represented by this instance.
+     *
+     * @param size requested size of pages.
+     * @return a request for a page of results that are sorted based on the sort criteria represented by this instance
+     *         and with the specified page size. This method never returns <code>null</code>.
+     */
+    public Pageable<T> pageSize(int size) {
+        return Pageable.<T>ofSize(size).sortBy(sorts);
+    }
+
+    /**
+     * Textual representation of this instance, including the result of invoking
+     * {@link Sort#toString()} on each member of the sort criteria, in order of
+     * precedence from highest to lowest.
+     *
+     * @return textual representation of this instance.
+     */
+    @Override
+    public String toString() {
+        return sorts.toString();
+    }
+}
\ No newline at end of file
diff --git a/api/src/main/java/jakarta/data/Sort.java b/api/src/main/java/jakarta/data/Sort.java
index db706c00..2be860f9 100644
--- a/api/src/main/java/jakarta/data/Sort.java
+++ b/api/src/main/java/jakarta/data/Sort.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022,2023 Contributors to the Eclipse Foundation
+ * Copyright (c) 2022,2024 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.
@@ -17,9 +17,11 @@
  */
 package jakarta.data;
 
+import jakarta.data.metamodel.StaticMetamodel;
 import jakarta.data.page.Pageable;
 import jakarta.data.repository.OrderBy;
 import jakarta.data.repository.Query;
+
 import java.util.Objects;
 
 /**
@@ -30,14 +32,16 @@ import java.util.Objects;
  * a {@link Direction} and a property.</p>
  *
  * <p>Dynamic <code>Sort</code> criteria can be specified when
- * {@link Pageable#sortBy(Sort[]) requesting a page of results}
+ * requesting a {@link Pageable#sortBy(Sort) page} of results,
  * or can be optionally specified as
- * parameters to a repository method in any of the positions that are after
- * the query parameters. You can use <code>Sort...</code> to allow a variable
- * number of <code>Sort</code> criteria. For example,</p>
+ * parameters to a repository find method in any of the positions that are after
+ * the query parameters.</p>
+ *
+ * <p>You can use {@code Sort<?>...} to allow a variable
+ * number of generic <code>Sort</code> criteria. For example,</p>
  *
  * <pre>
- * Employee[] findByYearHired(int yearYired, Limit maxResults, Sort... sortBy);
+ * Employee[] findByYearHired(int yearYired, Limit maxResults, {@code Sort<?>...} sortBy);
  * ...
  * highestPaidNewHires = employees.findByYearHired(Year.now(),
  *                                                 Limit.of(10),
@@ -46,6 +50,20 @@ import java.util.Objects;
  *                                                 Sort.asc("firstName"));
  * </pre>
  *
+ * <p>You can use {@link Order} in combination with the
+ * {@link StaticMetamodel} to allow a variable number of
+ * typed <code>Sort</code> criteria. For example,</p>
+ *
+ * <pre>
+ * Employee[] findByYearHired(int yearYired, Limit maxResults, {@code Order<Employee>} sortBy);
+ * ...
+ * highestPaidNewHires = employees.findByYearHired(Year.now(),
+ *                                                 Limit.of(10),
+ *                                                 Order.by(_Employee.salary.desc(),
+ *                                                          _Employee.lastName.asc(),
+ *                                                          _Employee.firstName.asc()));
+ * </pre>
+ *
  * <p>When combined on a method with static sort criteria
  * (<code>OrderBy</code> keyword or {@link OrderBy} annotation or
  * {@link Query} with an <code>ORDER BY</code> clause), the static
@@ -68,11 +86,12 @@ import java.util.Objects;
  *     sort criteria.</li>
  * </ul>
  *
+ * @param <T>         entity class of the property upon which to sort.
  * @param property    name of the property to order by.
  * @param isAscending whether ordering for this property is ascending (true) or descending (false).
  * @param ignoreCase  whether or not to request case insensitive ordering from a database with case sensitive collation.
  */
-public record Sort(String property, boolean isAscending, boolean ignoreCase) {
+public record Sort<T>(String property, boolean isAscending, boolean ignoreCase) {
 
     /**
      * <p>Defines sort criteria for an entity property. For more descriptive code, use:</p>
@@ -136,62 +155,67 @@ public record Sort(String property, boolean isAscending, boolean ignoreCase) {
     /**
      * Create a {@link Sort} instance
      *
+     * @param <T>        entity class of the sortable property.
      * @param property  the property name to order by
      * @param direction the direction in which to order.
      * @param ignoreCase whether to request a case insensitive ordering.
      * @return a {@link Sort} instance. Never {@code null}.
      * @throws NullPointerException when there is a null parameter
      */
-    public static Sort of(String property, Direction direction, boolean ignoreCase) {
+    public static <T> Sort<T> of(String property, Direction direction, boolean ignoreCase) {
         Objects.requireNonNull(direction, "direction is required");
-        return new Sort(property, Direction.ASC.equals(direction), ignoreCase);
+        return new Sort<>(property, Direction.ASC.equals(direction), ignoreCase);
     }
 
     /**
      * Create a {@link Sort} instance with {@link Direction#ASC ascending direction}
      * that does not request case insensitive ordering.
      *
+     * @param <T>      entity class of the sortable property.
      * @param property the property name to order by
      * @return a {@link Sort} instance. Never {@code null}.
      * @throws NullPointerException when the property is null
      */
-    public static Sort asc(String property) {
-        return new Sort(property, true, false);
+    public static <T> Sort<T> asc(String property) {
+        return new Sort<>(property, true, false);
     }
 
     /**
      * Create a {@link Sort} instance with {@link Direction#ASC ascending direction}
      * and case insensitive ordering.
      *
+     * @param <T>      entity class of the sortable property.
      * @param property the property name to order by.
      * @return a {@link Sort} instance. Never {@code null}.
      * @throws NullPointerException when the property is null.
      */
-    public static Sort ascIgnoreCase(String property) {
-        return new Sort(property, true, true);
+    public static <T> Sort<T> ascIgnoreCase(String property) {
+        return new Sort<>(property, true, true);
     }
 
     /**
      * Create a {@link Sort} instance with {@link Direction#DESC descending direction}
      * that does not request case insensitive ordering.
      *
+     * @param <T>      entity class of the sortable property.
      * @param property the property name to order by
      * @return a {@link Sort} instance. Never {@code null}.
      * @throws NullPointerException when the property is null
      */
-    public static Sort desc(String property) {
-        return new Sort(property, false, false);
+    public static <T> Sort<T> desc(String property) {
+        return new Sort<>(property, false, false);
     }
 
     /**
      * Create a {@link Sort} instance with {@link Direction#DESC descending direction}
      * and case insensitive ordering.
      *
+     * @param <T>      entity class of the sortable property.
      * @param property the property name to order by.
      * @return a {@link Sort} instance. Never {@code null}.
      * @throws NullPointerException when the property is null.
      */
-    public static Sort descIgnoreCase(String property) {
-        return new Sort(property, false, true);
+    public static <T> Sort<T> descIgnoreCase(String property) {
+        return new Sort<>(property, false, true);
     }
 }
\ No newline at end of file
diff --git a/api/src/main/java/jakarta/data/metamodel/SortableAttribute.java b/api/src/main/java/jakarta/data/metamodel/SortableAttribute.java
index 8e5faa45..1799ea10 100644
--- a/api/src/main/java/jakarta/data/metamodel/SortableAttribute.java
+++ b/api/src/main/java/jakarta/data/metamodel/SortableAttribute.java
@@ -30,21 +30,23 @@ import jakarta.data.Sort;
  * <li>boolean attributes</li>
  * <li>{@link TextAttribute textual attributes}</li>
  * </ul>
+ *
+ * @param <T> entity class of the static metamodel.
  */
-public interface SortableAttribute extends Attribute {
+public interface SortableAttribute<T> extends Attribute {
 
     /**
      * Obtain a request for an ascending {@link Sort} based on the entity attribute.
      *
      * @return a request for an ascending sort on the entity attribute.
      */
-    Sort asc();
+    Sort<T> asc();
 
     /**
      * Obtain a request for a descending {@link Sort} based on the entity attribute.
      *
      * @return a request for a descending sort on the entity attribute.
      */
-    Sort desc();
+    Sort<T> desc();
 
 }
diff --git a/api/src/main/java/jakarta/data/metamodel/StaticMetamodel.java b/api/src/main/java/jakarta/data/metamodel/StaticMetamodel.java
index 2c0731e9..3a615541 100644
--- a/api/src/main/java/jakarta/data/metamodel/StaticMetamodel.java
+++ b/api/src/main/java/jakarta/data/metamodel/StaticMetamodel.java
@@ -63,21 +63,23 @@ import jakarta.data.Sort;
  *     public static final String NAME_LAST = "name.last";
  *     public static final String YEAROFBIRTH = "yearOfBirth";
  *
- *     public static volatile SortableAttribute ssn; // ssn or id
+ *     public static volatile {@code SortableAttribute<Person>} ssn; // ssn or id
  *     public static volatile Attribute name;
- *     public static volatile TextAttribute name_first;
- *     public static volatile TextAttribute name_last;
- *     public static volatile SortableAttribute yearOfBirth;
+ *     public static volatile {@code TextAttribute<Person>} name_first;
+ *     public static volatile {@code TextAttribute<Person>} name_last;
+ *     public static volatile {@code SortableAttribute<Person>} yearOfBirth;
  * }
  * </pre>
  *
  * <p>And use it to refer to entity attributes in a type-safe manner,</p>
  *
  * <pre>
- * pageRequest = Pageable.ofSize(20).sortBy(_Person.yearOfBirth.desc(),
+ * {@code Pageable<Product>} pageRequest = Order.by(_Person.yearOfBirth.desc(),
  *                                          _Person.name_last.asc(),
  *                                          _Person.name_first.asc(),
- *                                          _Person.ssn.asc());
+ *                                          _Person.ssn.asc())
+ *                                      .page(1)
+ *                                      .size(20);
  * </pre>
  *
  * <p>When a class is annotated with {@code StaticMetamodel} and the
diff --git a/api/src/main/java/jakarta/data/metamodel/TextAttribute.java b/api/src/main/java/jakarta/data/metamodel/TextAttribute.java
index cc653d9c..c00390f0 100644
--- a/api/src/main/java/jakarta/data/metamodel/TextAttribute.java
+++ b/api/src/main/java/jakarta/data/metamodel/TextAttribute.java
@@ -21,21 +21,23 @@ import jakarta.data.Sort;
 
 /**
  * Represents an textual entity attribute in the {@link StaticMetamodel}.
+ *
+ * @param <T> entity class of the static metamodel.
  */
-public interface TextAttribute extends SortableAttribute {
+public interface TextAttribute<T> extends SortableAttribute<T> {
 
     /**
      * Obtain a request for an ascending, case insensitive {@link Sort} based on the entity attribute.
      *
      * @return a request for an ascending, case insensitive sort on the entity attribute.
      */
-    Sort ascIgnoreCase();
+    Sort<T> ascIgnoreCase();
 
     /**
      * Obtain a request for a descending, case insensitive {@link Sort} based on the entity attribute.
      *
      * @return a request for a descending, case insensitive sort on the entity attribute.
      */
-    Sort descIgnoreCase();
+    Sort<T> descIgnoreCase();
 
 }
diff --git a/api/src/main/java/jakarta/data/metamodel/package-info.java b/api/src/main/java/jakarta/data/metamodel/package-info.java
index 3f82ebc3..abca278b 100644
--- a/api/src/main/java/jakarta/data/metamodel/package-info.java
+++ b/api/src/main/java/jakarta/data/metamodel/package-info.java
@@ -35,9 +35,9 @@
  *
  * &#64;StaticMetamodel(Product.class)
  * public class _Product {
- *     public static volatile SortableAttribute id;
- *     public static volatile TextAttribute name;
- *     public static volatile SortableAttribute price);
+ *     public static volatile {@code SortableAttribute<Product>} id;
+ *     public static volatile {@code TextAttribute<Product>} name;
+ *     public static volatile {@code SortableAttribute<Product>} price);
  * }
  *
  * ...
@@ -47,10 +47,11 @@
  *
  * ...
  *
- * Pageable pageRequest = Pageable.ofSize(20)
- *                                .sortBy(_Product.price.desc(),
- *                                        _Product.name.asc(),
- *                                        _Product.id.asc());
+ * {@code Pageable<Product>} pageRequest = Order.by(_Product.price.desc(),
+ *                                          _Product.name.asc(),
+ *                                          _Product.id.asc())
+ *                                      .page(1)
+ *                                      .size(20);
  *
  * page1 = products.findByNameLike(namePattern, pageRequest);
  * </pre>
diff --git a/api/src/main/java/jakarta/data/page/KeysetAwareSlice.java b/api/src/main/java/jakarta/data/page/KeysetAwareSlice.java
index 88e39141..a1f36344 100644
--- a/api/src/main/java/jakarta/data/page/KeysetAwareSlice.java
+++ b/api/src/main/java/jakarta/data/page/KeysetAwareSlice.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022,2023 Contributors to the Eclipse Foundation
+ * Copyright (c) 2022,2024 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.
@@ -18,6 +18,7 @@
 package jakarta.data.page;
 
 import jakarta.data.repository.OrderBy;
+import jakarta.data.Sort;
 
 /**
  * <p>A slice of data with the ability to create a cursor from the
@@ -46,13 +47,13 @@ import jakarta.data.repository.OrderBy;
  * &#64;OrderBy("lastName")
  * &#64;OrderBy("firstName")
  * &#64;OrderBy("id")
- * KeysetAwareSlice&lt;Employee&gt; findByHoursWorkedGreaterThan(int hours, Pageable pagination);
+ * KeysetAwareSlice&lt;Employee&gt; findByHoursWorkedGreaterThan(int hours, {@code Pageable<Employee>} pageRequest);
  * </pre>
  *
  * <p>You can use an offset-based {@link Pageable} to request an initial page,</p>
  *
  * <pre>
- * page = employees.findByHoursWorkedGreaterThan(1500, Pageable.ofSize(50));
+ * page = employees.findByHoursWorkedGreaterThan(1500, Pageable.of(Employee.class).size(50));
  * </pre>
  *
  * <p>For subsequent pages, you can request pagination relative to the
@@ -70,14 +71,16 @@ import jakarta.data.repository.OrderBy;
  * <p>You can also construct a {@link Pageable page request} 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(jakarta.data.Sort...)} or {@link Pageable#sortBy(Iterable)} parameters,
+ * {@link Sort} parameters of the page request,
  * 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);
+ * {@code Pageable<Employee>} pageRequest = Pageable.of(Employee.class)
+ *                                          .size(50)
+ *                                          .afterKeyset(emp.lastName, emp.firstName, emp.id);
+ * page = employees.findByHoursWorkedGreaterThan(1500, pageRequest);
  * </pre>
  *
  * <p>By making the query for the next page relative to observed values,
@@ -101,7 +104,7 @@ import jakarta.data.repository.OrderBy;
  * in parenthesis.
  * Sort criteria must be specified independently from the user-provided query,
  * either with the {@link OrderBy} annotation or
- * {@link Pageable#sortBy(jakarta.data.Sort...)} or {@link Pageable#sortBy(Iterable)} parameters.
+ * {@link Sort} parameters to {@link Pageable}.
  * For example,</p>
  *
  * <pre>
@@ -109,7 +112,8 @@ import jakarta.data.repository.OrderBy;
  * &#64;OrderBy("zipcode")
  * &#64;OrderBy("birthYear")
  * &#64;OrderBy("id")
- * KeysetAwareSlice&lt;Customer&gt; getTopBuyers(int minOrders, float minSpent, Pageable pagination);
+ * KeysetAwareSlice&lt;Customer&gt; getTopBuyers(int minOrders, float minSpent,
+ *                                         {@code Pageable<Customer>} pageRequest);
  * </pre>
  *
  * <p>Queries that are used with keyset pagination must return entities
@@ -154,7 +158,7 @@ public interface KeysetAwareSlice<T> extends Slice<T> {
      *         or if it is known that there is not a next page.
      */
     @Override
-    Pageable nextPageable();
+    Pageable<T> nextPageable();
 
     /**
      * <p>Creates a request for the previous page
@@ -181,5 +185,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.
      */
-    Pageable previousPageable();
+    Pageable<T> previousPageable();
 }
\ No newline at end of file
diff --git a/api/src/main/java/jakarta/data/page/Page.java b/api/src/main/java/jakarta/data/page/Page.java
index 14247dcd..933c4cad 100644
--- a/api/src/main/java/jakarta/data/page/Page.java
+++ b/api/src/main/java/jakarta/data/page/Page.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023 Contributors to the Eclipse Foundation
+ * Copyright (c) 2023,2024 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.
@@ -25,7 +25,7 @@ package jakarta.data.page;
  * A page is obtained by supplying a {@link Pageable} parameter to a repository method. For example,</p>
  *
  * <pre>
- * {@code Page<Employee>} findByYearHired(int year, Pageable pageRequest);
+ * {@code Page<Employee>} findByYearHired(int year, {@code Pageable<?>} pageRequest);
  * </pre>
  *
  * <p>Repository methods that are declared to return <code>Page</code> or
@@ -37,7 +37,7 @@ package jakarta.data.page;
  * <p>For a lighter weight subset of query results that does not have awareness of the
  * total number of pages, {@link Slice} can be used instead of page.</p>
  *
- * @param <T> the type of elements in this page 
+ * @param <T> the type of elements in this page
  */
 public interface Page<T> extends Slice<T> {
 
diff --git a/api/src/main/java/jakarta/data/page/Pageable.java b/api/src/main/java/jakarta/data/page/Pageable.java
index a69ae8cd..e68544c4 100644
--- a/api/src/main/java/jakarta/data/page/Pageable.java
+++ b/api/src/main/java/jakarta/data/page/Pageable.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022,2023 Contributors to the Eclipse Foundation
+ * Copyright (c) 2022,2024 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.
@@ -18,7 +18,9 @@
 package jakarta.data.page;
 
 import jakarta.data.Limit;
+import jakarta.data.Order;
 import jakarta.data.Sort;
+import jakarta.data.metamodel.StaticMetamodel;
 import jakarta.data.repository.OrderBy;
 import jakarta.data.repository.Query;
 
@@ -36,10 +38,11 @@ import java.util.Optional;
  * <pre>
  * &#64;OrderBy("age")
  * &#64;OrderBy("ssn")
- * Person[] findByAgeBetween(int minAge, int maxAge, Pageable pagination);
+ * Person[] findByAgeBetween(int minAge, int maxAge, {@code Pageable<Person>} pageRequest);
  *
  * ...
- * for (Pageable p = Pageable.ofSize(100); p != null; p = page.length == 0 ? null : p.next()) {
+ * for ({@code Pageable<Person>} p = Pageable.of(Person.class).size(100);
+ *      p != null; p = page.length == 0 ? null : p.next()) {
  *   page = people.findByAgeBetween(35, 59, p);
  *   ...
  * }
@@ -55,35 +58,74 @@ import java.util.Optional;
  *     same method.</li>
  * <li>a <code>Pageable</code> parameter is supplied in combination
  *     with the <code>First</code> keyword.</li>
- * <li>a <code>Pageable</code> parameter is supplied and separate
+ * <li>a <code>Pageable</code> parameter with sort criteria is supplied and separate
  *     {@link Sort} parameters are also supplied to the same method.</li>
+ * <li>a <code>Pageable</code> parameter with sort criteria is supplied and an
+ *     {@link Order} parameter is also supplied to the same method.</li>
  * <li>the database is incapable of ordering with the requested
  *     sort criteria.</li>
  * </ul>
+ *
+ * @param <T> entity class of the attributes that are used as sort criteria.
  */
-public interface Pageable {
+public interface Pageable<T> {
+
+    /**
+     * <p>Creates a page request to use when querying on entities of the specified entity class.</p>
+     *
+     * <p>This method is useful for supplying the entity type when you do not have
+     * typed {@link Sort} instances. For example,</p>
+     *
+     * <pre>
+     * {@code Pageable<Car>} page1Request = Pageable.of(Car.class).page(1).size(25).sortBy(
+     *                                          Sort.desc("price"),
+     *                                          Sort.asc("mileage"),
+     *                                          Sort.asc("vin"));
+     * </pre>
+     *
+     * <p>If using typed {@link Sort} instances from the {@link StaticMetamodel},
+     * a more concise way to create page requests is to start with the {@link Order} class,
+     * as follows:</p>
+     *
+     * <pre>
+     * {@code Pageable<Car>} page1Request = Order.by(_Car.price.desc(),
+     *                                       _Car.mileage.asc(),
+     *                                       _Car.vin.asc())
+     *                                   .page(1)
+     *                                   .size(25);
+     * </pre>
+     *
+     * @param <T>         entity class of attributes that can be used as sort criteria.
+     * @param entityClass entity class of attributes that can be used as sort criteria.
+     * @return a new instance of <code>Pageable</code>. This method never returns <code>null</code>.
+     */
+    static <T> Pageable<T> of(Class<T> entityClass) {
+        return new Pagination<T>(1, 10, Collections.emptyList(), Mode.OFFSET, null);
+    }
 
     /**
      * Creates a new page request with the given page number and with a default size of 10.
      *
+     * @param <T>        entity class of the attributes that are used as sort criteria.
      * @param pageNumber The page number.
      * @return a new instance of <code>Pageable</code>. This method never returns <code>null</code>.
      * @throws IllegalArgumentException when the page number is negative or zero.
      */
-    static Pageable ofPage(long pageNumber) {
-        return new Pagination(pageNumber, 10, Collections.emptyList(), Mode.OFFSET, null);
+    static <T> Pageable<T> ofPage(long pageNumber) {
+        return new Pagination<T>(pageNumber, 10, Collections.emptyList(), Mode.OFFSET, null);
     }
 
     /**
      * Creates a new page request for requesting pages of the
      * specified size, starting with the first page number, which is 1.
      *
+     * @param <T>         entity class of the attributes that are used as sort criteria.
      * @param maxPageSize The number of query results in a full page.
      * @return a new instance of <code>Pageable</code>. This method never returns <code>null</code>.
      * @throws IllegalArgumentException when maximum page size is negative or zero.
      */
-    static Pageable ofSize(int maxPageSize) {
-        return new Pagination(1, maxPageSize, Collections.emptyList(), Mode.OFFSET, null);
+    static <T> Pageable<T> ofSize(int maxPageSize) {
+        return new Pagination<T>(1, maxPageSize, Collections.emptyList(), Mode.OFFSET, null);
     }
 
     /**
@@ -98,7 +140,7 @@ public interface Pageable {
      *         This method never returns <code>null</code>.
      * @throws IllegalArgumentException if no keyset values are provided.
      */
-    Pageable afterKeyset(Object... keyset);
+    Pageable<T> afterKeyset(Object... keyset);
 
     /**
      * <p>Requests {@link KeysetAwareSlice keyset pagination} in the reverse direction,
@@ -112,7 +154,7 @@ public interface Pageable {
      *         This method never returns <code>null</code>.
      * @throws IllegalArgumentException if no keyset values are provided.
      */
-    Pageable beforeKeyset(Object... keyset);
+    Pageable<T> beforeKeyset(Object... keyset);
 
     /**
      * <p>Requests {@link KeysetAwareSlice keyset pagination} in the forward direction,
@@ -126,7 +168,7 @@ public interface Pageable {
      *         This method never returns <code>null</code>.
      * @throws IllegalArgumentException if no keyset values are provided.
      */
-    Pageable afterKeysetCursor(Cursor keysetCursor);
+    Pageable<T> afterKeysetCursor(Cursor keysetCursor);
 
     /**
      * <p>Requests {@link KeysetAwareSlice keyset pagination} in the reverse direction,
@@ -140,7 +182,61 @@ public interface Pageable {
      *         This method never returns <code>null</code>.
      * @throws IllegalArgumentException if no keyset values are provided.
      */
-    Pageable beforeKeysetCursor(Cursor keysetCursor);
+    Pageable<T> beforeKeysetCursor(Cursor keysetCursor);
+
+    /**
+     * <p>Creates a new page request with the same pagination information,
+     * appending the specified {@link Sort#asc(String) ascending sort}
+     * with lower priority than all other sort criteria (if any) that have already
+     * been specified.</p>
+     *
+     * @param property name of the entity attribute upon which to sort.
+     * @return a new instance of <code>Pageable</code> with the ascending sort
+     *         as its lowest priority sort criteria.
+     * @throws NullPointerException when the property is null
+     */
+    Pageable<T> asc(String property);
+
+    /**
+     * <p>Creates a new page request with the same pagination information,
+     * appending the specified {@link Sort#ascIgnoreCase(String) case-insensitive ascending sort}
+     * with lower priority than all other sort criteria (if any) that have already
+     * been specified. The case-insensitive sort means that the respective value
+     * in the database is compared independent of case.</p>
+     *
+     * @param property name of the entity attribute upon which to sort.
+     * @return a new instance of <code>Pageable</code> with the case-insensitive ascending sort
+     *         as its lowest priority sort criteria.
+     * @throws NullPointerException when the property is null
+     */
+    Pageable<T> ascIgnoreCase(String property);
+
+    /**
+     * <p>Creates a new page request with the same pagination information,
+     * appending the specified {@link Sort#desc(String) descending sort}
+     * with lower priority than all other sort criteria (if any) that have already
+     * been specified.</p>
+     *
+     * @param property name of the entity attribute upon which to sort.
+     * @return a new instance of <code>Pageable</code> with the descending sort
+     *         as its lowest priority sort criteria.
+     * @throws NullPointerException when the property is null
+     */
+    Pageable<T> desc(String property);
+
+    /**
+     * <p>Creates a new page request with the same pagination information,
+     * appending the specified {@link Sort#descIgnoreCase(String) case-insensitive descending sort}
+     * with lower priority than all other sort criteria (if any) that have already
+     * been specified. The case-insensitive sort means that the respective value
+     * in the database is compared independent of case.</p>
+     *
+     * @param property name of the entity attribute upon which to sort.
+     * @return a new instance of <code>Pageable</code> with the case-insensitive descending sort
+     *         as its lowest priority sort criteria.
+     * @throws NullPointerException when the property is null
+     */
+    Pageable<T> descIgnoreCase(String property);
 
     /**
      * Compares with another instance to determine if both represent the same
@@ -186,7 +282,7 @@ public interface Pageable {
      *
      * @return the order collection; will never be {@code null}.
      */
-    List<Sort> sorts();
+    List<Sort<T>> sorts();
 
     /**
      * <p>Returns the <code>Pageable</code> requesting the next page
@@ -202,7 +298,7 @@ public interface Pageable {
      * @throws UnsupportedOperationException if this <code>Pageable</code> has a
      *         {@link Pageable.Cursor Cursor}.
      */
-    Pageable next();
+    Pageable<T> next();
 
     /**
      * <p>Creates a new page request with the same
@@ -211,7 +307,7 @@ public interface Pageable {
      * @param pageNumber The page number
      * @return a new instance of <code>Pageable</code>. This method never returns <code>null</code>.
      */
-    Pageable page(long pageNumber);
+    Pageable<T> page(long pageNumber);
 
     /**
      * <p>Creates a new page request with the same
@@ -223,7 +319,7 @@ public interface Pageable {
      * @param maxPageSize the number of query results in a full page.
      * @return a new instance of <code>Pageable</code>. This method never returns <code>null</code>.
      */
-    Pageable size(int maxPageSize);
+    Pageable<T> size(int maxPageSize);
 
     /**
      * <p>Creates a new page request with the same
@@ -237,7 +333,69 @@ public interface Pageable {
      * @param sorts sort criteria to use.
      * @return a new instance of <code>Pageable</code>. This method never returns <code>null</code>.
      */
-    Pageable sortBy(Iterable<Sort> sorts);
+    Pageable<T> sortBy(Iterable<Sort<T>> sorts);
+
+    /**
+     * <p>Creates a new page request with the same
+     * pagination information, except using the specified sort criteria.
+     * The order of precedence for sort criteria is that of any statically
+     * specified sort criteria (from the <code>OrderBy</code> keyword,
+     * {@link OrderBy} annotation or <code>ORDER BY</code> clause of a the
+     * {@link Query} annotation) followed by the order in which the
+     * {@link Sort} parameters to this method are listed.</p>
+     *
+     * @param sort sort criteria to use.
+     * @return a new instance of <code>Pageable</code>. This method never returns <code>null</code>.
+     */
+    Pageable<T> sortBy(Sort<T> sort);
+
+    /**
+     * <p>Creates a new page request with the same
+     * pagination information, except using the specified sort criteria.
+     * The order of precedence for sort criteria is that of any statically
+     * specified sort criteria (from the <code>OrderBy</code> keyword,
+     * {@link OrderBy} annotation or <code>ORDER BY</code> clause of a the
+     * {@link Query} annotation) followed by the order in which the
+     * {@link Sort} parameters to this method are listed.</p>
+     *
+     * @param sort1 dynamic sort criteria to use first.
+     * @param sort2 dynamic sort criteria to use second.
+     * @return a new instance of <code>Pageable</code>. This method never returns <code>null</code>.
+     */
+    Pageable<T> sortBy(Sort<T> sort1, Sort<T> sort2);
+
+    /**
+     * <p>Creates a new page request with the same
+     * pagination information, except using the specified sort criteria.
+     * The order of precedence for sort criteria is that of any statically
+     * specified sort criteria (from the <code>OrderBy</code> keyword,
+     * {@link OrderBy} annotation or <code>ORDER BY</code> clause of a the
+     * {@link Query} annotation) followed by the order in which the
+     * {@link Sort} parameters to this method are listed.</p>
+     *
+     * @param sort1 dynamic sort criteria to use first.
+     * @param sort2 dynamic sort criteria to use second.
+     * @param sort3 dynamic sort criteria to use last.
+     * @return a new instance of <code>Pageable</code>. This method never returns <code>null</code>.
+     */
+    Pageable<T> sortBy(Sort<T> sort1, Sort<T> sort2, Sort<T> sort3);
+
+    /**
+     * <p>Creates a new page request with the same
+     * pagination information, except using the specified sort criteria.
+     * The order of precedence for sort criteria is that of any statically
+     * specified sort criteria (from the <code>OrderBy</code> keyword,
+     * {@link OrderBy} annotation or <code>ORDER BY</code> clause of a the
+     * {@link Query} annotation) followed by the order in which the
+     * {@link Sort} parameters to this method are listed.</p>
+     *
+     * @param sort1 dynamic sort criteria to use first.
+     * @param sort2 dynamic sort criteria to use second.
+     * @param sort3 dynamic sort criteria to use third.
+     * @param sort4 dynamic sort criteria to use last.
+     * @return a new instance of <code>Pageable</code>. This method never returns <code>null</code>.
+     */
+    Pageable<T> sortBy(Sort<T> sort1, Sort<T> sort2, Sort<T> sort3, Sort<T> sort4);
 
     /**
      * <p>Creates a new page request with the same
@@ -248,11 +406,14 @@ public interface Pageable {
      * {@link Query} annotation) followed by the order in which the
      * {@link Sort} parameters to this method are listed.</p>
      *
-     * @param sorts sort criteria to use. This method can be invoked without parameters
-     *        to request a <code>Pageable</code> that does not specify sort criteria.
+     * @param sort1 dynamic sort criteria to use first.
+     * @param sort2 dynamic sort criteria to use second.
+     * @param sort3 dynamic sort criteria to use third.
+     * @param sort4 dynamic sort criteria to use fourth.
+     * @param sort5 dynamic sort criteria to use last.
      * @return a new instance of <code>Pageable</code>. This method never returns <code>null</code>.
      */
-    Pageable sortBy(Sort... sorts);
+    Pageable<T> sortBy(Sort<T> sort1, Sort<T> sort2, Sort<T> sort3, Sort<T> sort4, Sort<T> sort5);
 
     /**
      * The type of pagination: offset-based or
diff --git a/api/src/main/java/jakarta/data/page/Pagination.java b/api/src/main/java/jakarta/data/page/Pagination.java
index 81af7096..1dd58faf 100644
--- a/api/src/main/java/jakarta/data/page/Pagination.java
+++ b/api/src/main/java/jakarta/data/page/Pagination.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022,2023 Contributors to the Eclipse Foundation
+ * Copyright (c) 2022,2024 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.
@@ -18,6 +18,8 @@
 package jakarta.data.page;
 
 import jakarta.data.Sort;
+
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
@@ -27,7 +29,7 @@ import java.util.stream.StreamSupport;
 /**
  * Built-in implementation of Pageable.
  */
-record Pagination(long page, int size, List<Sort> sorts, Mode mode, Cursor type) implements Pageable {
+record Pagination<T>(long page, int size, List<Sort<T>> sorts, Mode mode, Cursor type) implements Pageable<T> {
 
     Pagination {
         if (page < 1) {
@@ -42,23 +44,46 @@ record Pagination(long page, int size, List<Sort> sorts, Mode mode, Cursor type)
     }
 
     @Override
-    public Pageable afterKeyset(Object... keyset) {
-        return new Pagination(page, size, sorts, Mode.CURSOR_NEXT, new KeysetCursor(keyset));
+    public Pageable<T> afterKeyset(Object... keyset) {
+        return new Pagination<T>(page, size, sorts, Mode.CURSOR_NEXT, new KeysetCursor(keyset));
+    }
+
+    @Override
+    public Pageable<T> beforeKeyset(Object... keyset) {
+        return new Pagination<T>(page, size, sorts, Mode.CURSOR_PREVIOUS, new KeysetCursor(keyset));
     }
 
     @Override
-    public Pageable beforeKeyset(Object... keyset) {
-        return new Pagination(page, size, sorts, Mode.CURSOR_PREVIOUS, new KeysetCursor(keyset));
+    public Pageable<T> afterKeysetCursor(Cursor keysetCursor) {
+        return new Pagination<T>(page, size, sorts, Mode.CURSOR_NEXT, keysetCursor);
     }
 
     @Override
-    public Pageable afterKeysetCursor(Cursor keysetCursor) {
-        return new Pagination(page, size, sorts, Mode.CURSOR_NEXT, keysetCursor);
+    public Pageable<T> beforeKeysetCursor(Cursor keysetCursor) {
+        return new Pagination<T>(page, size, sorts, Mode.CURSOR_PREVIOUS, keysetCursor);
     }
 
     @Override
-    public Pageable beforeKeysetCursor(Cursor keysetCursor) {
-        return new Pagination(page, size, sorts, Mode.CURSOR_PREVIOUS, keysetCursor);
+    public Pageable<T> asc(String property) {
+        return new Pagination<T>(page, size, combine(sorts, Sort.asc(property)), mode, type);
+    }
+
+    @Override
+    public Pageable<T> ascIgnoreCase(String property) {
+        return new Pagination<T>(page, size, combine(sorts, Sort.ascIgnoreCase(property)), mode, type);
+    }
+
+    private static final <E> List<E> combine(List<E> list, E element) {
+        int size = list.size();
+        if (size == 0) {
+            return List.of(element);
+        } else {
+            Object[] array = list.toArray(new Object[size + 1]);
+            array[size] = element;
+            @SuppressWarnings("unchecked")
+            List<E> newList = (List<E>) Collections.unmodifiableList(Arrays.asList(array));
+            return newList;
+        }
     }
 
     @Override
@@ -67,9 +92,19 @@ record Pagination(long page, int size, List<Sort> sorts, Mode mode, Cursor type)
     }
 
     @Override
-    public Pageable next() {
+    public Pageable<T> desc(String property) {
+        return new Pagination<T>(page, size, combine(sorts, Sort.desc(property)), mode, type);
+    }
+
+    @Override
+    public Pageable<T> descIgnoreCase(String property) {
+        return new Pagination<T>(page, size, combine(sorts, Sort.descIgnoreCase(property)), mode, type);
+    }
+
+    @Override
+    public Pageable<T> next() {
         if (mode == Mode.OFFSET) {
-            return new Pagination((page + 1), this.size, this.sorts, Mode.OFFSET, null);
+            return new Pagination<T>((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.");
@@ -85,7 +120,7 @@ record Pagination(long page, int size, List<Sort> sorts, Mode mode, Cursor type)
             s.append(", mode=").append(mode)
             .append(", ").append(type.size()).append(" keys");
         }
-        for (Sort sort : sorts) {
+        for (Sort<T> sort : sorts) {
             s.append(", ").append(sort.property());
             if (sort.ignoreCase()) {
                 s.append(" IGNORE CASE");
@@ -96,25 +131,45 @@ record Pagination(long page, int size, List<Sort> sorts, Mode mode, Cursor type)
     }
 
     @Override
-    public Pageable page(long pageNumber) {
-        return new Pagination(pageNumber, size, sorts, mode, type);
+    public Pageable<T> page(long pageNumber) {
+        return new Pagination<T>(pageNumber, size, sorts, mode, type);
     }
 
     @Override
-    public Pageable size(int maxPageSize) {
-        return new Pagination(page, maxPageSize, sorts, mode, type);
+    public Pageable<T> size(int maxPageSize) {
+        return new Pagination<T>(page, maxPageSize, sorts, mode, type);
     }
 
     @Override
-    public Pageable sortBy(Iterable<Sort> sorts) {
-        List<Sort> sortList = sorts == null
-                ? Collections.emptyList()
+    public Pageable<T> sortBy(Iterable<Sort<T>> sorts) {
+        List<Sort<T>> sortList = sorts instanceof List ? List.copyOf((List<Sort<T>>) sorts)
+                : sorts == null ? Collections.emptyList()
                 : StreamSupport.stream(sorts.spliterator(), false).collect(Collectors.toUnmodifiableList());
-        return new Pagination(page, size, sortList, mode, type);
+        return new Pagination<T>(page, size, sortList, mode, type);
+    }
+
+    @Override
+    public Pageable<T> sortBy(Sort<T> sort) {
+        return new Pagination<T>(page, size, List.of(sort), mode, type);
+    }
+
+    @Override
+    public Pageable<T> sortBy(Sort<T> sort1, Sort<T> sort2) {
+        return new Pagination<T>(page, size, List.of(sort1, sort2), mode, type);
+    }
+
+    @Override
+    public Pageable<T> sortBy(Sort<T> sort1, Sort<T> sort2, Sort<T> sort3) {
+        return new Pagination<T>(page, size, List.of(sort1, sort2, sort3), mode, type);
+    }
+
+    @Override
+    public Pageable<T> sortBy(Sort<T> sort1, Sort<T> sort2, Sort<T> sort3, Sort<T> sort4) {
+        return new Pagination<T>(page, size, List.of(sort1, sort2, sort3, sort4), mode, type);
     }
 
     @Override
-    public Pageable sortBy(Sort... sorts) {
-        return new Pagination(page, size, sorts == null ? Collections.emptyList() : List.of(sorts), mode, type);
+    public Pageable<T> sortBy(Sort<T> sort1, Sort<T> sort2, Sort<T> sort3, Sort<T> sort4, Sort<T> sort5) {
+        return new Pagination<T>(page, size, List.of(sort1, sort2, sort3, sort4, sort5), mode, type);
     }
 }
\ No newline at end of file
diff --git a/api/src/main/java/jakarta/data/page/Slice.java b/api/src/main/java/jakarta/data/page/Slice.java
index d30e91dd..7c708328 100644
--- a/api/src/main/java/jakarta/data/page/Slice.java
+++ b/api/src/main/java/jakarta/data/page/Slice.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022,2023 Contributors to the Eclipse Foundation
+ * Copyright (c) 2022,2024 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.
@@ -18,6 +18,7 @@
 package jakarta.data.page;
 
 import jakarta.data.Streamable;
+import jakarta.data.repository.Query;
 import java.util.List;
 
 /**
@@ -29,7 +30,7 @@ import java.util.List;
  * {@code Slice<Vehicle>} find({@code @By("make")} String make,
  *                     {@code @By("model")} String model,
  *                     {@code @By("year")} int designYear,
- *                     Pageable pagination);
+ *                     {@code Pageable<?>} pageRequest);
  * </pre>
  *
  * <p>Unlike {@link Page}, a {@code Slice} does not have awareness of the total number of pages
@@ -78,12 +79,42 @@ public interface Slice<T> extends Streamable<T> {
      *
      * @return the request for the current page; will never be {@code null}.
      */
-    Pageable pageable();
+    Pageable<T> pageable();
+
+    /**
+     * <p>Returns the {@link Pageable page request} for which this
+     * slice was obtained.</p>
+     *
+     * <p>This method is provided for when {@link Query query language} is used to
+     * return a result of different type than the entity that is being queried.
+     * This method allows the {@link Pageable} to be returned for the
+     * type of entity class that was queried.</p>
+     *
+     * @param <E>         entity class of the attributes that are used as sort criteria.
+     * @param entityClass entity class of the attributes that are used as sort criteria.
+     * @return the request for the current page; will never be {@code null}.
+     */
+    <E> Pageable<E> pageable(Class<E> entityClass);
 
     /**
      * Returns a request for the {@link Pageable#next() next} page, or <code>null</code> if it is known that there is no next page.
      *
      * @return a request for the next page.
      */
-    Pageable nextPageable();
+    Pageable<T> nextPageable();
+
+    /**
+     * <p>Returns a request for the {@link Pageable#next() next} page,
+     * or <code>null</code> if it is known that there is no next page.</p>
+     *
+     * <p>This method is useful when {@link Query query language} is used to
+     * return a result of different type than the entity that is being queried.
+     * This method allows the subsequent {@link Pageable} to be returned for the
+     * type of entity class that is being queried.</p>
+     *
+     * @param <E>         entity class of the attributes that are used as sort criteria.
+     * @param entityClass entity class of the attributes that are used as sort criteria.
+     * @return a request for the next page.
+     */
+    <E> Pageable<E> nextPageable(Class<E> entityClass);
 }
diff --git a/api/src/main/java/jakarta/data/page/package-info.java b/api/src/main/java/jakarta/data/page/package-info.java
index c2fc622c..6977de44 100644
--- a/api/src/main/java/jakarta/data/page/package-info.java
+++ b/api/src/main/java/jakarta/data/page/package-info.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023 Contributors to the Eclipse Foundation
+ * Copyright (c) 2023,2024 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.
@@ -30,9 +30,9 @@
  * to allow data to be read in slices or pages,</p>
  *
  * <pre>
- * Pageable.ofSize(25).sortBy(Sort.asc("lastName"),
- *                            Sort.asc("firstName"),
- *                            Sort.asc("id")); // unique identifier
+ * Pageable.of(Person.class).size(25).sortBy(Sort.asc("lastName"),
+ *                                           Sort.asc("firstName"),
+ *                                           Sort.asc("id")); // unique identifier
  * </pre>
  *
  * <p>In the example above, even if multiple people have the same last names
diff --git a/api/src/main/java/jakarta/data/repository/CrudRepository.java b/api/src/main/java/jakarta/data/repository/CrudRepository.java
index 88007510..467c90b7 100644
--- a/api/src/main/java/jakarta/data/repository/CrudRepository.java
+++ b/api/src/main/java/jakarta/data/repository/CrudRepository.java
@@ -53,7 +53,7 @@ import jakarta.data.exceptions.EntityExistsException;
  * {@code @Repository}
  * public interface Cars extends CrudRepository{@code <Car, Long>} {
  *
- *     List{@code <Car>} findByMakeAndModel(String make, String model, Sort...);
+ *     List{@code <Car>} findByMakeAndModel(String make, String model, {@code Sort<?>...} sorts);
  *
  *     ...
  * }
diff --git a/api/src/main/java/jakarta/data/repository/OrderBy.java b/api/src/main/java/jakarta/data/repository/OrderBy.java
index ba1be988..bc0a9529 100644
--- a/api/src/main/java/jakarta/data/repository/OrderBy.java
+++ b/api/src/main/java/jakarta/data/repository/OrderBy.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022,2023 Contributors to the Eclipse Foundation
+ * Copyright (c) 2022,2024 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.
@@ -17,6 +17,7 @@
  */
 package jakarta.data.repository;
 
+import jakarta.data.Order;
 import jakarta.data.Sort;
 import jakarta.data.page.Pageable;
 import java.lang.annotation.ElementType;
@@ -32,8 +33,8 @@ import java.lang.annotation.Target;
  * repository method, the precedence for sorting follows the order
  * in which the <code>OrderBy</code> annotations are specified,
  * and after that follows any sort criteria that is supplied
- * dynamically by {@link Sort} parameters or by a
- * {@link Pageable} parameter with {@link Pageable#sorts()}.</p>
+ * dynamically by {@link Sort} parameters, {@link Order} parameter, or by a
+ * {@link Pageable} parameter with {@link Pageable#sorts() sort criteria}.</p>
  *
  * <p>For example, the following sorts first by the
  * <code>lastName</code> attribute in ascending order,
@@ -47,7 +48,7 @@ import java.lang.annotation.Target;
  * <pre>
  * &#64;OrderBy("lastName")
  * &#64;OrderBy("firstName")
- * Person[] findByZipCode(int zipCode, Pageable pagination);
+ * Person[] findByZipCode(int zipCode, {@code Pageable<?>} pageRequest);
  * </pre>
  *
  * <p>The precise meaning of ascending and descending order is
diff --git a/api/src/main/java/jakarta/data/repository/PageableRepository.java b/api/src/main/java/jakarta/data/repository/PageableRepository.java
index 97662ba3..e4369176 100644
--- a/api/src/main/java/jakarta/data/repository/PageableRepository.java
+++ b/api/src/main/java/jakarta/data/repository/PageableRepository.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022,2023 Contributors to the Eclipse Foundation
+ * Copyright (c) 2022,2024 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.
@@ -22,7 +22,7 @@ import jakarta.data.page.Page;
 import jakarta.data.page.Pageable;
 
 /**
- * <p>A built-in repository supertype with methods that use pagination and sorting to retreive entities.
+ * <p>A built-in repository supertype with methods that use pagination and sorting to retrieve entities.
  * Methods that are inherited from {@link BasicRepository} provide additional basic built-in save, delete, and find
  * functionality.</p>
  *
@@ -71,7 +71,7 @@ import jakarta.data.page.Pageable;
  *
  * long howMany = people.countByFirstName(person1.firstName);
  *
- * Pagination page1Request = Pageable.ofSize(25).sortBy(Sort.asc("ssn"));
+ * Pagination page1Request = Pageable.of(Person.class).size(25).sortBy(Sort.asc("ssn"));
  * Page{@code <Person>} page1 = people.findAll(page1Request);
  * </pre>
  *
@@ -93,6 +93,6 @@ public interface PageableRepository<T, K> extends BasicRepository<T, K> {
      * or {@link Pageable.Mode#CURSOR_PREVIOUS} pagination mode is selected.
      * @see Pageable.Mode
      */
-    Page<T> findAll(Pageable pageable);
+    Page<T> findAll(Pageable<T> pageable);
 
 }
diff --git a/api/src/main/java/jakarta/data/repository/Param.java b/api/src/main/java/jakarta/data/repository/Param.java
index 71a7ad93..cdf85682 100644
--- a/api/src/main/java/jakarta/data/repository/Param.java
+++ b/api/src/main/java/jakarta/data/repository/Param.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Contributors to the Eclipse Foundation
+ * Copyright (c) 2022,2024 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.
@@ -34,7 +34,7 @@ import java.lang.annotation.Target;
  *
  *     {@code @Query("SELECT p from Products p WHERE (p.length * p.width * p.height <= :maxVolume)")}
  *     {@code Page<Product>} freeShippingEligible({@code @Param}("maxVolume") float volumeLimit,
- *                                        Pageable pageRequest);
+ *                                        {@code Pageable<?>} pageRequest);
  *
  *     ...
  * }
diff --git a/api/src/main/java/jakarta/data/repository/Query.java b/api/src/main/java/jakarta/data/repository/Query.java
index ff0c126d..d85a3563 100644
--- a/api/src/main/java/jakarta/data/repository/Query.java
+++ b/api/src/main/java/jakarta/data/repository/Query.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022,2023 Contributors to the Eclipse Foundation
+ * Copyright (c) 2022,2024 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.
@@ -20,6 +20,7 @@ package jakarta.data.repository;
 import jakarta.data.Sort;
 import jakarta.data.page.KeysetAwarePage;
 import jakarta.data.page.Page;
+import jakarta.data.page.Slice;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -32,6 +33,8 @@ import java.lang.annotation.Target;
  * JPQL queries if backed by a Jakarta Persistence provider,
  * and otherwise must support SQL queries.</p>
  *
+ * <h2>Parameters</h2>
+ *
  * <p>The query language can used named parameters or positional parameters.</p>
  *
  * <p><b>Named parameters</b> are referred to by name within the query language.
@@ -60,13 +63,25 @@ import java.lang.annotation.Target;
  *
  *     // JPQL using named parameters
  *     {@code @Query("SELECT DISTINCT p.name from Person p WHERE (LENGTH(p.name) >= :min AND LENGTH(p.name) <= :max)")}
- *     {@code List<String>} namesOfLength({@code @Param}("min") int minLength,
- *                                {@code @Param}("max") int minLength,
- *                                Sort... sorts);
+ *     {@code Page<String>} namesOfLength({@code @Param}("min") int minLength,
+ *                                {@code @Param}("max") int maxLength,
+ *                                {@code Pageable<Person>} pageRequest);
  *
  *     ...
  * }
  * </pre>
+ *
+ * <h2>Return Types</h2>
+ *
+ * <p>Some query languages such as JPQL can be used to return a type other than the
+ * entity class, as shown in the above example, resulting in a {@link Page} that is
+ * parameterized with the query result type rather than the entity class.
+ * to request a subsequent page, use the {@link Slice#nextPageable(Class)} method
+ * to specify the entity class. For example,</p>
+ *
+ * <pre>
+ * {@code Page<String>} page2 = people.namesOfLength(5, 10, page1.nextPageable(Person.class));
+ * </pre>
  */
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.METHOD)
diff --git a/api/src/main/java/module-info.java b/api/src/main/java/module-info.java
index 3a7f64ad..f27d33c4 100644
--- a/api/src/main/java/module-info.java
+++ b/api/src/main/java/module-info.java
@@ -17,7 +17,9 @@
  */
 
 import jakarta.data.Limit;
+import jakarta.data.Order;
 import jakarta.data.Sort;
+import jakarta.data.metamodel.StaticMetamodel;
 import jakarta.data.page.Pageable;
 import jakarta.data.repository.BasicRepository;
 import jakarta.data.repository.By;
@@ -149,15 +151,15 @@ import java.util.List;
  * }
  *
  * &#64;Repository
- * public interface Orders {
+ * public interface Purchases {
  *     &#64;OrderBy("address.zipCode")
- *     List&lt;Order&gt; findByAddressZipCodeIn(List&lt;Integer&gt; zipCodes);
+ *     List&lt;Purchase&gt; findByAddressZipCodeIn(List&lt;Integer&gt; zipCodes);
  *
- *     &#64;Query("SELECT o FROM Order o WHERE o.address.zipCode=?1")
- *     List&lt;Order&gt; forZipCode(int zipCode);
+ *     &#64;Query("SELECT o FROM Purchase o WHERE o.address.zipCode=?1")
+ *     List&lt;Purchase&gt; forZipCode(int zipCode);
  *
  *     &#64;Save
- *     Order checkout(Order order);
+ *     Purchase checkout(Purchase purchase);
  * }
  * </pre>
  *
@@ -582,10 +584,10 @@ import java.util.List;
  *
  * <pre>
  * // Query by Method Name:
- * Vehicle[] findByMakeAndModelAndYear(String makerName, String model, int year, Sort... sorts);
+ * Vehicle[] findByMakeAndModelAndYear(String makerName, String model, int year, {@code Sort<?>...} sorts);
  *
  * // Parameter-based Conditions:
- * Vehicle[] find(String make, String model, int year, Sort... sorts);
+ * Vehicle[] find(String make, String model, int year, {@code Sort<?>...} sorts);
  * </pre>
  *
  * <h2>Additional Method Parameters</h2>
@@ -615,40 +617,64 @@ import java.util.List;
  *
  * <h3>Pagination</h3>
  *
- * <p>You can request that results be paginated by adding a {@link Pageable}
+ * <p>You can request that results be split into pages by adding a {@link Pageable}
  * parameter to a repository find method. For example,</p>
  *
  * <pre>
  * Product[] findByNameLikeOrderByAmountSoldDescNameAsc(
- *           String pattern, Pageable pagination);
+ *           String pattern, {@code Pageable<Product>} pageRequest);
  * ...
  * page1 = products.findByNameLikeOrderByAmountSoldDescNameAsc(
- *                  "%phone%", Pageable.ofSize(20));
+ *                  "%phone%", Pageable.of(Product.class).size(20));
  * </pre>
  *
  * <h3>Sorting at Runtime</h3>
  *
- * <p>When using pagination, you can dynamically supply sorting criteria
- * via the {@link Pageable#sortBy(Sort...)} and {@link Pageable#sortBy(Iterable)}
- * methods. For example,</p>
+ * <p>When requesting pages, you can dynamically supply sorting criteria
+ * via the {@link Pageable#sortBy(Sort)} method and other methods
+ * of {@code Pageable} with the same name. For example,</p>
  *
  * <pre>
- * Product[] findByNameLike(String pattern, Pageable pagination);
+ * Product[] findByNameLike(String pattern, {@code Pageable<Product>} pagination);
  *
  * ...
- * Pageable pagination = Pageable.ofSize(25).sortBy(
- *                           Sort.desc("price"),
- *                           Sort.asc("name"));
+ * {@code Pageable<Product>} page1Request = Pageable.of(Product.class)
+ *                                          .size(25)
+ *                                          .sortBy(Sort.desc("price"),
+ *                                                  Sort.asc("name"));
  * page1 = products.findByNameLikeAndPriceBetween(
- *                 namePattern, minPrice, maxPrice, pagination);
+ *                 namePattern, minPrice, maxPrice, page1Request);
  * </pre>
  *
- * <p>To supply sorting criteria dynamically without using pagination,
- * add one or more {@link Sort} parameters (or <code>Sort...</code>)
- * to a repository find method. For example,</p>
+ * <p>An alternative when using the {@link StaticMetamodel} is to obtain the
+ * page request from an {@link Order} instance, as follows,</p>
  *
  * <pre>
- * Product[] findByNameLike(String pattern, Limit max, Sort... sortBy);
+ * {@code Pageable<Product>} pageRequest = Order.by(_Product.price.desc(),
+ *                                          _Product.name.asc())
+ *                                      .pageSize(25));
+ * </pre>
+ *
+ * <p>To supply sort criteria dynamically without using pagination,
+ * populate an {@link Order} instance with one or more {@link Sort} parameters
+ * and supply it to a repository find method. For example,</p>
+ *
+ * <pre>
+ * Product[] findByNameLike(String pattern, Limit max, {@code Order<Product>} sortBy);
+ *
+ * ...
+ * found = products.findByNameLike(namePattern, Limit.of(25), Order.by(
+ *                                 Sort.desc("price"),
+ *                                 Sort.desc("amountSold"),
+ *                                 Sort.asc("name")));
+ * </pre>
+ *
+ * <p>Generic, untyped {@link Sort} criteria can be supplied directly to a
+ * repository method with a variable arguments {@code Sort<?>...} parameter.
+ * For example,</p>
+ *
+ * <pre>
+ * Product[] findByNameLike(String pattern, Limit max, {@code Sort<?>...} sortBy);
  *
  * ...
  * found = products.findByNameLike(namePattern, Limit.of(25),
diff --git a/spec/src/main/asciidoc/repository.asciidoc b/spec/src/main/asciidoc/repository.asciidoc
index 67663b55..02e5e235 100644
--- a/spec/src/main/asciidoc/repository.asciidoc
+++ b/spec/src/main/asciidoc/repository.asciidoc
@@ -607,9 +607,9 @@ public class _Product {
   public static final String NAME = "name";
   public static final String PRICE = "price";
 
-  public static volatile SortableAttribute id;
-  public static volatile TextAttribute name;
-  public static volatile SortableAttribute price;
+  public static volatile SortableAttribute<Product> id;
+  public static volatile TextAttribute<Product> name;
+  public static volatile SortableAttribute<Product> price;
 }
 ----
 
@@ -721,7 +721,7 @@ Each parameter of an annotated query method must either:
 
 - have exactly the same name and type as a named parameter of the query,
 - have exactly the same type and position within the parameter list of the method as a positional parameter of the query, or
-- be of type `Limit`, `Pageable`, or `Sort`.
+- be of type `Limit`, `Order`, `Pageable`, or `Sort`.
 
 A repository with annotated query methods with named parameters must be compiled so that parameter names are preserved in the class file (for example, using `javac -parameters`), or the parameter names must be specified explicitly using the `@Param` annotation.
 
@@ -780,7 +780,7 @@ The Jakarta Data implementation automatically recognizes the query annotations i
 An _automatic query method_ is an abstract method that either generates a query based on the parameters of the method or based on the name of the method (the Query by Method Name pattern is discussed separately). The method return type identifies the entity. For example: `E`, `Optional<E>`, `Page<E>`, or `List<E>`, where `E` is an entity class. Each parameter must either:
 
 - have exactly the same type and name as a persistent field or property of the entity class, or
-- be of type `Limit`, `Pageable`, or `Sort`.
+- be of type `Limit`, `Order`, `Pageable`, or `Sort`.
 
 A repository with automatic query methods that are based on parameters must either be compiled so that parameter names are preserved in the class file (for example, using `javac -parameters`), or the corresponding entity attribute name for parameters must be specified explicitly using the `@By` annotation.
 
@@ -794,7 +794,7 @@ List<Book> booksByYear(Year year, Sort order, Limit limit);
 
 Page<Book> find(@By("year") Year publishedIn,
                 @By("genre") Category type,
-                Pageable pagination);
+                Pageable<?> pagination);
 ----
 
 Automatic query methods _are_ portable between providers.
@@ -1073,16 +1073,16 @@ Refer to the Jakarta Data module JavaDoc section on "Return Types for Repository
 
 Jakarta Data also supports particular parameters to define pagination and sorting.
 
-Jakarta Data recognizes, when specified on a repository method after the query parameters, the specific types, `Limit`, `Pageable`, and `Sort`, to dynamically apply limits, pagination, and sorting to queries, respectively. The following example demonstrates these features:
+Jakarta Data recognizes, when specified on a repository method after the query parameters, the specific types, `Limit`, `Order`, `Pageable`, and `Sort`, to dynamically apply limits, pagination, and sorting to queries, respectively. The following example demonstrates these features:
 
 [source,java]
 ----
 @Repository
 public interface ProductRepository extends BasicRepository<Product, Long> {
 
-  List<Product> findByName(String name, Pageable pageable);
+  List<Product> findByName(String name, Pageable<?> pageable);
 
-  List<Product> findByNameLike(String pattern, Limit max, Sort... sorts);
+  List<Product> findByNameLike(String pattern, Limit max, Sort<?>... sorts);
 
 }
 ----
@@ -1098,7 +1098,7 @@ You can combine sorting with a starting page and maximum page size by using prop
 
 [source,java]
 ----
-Pageable pageable = Pageable.ofSize(20).page(1).sortBy(Sort.desc("price"));
+Pageable<?> pageable = Pageable.ofSize(20).page(1).sortBy(Sort.desc("price"));
 first20 = products.findByNameLike(name, pageable);
 
 ----
@@ -1119,7 +1119,7 @@ Sort criteria is provided statically for a repository method by using the `Order
 
 ==== Dynamic Mechanisms for Sort Criteria
 
-Sort criteria is provided dynamically to repository methods either via `Sort` parameters or via a `Pageable` parameter that has one or more `Sort` values. `Sort` and `Pageable` containing `Sort` must not both be provided to the same method.
+Sort criteria is provided dynamically to repository methods either via `Sort` parameters or via a `Pageable` or `Order` parameter that has one or more `Sort` values. `Sort` and `Pageable` containing `Sort` must not both be provided to the same method. Similarly, `Order` and `Pageable` containing `Sort` must not both be provided to the same method.
 
 ==== Examples of Sort Criteria Precedence
 
@@ -1128,23 +1128,23 @@ The following examples work through scenarios where static and dynamic sort crit
 [source,java]
 ----
 // Sorts first by type. When type is the same, applies the Pageable's sort criteria
-Page<User> findByNameStartsWithOrderByType(String namePrefix, Pageable pagination);
+Page<User> findByNameStartsWithOrderByType(String namePrefix, Pageable<?> pagination);
 
 // Sorts first by type. When type is the same, applies the criteria in the Sorts
-List<User> findByNameStartsWithOrderByType(String namePrefix, Sort... sorts);
+List<User> findByNameStartsWithOrderByType(String namePrefix, Sort<?>... sorts);
 
 // Sorts first by age. When age is the same, applies the Pageable's sort criteria
 @OrderBy("age")
-Page<User> findByNameStartsWith(String namePrefix, Pageable pagination);
+Page<User> findByNameStartsWith(String namePrefix, Pageable<?> pagination);
 
 // Sorts first by age. When age is the same, applies the criteria in the Sorts
 @OrderBy("age")
-List<User> findByNameStartsWith(String namePrefix, Sort... sorts);
+List<User> findByNameStartsWith(String namePrefix, Sort<?>... sorts);
 
 // Sorts first by name. When name is the same, applies the Pageable's sort criteria
 @Query("SELECT u FROM User u WHERE (u.age > ?1)")
 @OrderBy("name")
-KeysetAwarePage<User> olderThan(int age, Pageable pagination);
+KeysetAwarePage<User> olderThan(int age, Pageable<?> pagination);
 ----
 
 === Pagination in Jakarta Data
@@ -1237,7 +1237,10 @@ Code Execution:
 @Inject
 People people;
 
-Page<Person> page = people.findAll(Pageable.ofPage(1).size(2).sortBy(Sort.asc("id")));
+Page<Person> page = people.findAll(Pageable.of(Person.class)
+                                           .page(1)
+                                           .size(2)
+                                           .sortBy(Sort.asc("id")));
 ----
 
 Resulting Page Content:
@@ -1255,7 +1258,7 @@ Next Page Execution:
 
 [source,java]
 ----
-Pageable nextPageable = page.nextPageable();
+Pageable<Person> nextPageable = page.nextPageable();
 Page<Person> page2 = people.findAll(nextPageable);
 ----
 
@@ -1286,7 +1289,7 @@ For example,
 @Repository
 public interface CustomerRepository extends BasicRepository<Customer, Long> {
   KeysetAwareSlice<Customer> findByZipcodeOrderByLastNameAscFirstNameAscIdAsc(
-                                 int zipcode, Pageable pageable);
+                                 int zipcode, Pageable<?> pageable);
 }
 ----
 
@@ -1294,7 +1297,7 @@ You can obtain the initial page relative to an offset and subsequent pages relat
 
 [source,java]
 ----
-for (Pageable p = Pageable.ofSize(50); p != null; ) {
+for (Pageable<?> p = Pageable.ofSize(50); p != null; ) {
   page = customers.findByZipcodeOrderByLastNameAscFirstNameAscIdAsc(55901, p);
   ...
   p = page.nextPageable();
@@ -1306,7 +1309,7 @@ Or you can obtain the next (or previous) page relative to a known entity,
 [source,java]
 ----
 Customer c = ...
-Pageable 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);
 ----
 
@@ -1353,7 +1356,7 @@ Other types of updates to data, however, will cause duplicate or missed results.
 * 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 `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.
+* 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` criteria 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
 
@@ -1364,7 +1367,7 @@ Here is an example where an application uses `@Query` to provide a partial query
 @Repository
 public interface CustomerRepository extends BasicRepository<Customer, Long> {
   @Query("SELECT o FROM Customer o WHERE (o.totalSpent / o.totalPurchases > ?1)")
-  KeysetAwareSlice<Customer> withAveragePurchaseAbove(float minimum, Pageable pagination);
+  KeysetAwareSlice<Customer> withAveragePurchaseAbove(float minimum, Pageable<?> pagination);
 }
 ----
 
@@ -1372,9 +1375,9 @@ Example traversal of pages:
 
 [source,java]
 ----
-for (Pageable p = Pageable.ofSize(25).sortBy(Sort.desc("yearBorn"),
-                                             Sort.asc("name"),
-                                             Sort.asc("id")));
+for (Pageable<?> p = Pageable.ofSize(25).sortBy(Sort.desc("yearBorn"),
+                                                Sort.asc("name"),
+                                                Sort.asc("id"));
      p != null; ) {
   page = customers.withAveragePurchaseAbove(50.0f, p);
   ...
@@ -1390,7 +1393,7 @@ In this example, the application uses a cursor to request pages in forward and p
 ----
 @Repository
 public interface Products extends CrudRepository<Product, Long> {
-  KeysetAwareSlice<Product> findByNameContains(String namePattern, Pageable pageRequest);
+  KeysetAwareSlice<Product> findByNameContains(String namePattern, Pageable<?> pageRequest);
 }
 ----
 
@@ -1399,9 +1402,9 @@ Obtaining the next 10 products that cost $50.00 or more:
 [source,java]
 ----
 float priceMidpoint = 50.0f;
-Pageable pageRequest = Pageable.ofSize(10)
-                               .sortBy(Sort.asc("price"), Sort.asc("id"))
-                               .afterKeyset(priceMidpoint, 0L);
+Pageable<?> pageRequest = Pageable.ofSize(10)
+                                  .sortBy(Sort.asc("price"), Sort.asc("id"))
+                                  .afterKeyset(priceMidpoint, 0L);
 KeysetAwareSlice<Product> moreExpensive = products.findByNameContains(pattern, pageRequest);
 ----
 
@@ -1440,7 +1443,7 @@ This keyset cursor-based pagination scenario uses the same `Person` entity and e
 ----
 @Repository
 public interface People extends BasicRepository<Person, Long> {
-    KeysetAwarePage<Person> findAll(Pageable pagination);
+    KeysetAwarePage<Person> findAll(Pageable<Person> pagination);
 }
 ----
 
@@ -1451,7 +1454,9 @@ Code Execution:
 @Inject
 People people;
 
-Pageable firstPageRequest = Pageable.ofSize(4).sortBy(Sort.asc("name"), Sort.asc("id"));
+Pageable<Person> firstPageRequest = Pageable.of(Person.class)
+                                            .size(4)
+                                            .sortBy(Sort.asc("name"), Sort.asc("id"));
 KeysetAwarePage<Person> page = people.findAll(firstPageRequest);
 ----
 
@@ -1480,7 +1485,7 @@ Next Page Execution:
 
 [source,java]
 ----
-Pageable nextPageRequest = page.nextPageable();
+Pageable<Person> nextPageRequest = page.nextPageable();
 KeysetAwarePage<Person> page2 = people.findAll(nextPageRequest);
 ----
 
diff --git a/tck/src/main/java/ee/jakarta/tck/data/framework/read/only/AsciiCharacters.java b/tck/src/main/java/ee/jakarta/tck/data/framework/read/only/AsciiCharacters.java
index 86c4fd2e..2e7f394a 100644
--- a/tck/src/main/java/ee/jakarta/tck/data/framework/read/only/AsciiCharacters.java
+++ b/tck/src/main/java/ee/jakarta/tck/data/framework/read/only/AsciiCharacters.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023 Contributors to the Eclipse Foundation
+ * Copyright (c) 2023,2024 Contributors to the Eclipse Foundation
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -20,6 +20,7 @@ import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Stream;
 
+import jakarta.data.Order;
 import jakarta.data.Sort;
 import jakarta.data.Streamable;
 import jakarta.data.page.Page;
@@ -51,7 +52,7 @@ public interface AsciiCharacters extends DataRepository<AsciiCharacter, Long>, I
     Stream<AsciiCharacter> findByHexadecimalIgnoreCaseBetweenAndHexadecimalNotIn(String minHex,
                                                                                  String maxHex,
                                                                                  Set<String> excludeHex,
-                                                                                 Sort... sorts);
+                                                                                 Order<AsciiCharacter> sorts);
 
     AsciiCharacter findByHexadecimalIgnoreCase(String hex);
 
@@ -59,11 +60,11 @@ public interface AsciiCharacters extends DataRepository<AsciiCharacter, Long>, I
 
     Optional<AsciiCharacter> findByNumericValue(int id);
 
-    Page<AsciiCharacter> findByNumericValueBetween(int min, int max, Pageable pagination);
+    Page<AsciiCharacter> findByNumericValueBetween(int min, int max, Pageable<AsciiCharacter> pagination);
 
     Streamable<AsciiCharacter> findByNumericValueLessThanEqualAndNumericValueGreaterThanEqual(int max, int min);
 
-    AsciiCharacter[] findFirst3ByNumericValueGreaterThanEqualAndHexadecimalEndsWith(long minValue, String lastHexDigit, Sort sort);
+    AsciiCharacter[] findFirst3ByNumericValueGreaterThanEqualAndHexadecimalEndsWith(long minValue, String lastHexDigit, Sort<AsciiCharacter> sort);
 
     Optional<AsciiCharacter> findFirstByHexadecimalStartsWithAndIsControlOrderByIdAsc(String firstHexDigit, boolean isControlChar);
 
diff --git a/tck/src/main/java/ee/jakarta/tck/data/framework/read/only/IdOperations.java b/tck/src/main/java/ee/jakarta/tck/data/framework/read/only/IdOperations.java
index b24a9696..6dec72b3 100644
--- a/tck/src/main/java/ee/jakarta/tck/data/framework/read/only/IdOperations.java
+++ b/tck/src/main/java/ee/jakarta/tck/data/framework/read/only/IdOperations.java
@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2023 Contributors to the Eclipse Foundation
+ * Copyright (c) 2023,2024 Contributors to the Eclipse Foundation
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -20,6 +20,7 @@ import java.util.Collection;
 import java.util.stream.Stream;
 
 import jakarta.data.Limit;
+import jakarta.data.Order;
 import jakarta.data.Sort;
 
 /**
@@ -28,13 +29,13 @@ import jakarta.data.Sort;
  * @param <T> type of entity.
  */
 public interface IdOperations<T> {
-    Stream<T> findByIdBetween(long minimum, long maximum, Sort sort);
+    Stream<T> findByIdBetween(long minimum, long maximum, Sort<T> sort);
 
     Collection<T> findByIdGreaterThanEqual(long minimum,
                                            Limit limit,
-                                           Sort... sorts);
+                                           Order<T> sorts);
 
-    T[] findByIdLessThan(long exclusiveMax, Sort primarySort, Sort secondarySort);
+    T[] findByIdLessThan(long exclusiveMax, Sort<T> primarySort, Sort<T> secondarySort);
 
-    ArrayList<T> findByIdLessThanEqual(long maximum, Sort... sorts);
+    ArrayList<T> findByIdLessThanEqual(long maximum, Order<T> sorts);
 }
\ No newline at end of file
diff --git a/tck/src/main/java/ee/jakarta/tck/data/framework/read/only/NaturalNumbers.java b/tck/src/main/java/ee/jakarta/tck/data/framework/read/only/NaturalNumbers.java
index b5d38914..1656785e 100644
--- a/tck/src/main/java/ee/jakarta/tck/data/framework/read/only/NaturalNumbers.java
+++ b/tck/src/main/java/ee/jakarta/tck/data/framework/read/only/NaturalNumbers.java
@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2023 Contributors to the Eclipse Foundation
+ * Copyright (c) 2023,2024 Contributors to the Eclipse Foundation
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -18,7 +18,7 @@ package ee.jakarta.tck.data.framework.read.only;
 import java.util.stream.Stream;
 
 import jakarta.data.Limit;
-import jakarta.data.Sort;
+import jakarta.data.Order;
 import jakarta.data.page.KeysetAwareSlice;
 import jakarta.data.page.Pageable;
 import jakarta.data.page.Slice;
@@ -37,23 +37,23 @@ import ee.jakarta.tck.data.framework.read.only.NaturalNumber.NumberType;
 public interface NaturalNumbers extends BasicRepository<NaturalNumber, Long>, IdOperations<NaturalNumber> {
 
     KeysetAwareSlice<NaturalNumber> findByFloorOfSquareRootOrderByIdAsc(long sqrtFloor,
-                                                                        Pageable pagination);
+                                                                        Pageable<NaturalNumber> pagination);
 
     Stream<NaturalNumber> findByIdBetweenOrderByNumTypeAsc(long minimum,
                                                            long maximum,
-                                                           Sort... sorts);
+                                                           Order<NaturalNumber> sorts);
 
     Slice<NaturalNumber> findByIdLessThanOrderByFloorOfSquareRootDesc(long exclusiveMax,
-                                                                      Pageable pagination);
+                                                                      Pageable<NaturalNumber> pagination);
 
     KeysetAwareSlice<NaturalNumber> findByNumTypeAndNumBitsRequiredLessThan(NumberType type,
                                                                             short bitsUnder,
-                                                                            Pageable pagination);
+                                                                            Pageable<NaturalNumber> pagination);
 
-    NaturalNumber[] findByNumTypeNot(NumberType notThisType, Limit limit, Sort... sorts);
+    NaturalNumber[] findByNumTypeNot(NumberType notThisType, Limit limit, Order<NaturalNumber> sorts);
 
     Slice<NaturalNumber> findByNumTypeAndFloorOfSquareRootLessThanEqual(NumberType type,
                                                                         long maxSqrtFloor,
-                                                                        Pageable pagination);
+                                                                        Pageable<NaturalNumber> pagination);
 
 }
diff --git a/tck/src/main/java/ee/jakarta/tck/data/framework/read/only/PositiveIntegers.java b/tck/src/main/java/ee/jakarta/tck/data/framework/read/only/PositiveIntegers.java
index dce11143..659e6964 100644
--- a/tck/src/main/java/ee/jakarta/tck/data/framework/read/only/PositiveIntegers.java
+++ b/tck/src/main/java/ee/jakarta/tck/data/framework/read/only/PositiveIntegers.java
@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2023 Contributors to the Eclipse Foundation
+ * Copyright (c) 2023,2024 Contributors to the Eclipse Foundation
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -22,7 +22,7 @@ import java.util.stream.Stream;
 
 import ee.jakarta.tck.data.framework.read.only.NaturalNumber.NumberType;
 import jakarta.data.Limit;
-import jakarta.data.Sort;
+import jakarta.data.Order;
 import jakarta.data.Streamable;
 import jakarta.data.page.KeysetAwarePage;
 import jakarta.data.page.Page;
@@ -43,7 +43,7 @@ public interface PositiveIntegers extends PageableRepository<NaturalNumber, Long
 
     KeysetAwarePage<NaturalNumber> findByFloorOfSquareRootNotAndIdLessThanOrderByBitsRequiredDesc(long excludeSqrt,
                                                                                                   long eclusiveMax,
-                                                                                                  Pageable pagination);
+                                                                                                  Pageable<NaturalNumber> pagination);
 
     Iterable<NaturalNumber> findByIsOddTrueAndIdLessThanEqualOrderByIdDesc(long max);
 
@@ -53,9 +53,10 @@ public interface PositiveIntegers extends PageableRepository<NaturalNumber, Long
 
     Stream<NaturalNumber> findByNumTypeOrFloorOfSquareRoot(NumberType type, long floor);
 
-    Page<NaturalNumber> findMatching(long floorOfSquareRoot, Short numBitsRequired, NumberType numType, Pageable pagination);
+    Page<NaturalNumber> findMatching(long floorOfSquareRoot, Short numBitsRequired, NumberType numType,
+            Pageable<NaturalNumber> pagination);
 
     Optional<NaturalNumber> findNumber(long id);
 
-    List<NaturalNumber> findOdd(boolean isOdd, NumberType numType, Limit limit, Sort... sorts);
+    List<NaturalNumber> findOdd(boolean isOdd, NumberType numType, Limit limit, Order<NaturalNumber> sorts);
 }
\ No newline at end of file
diff --git a/tck/src/main/java/ee/jakarta/tck/data/framework/read/only/_AsciiChar.java b/tck/src/main/java/ee/jakarta/tck/data/framework/read/only/_AsciiChar.java
index f950b0b4..c400c06a 100644
--- a/tck/src/main/java/ee/jakarta/tck/data/framework/read/only/_AsciiChar.java
+++ b/tck/src/main/java/ee/jakarta/tck/data/framework/read/only/_AsciiChar.java
@@ -30,11 +30,11 @@ public class _AsciiChar {
     public static final String HEXADECIMAL = "hexadecimal";
     public static final String NUMERICVALUE = "numericValue";
 
-    public static volatile SortableAttribute id;
-    public static volatile TextAttribute hexadecimal;
+    public static volatile SortableAttribute<AsciiCharacter> id;
+    public static volatile TextAttribute<AsciiCharacter> hexadecimal;
     public static volatile Attribute isControl; // user decided it didn't care about sorting for this one
-    public static volatile SortableAttribute numericValue;
-    public static volatile TextAttribute thisCharacter;
+    public static volatile SortableAttribute<AsciiCharacter> numericValue;
+    public static volatile TextAttribute<AsciiCharacter> thisCharacter;
 
     // Avoids the checkstyle error,
     // HideUtilityClassConstructor: Utility classes should not have a public or default constructor
diff --git a/tck/src/main/java/ee/jakarta/tck/data/framework/read/only/_AsciiCharacter.java b/tck/src/main/java/ee/jakarta/tck/data/framework/read/only/_AsciiCharacter.java
index d7e3ca74..30a91b52 100644
--- a/tck/src/main/java/ee/jakarta/tck/data/framework/read/only/_AsciiCharacter.java
+++ b/tck/src/main/java/ee/jakarta/tck/data/framework/read/only/_AsciiCharacter.java
@@ -32,26 +32,28 @@ public class _AsciiCharacter {
     public static final String HEXADECIMAL = "hexadecimal";
     public static final String NUMERICVALUE = "numericValue";
 
-    public static final SortableAttribute id = new NumericAttr("id");
-    public static final TextAttribute hexadecimal = new TextAttr("hexadecimal");
-    public static final SortableAttribute isControl = new BooleanAttr("isControl");
-    public static final SortableAttribute numericValue = new NumericAttr("numericValue");
-    public static final TextAttribute thisCharacter = new TextAttr("thisCharacter");
+    public static final SortableAttribute<AsciiCharacter> id = new NumericAttr("id");
+    public static final TextAttribute<AsciiCharacter> hexadecimal = new TextAttr("hexadecimal");
+    public static final SortableAttribute<AsciiCharacter> isControl = new BooleanAttr("isControl");
+    public static final SortableAttribute<AsciiCharacter> numericValue = new NumericAttr("numericValue");
+    public static final TextAttribute<AsciiCharacter> thisCharacter = new TextAttr("thisCharacter");
 
-    private static record BooleanAttr(String name, Sort asc, Sort desc) implements SortableAttribute {
+    private static record BooleanAttr(String name, Sort<AsciiCharacter> asc, Sort<AsciiCharacter> desc)
+            implements SortableAttribute<AsciiCharacter> {
         private BooleanAttr(String name) {
             this(name, Sort.asc(name), Sort.desc(name));
         }
     };
 
-    private static record NumericAttr(String name, Sort asc, Sort desc) implements SortableAttribute {
+    private static record NumericAttr(String name, Sort<AsciiCharacter> asc, Sort<AsciiCharacter> desc)
+            implements SortableAttribute<AsciiCharacter> {
         private NumericAttr(String name) {
             this(name, Sort.asc(name), Sort.desc(name));
         }
     };
 
-    private static record TextAttr(String name, Sort asc, Sort ascIgnoreCase, Sort desc, Sort descIgnoreCase)
-                    implements TextAttribute {
+    private static record TextAttr(String name, Sort<AsciiCharacter> asc, Sort<AsciiCharacter> ascIgnoreCase,
+            Sort<AsciiCharacter> desc, Sort<AsciiCharacter> descIgnoreCase) implements TextAttribute<AsciiCharacter> {
         private TextAttr(String name) {
             this(name, Sort.asc(name), Sort.ascIgnoreCase(name), Sort.desc(name), Sort.descIgnoreCase(name));
         }
diff --git a/tck/src/main/java/ee/jakarta/tck/data/standalone/persistence/Catalog.java b/tck/src/main/java/ee/jakarta/tck/data/standalone/persistence/Catalog.java
index b4c009c5..8500a754 100644
--- a/tck/src/main/java/ee/jakarta/tck/data/standalone/persistence/Catalog.java
+++ b/tck/src/main/java/ee/jakarta/tck/data/standalone/persistence/Catalog.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023 Contributors to the Eclipse Foundation
+ * Copyright (c) 2023,2024 Contributors to the Eclipse Foundation
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -20,7 +20,7 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.stream.Stream;
 
-import jakarta.data.Sort;
+import jakarta.data.Order;
 import jakarta.data.Streamable;
 import jakarta.data.repository.DataRepository;
 import jakarta.data.repository.Delete;
@@ -75,7 +75,7 @@ public interface Catalog extends DataRepository<Product, String> {
 
     LinkedList<Product> findByDepartmentsEmpty();
 
-    Iterable<Product> findByIdBetween(String first, String last, Sort... sorts);
+    Iterable<Product> findByIdBetween(String first, String last, Order<Product> sorts);
 
     List<Product> findByNameLike(String name);
     


Test patch:
diff --git a/api/src/test/java/jakarta/data/SortTest.java b/api/src/test/java/jakarta/data/SortTest.java
index 7479b762..95d97c40 100644
--- a/api/src/test/java/jakarta/data/SortTest.java
+++ b/api/src/test/java/jakarta/data/SortTest.java
@@ -37,7 +37,7 @@ class SortTest {
     @Test
     @DisplayName("Should use ascending sort when direction is ASC")
     void shouldCreateAscendingSort() {
-        Sort order = Sort.of(NAME, Direction.ASC, false);
+        Sort<?> order = Sort.of(NAME, Direction.ASC, false);
 
         assertSoftly(softly -> {
             softly.assertThat(order).isNotNull();
@@ -51,7 +51,7 @@ class SortTest {
     @Test
     @DisplayName("Should descending short when direction is DESC")
     void shouldCreateDescendingSort() {
-        Sort order = Sort.of(NAME, Direction.DESC, true);
+        Sort<?> order = Sort.of(NAME, Direction.DESC, true);
 
         assertSoftly(softly -> {
             softly.assertThat(order).isNotNull();
@@ -65,7 +65,7 @@ class SortTest {
     @Test
     @DisplayName("Should ascending sort when Sort.asc method is used")
     void shouldCreateAsc() {
-        Sort order = Sort.asc("name");
+        Sort<?> order = Sort.asc("name");
 
         assertSoftly(softly -> {
             softly.assertThat(order).isNotNull();
@@ -79,7 +79,7 @@ class SortTest {
     @Test
     @DisplayName("Should use ascending sort ignoring case when Sort.ascIgnoreCase method is used")
     void shouldCreateAscIgnoreCase() {
-        Sort order = Sort.ascIgnoreCase("name");
+        Sort<?> order = Sort.ascIgnoreCase("name");
 
         assertSoftly(softly -> {
             softly.assertThat(order).isNotNull();
@@ -93,7 +93,7 @@ class SortTest {
     @Test
     @DisplayName("Should descending sort when Sort.desc method is used")
     void shouldCreateDesc() {
-        Sort order = Sort.desc(NAME);
+        Sort<?> order = Sort.desc(NAME);
 
         assertSoftly(softly -> {
             softly.assertThat(order).isNotNull();
@@ -107,7 +107,7 @@ class SortTest {
     @Test
     @DisplayName("Should use descending sort ignoring case when Sort.descIgnoreCase method is used")
     void shouldCreateDescIgnoreCase() {
-        Sort order = Sort.descIgnoreCase(NAME);
+        Sort<?> order = Sort.descIgnoreCase(NAME);
 
         assertSoftly(softly -> {
             softly.assertThat(order).isNotNull();
diff --git a/api/src/test/java/jakarta/data/page/KeysetPageableTest.java b/api/src/test/java/jakarta/data/page/KeysetPageableTest.java
index 4e51026b..0614efa7 100644
--- a/api/src/test/java/jakarta/data/page/KeysetPageableTest.java
+++ b/api/src/test/java/jakarta/data/page/KeysetPageableTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022,2023 Contributors to the Eclipse Foundation
+ * Copyright (c) 2022,2024 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.
@@ -34,7 +34,7 @@ class KeysetPageableTest {
     @Test
     @DisplayName("Should include keyset values in next Pageable")
     void shouldCreatePageableAfterKeyset() {
-        Pageable pageable = Pageable.ofSize(20).afterKeyset("First", 2L, 3);
+        Pageable<?> pageable = Pageable.ofSize(20).afterKeyset("First", 2L, 3);
 
         assertSoftly(softly -> {
             softly.assertThat(pageable.size()).isEqualTo(20);
@@ -51,7 +51,7 @@ class KeysetPageableTest {
     @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);
+        Pageable<?> pageable = Pageable.ofSize(35).sortBy(Sort.asc("name"), Sort.asc("id")).afterKeysetCursor(cursor);
 
         assertSoftly(softly -> {
             softly.assertThat(pageable.size()).isEqualTo(35);
@@ -67,7 +67,7 @@ class KeysetPageableTest {
     @Test
     @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").page(10);
+        Pageable<?> pageable = Pageable.ofSize(30).sortBy(Sort.desc("yearBorn"), Sort.asc("ssn")).beforeKeyset(1991, "123-45-6789").page(10);
 
         assertSoftly(softly -> {
             softly.assertThat(pageable.size()).isEqualTo(30);
@@ -84,7 +84,7 @@ class KeysetPageableTest {
     @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);
+        Pageable<?> pageable = Pageable.ofPage(8).beforeKeysetCursor(cursor);
 
         assertSoftly(softly -> {
             softly.assertThat(pageable.size()).isEqualTo(10);
@@ -103,12 +103,12 @@ class KeysetPageableTest {
     @Test
     @DisplayName("Should be usable in a hashing structure")
     void shouldHash() {
-        Pageable pageable1 = Pageable.ofSize(15).afterKeyset(1, '1', "1")
-                                           .sortBy(Sort.desc("yearHired"), Sort.asc("lastName"), Sort.asc("id"));
-        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<>();
+        Pageable<?> pageable1 = Pageable.ofSize(15).afterKeyset(1, '1', "1")
+                                              .sortBy(Sort.desc("yearHired"), Sort.asc("lastName"), Sort.asc("id"));
+        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());
@@ -127,12 +127,12 @@ class KeysetPageableTest {
     @Test
     @DisplayName("Should be displayable as String with toString")
     void shouldPageableDisplayAsString() {
-        Pageable pageable = Pageable.ofSize(200).afterKeyset("value1", 1);
+        Pageable<?> pageable = Pageable.ofSize(200).afterKeyset("value1", 1);
 
         assertSoftly(softly -> softly.assertThat(pageable.toString())
               .isEqualTo("Pageable{page=1, size=200, mode=CURSOR_NEXT, 2 keys}"));
 
-        Pageable pageableWithSorts = Pageable.ofSize(100).sortBy(Sort.desc("name"), Sort.asc("id"))
+        Pageable<?> pageableWithSorts = Pageable.ofSize(100).sortBy(Sort.desc("name"), Sort.asc("id"))
                                              .beforeKeyset("Item1", 3456);
 
         assertSoftly(softly -> softly.assertThat(pageableWithSorts.toString())
@@ -142,13 +142,13 @@ class KeysetPageableTest {
     @Test
     @DisplayName("Should return true from equals if keyset values and other properties are equal")
     void shouldBeEqualWithSameKeysetValues() {
-        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).size(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<?> 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).size(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);
@@ -203,9 +203,9 @@ class KeysetPageableTest {
     @Test
     @DisplayName("Keyset should be replaced on new instance of Pageable")
     void shouldReplaceKeyset() {
-        Pageable p1 = Pageable.ofSize(30).sortBy(Sort.asc("lastName"), Sort.asc("firstName"), Sort.asc("id"))
+        Pageable<?> p1 = Pageable.ofSize(30).sortBy(Sort.asc("lastName"), Sort.asc("firstName"), Sort.asc("id"))
                                          .afterKeyset("last1", "fname1", 100).page(12);
-        Pageable p2 = p1.beforeKeyset("lname2", "fname2", 200);
+        Pageable<?> p2 = p1.beforeKeyset("lname2", "fname2", 200);
 
         assertSoftly(softly -> {
             softly.assertThat(p1.mode()).isEqualTo(Pageable.Mode.CURSOR_NEXT);
diff --git a/api/src/test/java/jakarta/data/page/PageableTest.java b/api/src/test/java/jakarta/data/page/PageableTest.java
index a21b45b0..9538c15e 100644
--- a/api/src/test/java/jakarta/data/page/PageableTest.java
+++ b/api/src/test/java/jakarta/data/page/PageableTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022,2023 Contributors to the Eclipse Foundation
+ * Copyright (c) 2022,2024 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.
@@ -33,7 +33,7 @@ class PageableTest {
     @Test
     @DisplayName("Should correctly paginate")
     void shouldCreatePageable() {
-        Pageable pageable = Pageable.ofPage(2).size(6);
+        Pageable<?> pageable = Pageable.ofPage(2).size(6);
 
         assertSoftly(softly -> {
             softly.assertThat(pageable.page()).isEqualTo(2L);
@@ -44,7 +44,7 @@ class PageableTest {
     @Test
     @DisplayName("Should create pageable with size")
     void shouldCreatePageableWithSize() {
-        Pageable pageable = Pageable.ofSize(50);
+        Pageable<?> pageable = Pageable.ofSize(50);
 
         assertSoftly(softly -> {
             softly.assertThat(pageable.page()).isEqualTo(1L);
@@ -55,8 +55,8 @@ class PageableTest {
     @Test
     @DisplayName("Should navigate next")
     void shouldNext() {
-        Pageable pageable = Pageable.ofSize(1).page(2);
-        Pageable next = pageable.next();
+        Pageable<?> pageable = Pageable.ofSize(1).page(2);
+        Pageable<?> next = pageable.next();
 
         assertSoftly(softly -> {
             softly.assertThat(pageable.page()).isEqualTo(2L);
@@ -69,7 +69,7 @@ class PageableTest {
     @Test
     @DisplayName("Should create a new Pageable at the given page with a default size of 10")
     void shouldCreatePage() {
-        Pageable pageable = Pageable.ofPage(5);
+        Pageable<?> pageable = Pageable.ofPage(5);
 
         assertSoftly(softly -> {
             softly.assertThat(pageable.page()).isEqualTo(5L);
@@ -91,7 +91,7 @@ class PageableTest {
     @Test
     @DisplayName("Should throw IllegalArgumentException when page is not present")
     void shouldReturnErrorWhenThereIsIllegalArgument() {
-        Pageable p1 = Pageable.ofPage(1);
+        Pageable<?> p1 = Pageable.ofPage(1);
 
         assertThatIllegalArgumentException().isThrownBy(() -> Pageable.ofPage(0));
         assertThatIllegalArgumentException().isThrownBy(() -> Pageable.ofPage(-1));
@@ -103,21 +103,18 @@ class PageableTest {
 
     @Test
     public void shouldHaveEmptySortListWhenSortIsNullOrEmpty() {
-        Pageable p = Pageable.ofSize(2).sortBy(Sort.asc("Id"));
+        Pageable<Object> p = Pageable.ofSize(2).sortBy(Sort.asc("Id"));
 
         assertSoftly(softly -> {
             softly.assertThat(p.sorts()).isEqualTo(List.of(Sort.asc("Id")));
-            softly.assertThat(p.sortBy().sorts()).isEqualTo(Collections.EMPTY_LIST);
-            softly.assertThat(p.sortBy((Iterable<Sort>) null).sorts()).isEqualTo(Collections.EMPTY_LIST);
-            softly.assertThat(p.sortBy((Sort[]) null).sorts()).isEqualTo(Collections.EMPTY_LIST);
-            softly.assertThat(p.sortBy().sorts()).isEqualTo(Collections.EMPTY_LIST);
+            softly.assertThat(p.sortBy((Iterable<Sort<Object>>) null).sorts()).isEqualTo(Collections.EMPTY_LIST);
             softly.assertThat(p.sortBy(List.of()).sorts()).isEqualTo(Collections.EMPTY_LIST);
         });
     }
 
     @Test
     void shouldCreatePageableSort() {
-        Pageable pageable = Pageable.ofSize(3).sortBy(Sort.asc("name"));
+        Pageable<?> pageable = Pageable.ofSize(3).sortBy(Sort.asc("name"));
 
         assertSoftly(softly -> {
             softly.assertThat(pageable).isNotNull();
@@ -131,8 +128,8 @@ class PageableTest {
     @DisplayName("Should expect UnsupportedOperationException when sort is modified")
     void shouldNotModifySort() {
         assertThatThrownBy( () -> {
-            Pageable pageable = Pageable.ofSize(3).sortBy(Sort.asc("name"));
-            List<Sort> sorts = pageable.sorts();
+            Pageable<Object> pageable = Pageable.ofSize(3).sortBy(Sort.asc("name"));
+            List<Sort<Object>> sorts = pageable.sorts();
 
             sorts.clear();
         }).isInstanceOf(UnsupportedOperationException.class);
@@ -141,8 +138,8 @@ class PageableTest {
     @Test
     @DisplayName("Should not modify sort on next page")
     void shouldNotModifySortOnNextPage() {
-        Pageable pageable = Pageable.ofSize(3).sortBy(Sort.asc("name"), Sort.desc("age"));
-        Pageable next = pageable.next();
+        Pageable<?> pageable = Pageable.ofSize(3).sortBy(Sort.asc("name"), Sort.desc("age"));
+        Pageable<?> next = pageable.next();
 
         assertSoftly(softly -> {
             softly.assertThat(pageable.page()).isEqualTo(1);
@@ -160,8 +157,8 @@ class PageableTest {
     @Test
     @DisplayName("Page number should be replaced on new instance of Pageable")
     void shouldReplacePage() {
-        Pageable p6 = Pageable.ofSize(75).page(6).sortBy(Sort.desc("price"));
-        Pageable p7 = p6.page(7);
+        Pageable<?> p6 = Pageable.ofSize(75).page(6).sortBy(Sort.desc("price"));
+        Pageable<?> p7 = p6.page(7);
 
         assertSoftly(softly -> {
             softly.assertThat(p7.page()).isEqualTo(7L);
@@ -176,8 +173,8 @@ class PageableTest {
     @Test
     @DisplayName("Size should be replaced on new instance of Pageable")
     void shouldReplaceSize() {
-        Pageable s90 = Pageable.ofPage(4).size(90);
-        Pageable s80 = s90.size(80);
+        Pageable<?> s90 = Pageable.ofPage(4).size(90);
+        Pageable<?> s80 = s90.size(80);
 
         assertSoftly(softly -> {
             softly.assertThat(s80.size()).isEqualTo(80);
@@ -190,8 +187,8 @@ class PageableTest {
     @Test
     @DisplayName("Sorts should be replaced on new instance of Pageable")
     void shouldReplaceSorts() {
-        Pageable p1 = Pageable.ofSize(55).sortBy(Sort.desc("lastName"), Sort.asc("firstName"));
-        Pageable p2 = p1.sortBy(Sort.asc("firstName"), Sort.asc("lastName"));
+        Pageable<?> p1 = Pageable.ofSize(55).sortBy(Sort.desc("lastName"), Sort.asc("firstName"));
+        Pageable<?> p2 = p1.sortBy(Sort.asc("firstName"), Sort.asc("lastName"));
 
         assertSoftly(softly -> {
             softly.assertThat(p1.sorts()).isEqualTo(List.of(Sort.desc("lastName"), Sort.asc("firstName")));
@@ -202,4 +199,76 @@ class PageableTest {
             softly.assertThat(p2.size()).isEqualTo(55);
         });
     }
+
+    @Test
+    @DisplayName("Sorts should be appended by the Pageable.asc method")
+    void shouldAppendAscendingSort() {
+        Pageable<?> p1 = Pageable.ofSize(50).asc("first");
+        Pageable<?> p2 = p1.asc("second");
+        Pageable<?> p3 = p2.asc("third");
+
+        assertSoftly(softly -> {
+            softly.assertThat(p1.sorts()).isEqualTo(
+                    List.of(Sort.asc("first")));
+            softly.assertThat(p2.sorts()).isEqualTo(
+                    List.of(Sort.asc("first"), Sort.asc("second")));
+            softly.assertThat(p3.sorts()).isEqualTo(
+                    List.of(Sort.asc("first"), Sort.asc("second"), Sort.asc("third")));
+            softly.assertThat(p3.size()).isEqualTo(50);
+        });
+    }
+
+    @Test
+    @DisplayName("Sorts should be appended by the Pageable.ascIgnoreCase method")
+    void shouldAppendCaseInsensitiveAscendingSort() {
+        Pageable<?> p1 = Pageable.ofSize(40).ascIgnoreCase("first");
+        Pageable<?> p2 = p1.ascIgnoreCase("second");
+        Pageable<?> p3 = p2.ascIgnoreCase("third");
+
+        assertSoftly(softly -> {
+            softly.assertThat(p1.sorts()).isEqualTo(
+                    List.of(Sort.ascIgnoreCase("first")));
+            softly.assertThat(p2.sorts()).isEqualTo(
+                    List.of(Sort.ascIgnoreCase("first"), Sort.ascIgnoreCase("second")));
+            softly.assertThat(p3.sorts()).isEqualTo(
+                    List.of(Sort.ascIgnoreCase("first"), Sort.ascIgnoreCase("second"), Sort.ascIgnoreCase("third")));
+            softly.assertThat(p3.size()).isEqualTo(40);
+        });
+    }
+
+    @Test
+    @DisplayName("Sorts should be appended by the Pageable.descIgnoreCase method")
+    void shouldAppendCaseInsensitiveDescendingSort() {
+        Pageable<?> p1 = Pageable.ofSize(30).descIgnoreCase("first");
+        Pageable<?> p2 = p1.descIgnoreCase("second");
+        Pageable<?> p3 = p2.descIgnoreCase("third");
+
+        assertSoftly(softly -> {
+            softly.assertThat(p1.sorts()).isEqualTo(
+                    List.of(Sort.descIgnoreCase("first")));
+            softly.assertThat(p2.sorts()).isEqualTo(
+                    List.of(Sort.descIgnoreCase("first"), Sort.descIgnoreCase("second")));
+            softly.assertThat(p3.sorts()).isEqualTo(
+                    List.of(Sort.descIgnoreCase("first"), Sort.descIgnoreCase("second"), Sort.descIgnoreCase("third")));
+            softly.assertThat(p3.size()).isEqualTo(30);
+        });
+    }
+
+    @Test
+    @DisplayName("Sorts should be appended by the Pageable.desc method")
+    void shouldAppendDescendingSort() {
+        Pageable<?> p1 = Pageable.ofSize(20).desc("first");
+        Pageable<?> p2 = p1.desc("second");
+        Pageable<?> p3 = p2.desc("third");
+
+        assertSoftly(softly -> {
+            softly.assertThat(p1.sorts()).isEqualTo(
+                    List.of(Sort.desc("first")));
+            softly.assertThat(p2.sorts()).isEqualTo(
+                    List.of(Sort.desc("first"), Sort.desc("second")));
+            softly.assertThat(p3.sorts()).isEqualTo(
+                    List.of(Sort.desc("first"), Sort.desc("second"), Sort.desc("third")));
+            softly.assertThat(p3.size()).isEqualTo(20);
+        });
+    }
 }
diff --git a/api/src/test/java/jakarta/data/page/PaginationTest.java b/api/src/test/java/jakarta/data/page/PaginationTest.java
index fd609b94..e657826d 100644
--- a/api/src/test/java/jakarta/data/page/PaginationTest.java
+++ b/api/src/test/java/jakarta/data/page/PaginationTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023 Contributors to the Eclipse Foundation
+ * Copyright (c) 2023,2024 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.
@@ -32,14 +32,14 @@ class PaginationTest {
     void shouldThrowExceptionWhenNoKeysetValuesWereProvided() {
         assertThatIllegalArgumentException()
                 .as("Mode must be different from OFFSET when cursor is null")
-                .isThrownBy(() -> new Pagination(1, 10, Collections.emptyList(), Pageable.Mode.CURSOR_NEXT, null));
+                .isThrownBy(() -> new Pagination<>(1, 10, Collections.emptyList(), Pageable.Mode.CURSOR_NEXT, null));
     }
 
     @Test
     @DisplayName("Should throw UnsupportedOperationException when keyset is not supported")
     void shouldThrowExceptionWhenKeysetIsNotSupported() {
         assertThatThrownBy(() -> {
-            Pagination pagination = new Pagination(1, 10, Collections.emptyList(), Pageable.Mode.CURSOR_NEXT, new KeysetCursor("me", 200));
+            Pagination<?> pagination = new Pagination<>(1, 10, Collections.emptyList(), Pageable.Mode.CURSOR_NEXT, new KeysetCursor("me", 200));
             pagination.next();
         }).isInstanceOf(UnsupportedOperationException.class)
                 .hasMessage("Not supported for keyset pagination. Instead use afterKeyset or afterKeysetCursor to provide the next keyset values or obtain the nextPageable from a KeysetAwareSlice.");
diff --git a/tck/src/main/java/ee/jakarta/tck/data/standalone/entity/EntityTests.java b/tck/src/main/java/ee/jakarta/tck/data/standalone/entity/EntityTests.java
index 68e83cb6..1b6f6434 100644
--- a/tck/src/main/java/ee/jakarta/tck/data/standalone/entity/EntityTests.java
+++ b/tck/src/main/java/ee/jakarta/tck/data/standalone/entity/EntityTests.java
@@ -56,6 +56,7 @@ import ee.jakarta.tck.data.framework.read.only.PositiveIntegers;
 import ee.jakarta.tck.data.framework.read.only.NaturalNumber.NumberType;
 import ee.jakarta.tck.data.framework.utilities.TestPropertyUtility;
 import jakarta.data.Limit;
+import jakarta.data.Order;
 import jakarta.data.Sort;
 import jakarta.data.Streamable;
 import jakarta.data.exceptions.EmptyResultException;
@@ -401,7 +402,7 @@ public class EntityTests {
 
     @Assertion(id = "133", strategy = "Request a Page higher than the final Page, expecting an empty Page with 0 results.")
     public void testBeyondFinalPage() {
-        Pageable sixth = Pageable.ofPage(6).sortBy(Sort.asc("numericValue")).size(10);
+        Pageable<AsciiCharacter> sixth = Order.by(_AsciiCharacter.numericValue.asc()).page(6).size(10);
         Page<AsciiCharacter> page;
         try {
             page = characters.findByNumericValueBetween(48, 90, sixth);
@@ -420,7 +421,7 @@ public class EntityTests {
 
     @Assertion(id = "133", strategy = "Request a Slice higher than the final Slice, expecting an empty Slice with 0 results.")
     public void testBeyondFinalSlice() {
-        Pageable sixth = Pageable.ofSize(5).sortBy(Sort.desc("id")).page(6);
+        Pageable<NaturalNumber> sixth = Pageable.of(NaturalNumber.class).size(5).sortBy(Sort.desc("id")).page(6);
         Slice<NaturalNumber> slice = numbers.findByNumTypeAndFloorOfSquareRootLessThanEqual(NumberType.PRIME, 8L,
                 sixth);
         assertEquals(0, slice.numberOfElements());
@@ -453,13 +454,13 @@ public class EntityTests {
                                      .collect(Collectors.toList()));
 
         assertEquals(List.of('D', 'E', 'F', 'G', 'H'),
-                     characters.findByIdGreaterThanEqual(68L, Limit.of(5), Sort.asc("numericValue"), Sort.asc("id"))
+                     characters.findByIdGreaterThanEqual(68L, Limit.of(5), Order.by(Sort.asc("numericValue"), Sort.asc("id")))
                                      .stream()
                                      .map(AsciiCharacter::getThisCharacter)
                                      .collect(Collectors.toList()));
 
         assertEquals(List.of(71L, 73L, 79L, 83L, 89L),
-                     numbers.findByIdGreaterThanEqual(68L, Limit.of(5), Sort.asc("numType"), Sort.asc("id"))
+                     numbers.findByIdGreaterThanEqual(68L, Limit.of(5), Order.by(Sort.asc("numType"), Sort.asc("id")))
                                      .stream()
                                      .map(NaturalNumber::getId)
                                      .collect(Collectors.toList()));
@@ -542,7 +543,7 @@ public class EntityTests {
 
     @Assertion(id = "133", strategy = "Request the last Page of up to 10 results, expecting to find the final 3.")
     public void testFinalPageOfUpTo10() {
-        Pageable fifthPageRequest = Pageable.ofSize(10).page(5).sortBy(Sort.asc("numericValue"));
+        Pageable<AsciiCharacter> fifthPageRequest = Order.by(_AsciiCharacter.numericValue.asc()).pageSize(10).page(5);
         Page<AsciiCharacter> page;
         try {
             page = characters.findByNumericValueBetween(48, 90, fifthPageRequest); // 'X' to 'Z'
@@ -590,7 +591,7 @@ public class EntityTests {
 
     @Assertion(id = "133", strategy = "Request the last Slice of up to 5 results, expecting to find the final 2.")
     public void testFinalSliceOfUpTo5() {
-        Pageable fifth = Pageable.ofSize(5).page(5).sortBy(Sort.desc("id"));
+        Pageable<NaturalNumber> fifth = Pageable.of(NaturalNumber.class).size(5).page(5).sortBy(Sort.desc("id"));
         Slice<NaturalNumber> slice = numbers.findByNumTypeAndFloorOfSquareRootLessThanEqual(NumberType.PRIME, 8L,
                 fifth);
         assertEquals(true, slice.hasContent());
@@ -626,7 +627,8 @@ public class EntityTests {
                           "ascending and descending sort. Verify the page contains all 12 expected entities, " +
                           "sorted according to the mixture of ascending and descending sort orders specified.")
     public void testFindAllWithPagination() {
-        Pageable page2request = Pageable.ofPage(2).size(12).sortBy(Sort.asc("floorOfSquareRoot"), Sort.desc("id"));
+        Pageable<NaturalNumber> page2request = Pageable.of(NaturalNumber.class).page(2).size(12)
+                .sortBy(Sort.asc("floorOfSquareRoot"), Sort.desc("id"));
         Page<NaturalNumber> page2 = positives.findAll(page2request);
 
         assertEquals(12, page2.numberOfElements());
@@ -667,9 +669,9 @@ public class EntityTests {
     public void testFindList() {
         List<NaturalNumber> oddCompositeNumbers = positives.findOdd(true, NumberType.COMPOSITE,
                                                                     Limit.of(10),
-                                                                    Sort.asc("floorOfSquareRoot"),
-                                                                    Sort.desc("numBitsRequired"),
-                                                                    Sort.asc("id"));
+                                                                    Order.by(Sort.asc("floorOfSquareRoot"),
+                                                                               Sort.desc("numBitsRequired"),
+                                                                               Sort.asc("id")));
         assertEquals(List.of(9L, 15L,  // 3 <= sqrt < 4, 4 bits
                              21L,      // 4 <= sqrt < 5, 5 bits
                              33L, 35L, // 5 <= sqrt < 6, 6 bits
@@ -681,7 +683,7 @@ public class EntityTests {
                                      .map(NaturalNumber::getId)
                                      .collect(Collectors.toList()));
 
-        List<NaturalNumber> evenPrimeNumbers = positives.findOdd(false, NumberType.PRIME, Limit.of(9));
+        List<NaturalNumber> evenPrimeNumbers = positives.findOdd(false, NumberType.PRIME, Limit.of(9), Order.by());
 
         assertEquals(1, evenPrimeNumbers.size());
         NaturalNumber num = evenPrimeNumbers.get(0);
@@ -723,7 +725,7 @@ public class EntityTests {
     @Assertion(id = "133",
                strategy = "Find a page of entities, with entity attributes identified by the parameter names and matching the parameter values.")
     public void testFindPage() {
-        Pageable page1Request = Pageable.ofSize(7).sortBy(Sort.desc("id"));
+        Pageable<NaturalNumber> page1Request = Pageable.of(NaturalNumber.class).size(7).sortBy(Sort.desc("id"));
 
         Page<NaturalNumber> page1 = positives.findMatching(9L, Short.valueOf((short) 7), NumberType.COMPOSITE,
                                                            page1Request);
@@ -756,7 +758,7 @@ public class EntityTests {
         // 4, 5, 6, 7, 8 square root rounds down to 2
         // 1, 2, 3 square root rounds down to 1
 
-        Pageable first8 = Pageable.ofSize(8).sortBy(Sort.asc("id"));
+        Pageable<NaturalNumber> first8 = Pageable.of(NaturalNumber.class).size(8).sortBy(Sort.asc("id"));
         KeysetAwarePage<NaturalNumber> page;
 
         try {
@@ -804,7 +806,7 @@ public class EntityTests {
                           "then request the next KeysetAwareSlice and the KeysetAwareSlice after that, " +
                           "expecting to find all results.")
     public void testFirstKeysetAwareSliceOf6AndNextSlices() {
-        Pageable first6 = Pageable.ofSize(6);
+        Pageable<NaturalNumber> first6 = Pageable.ofSize(6);
         KeysetAwareSlice<NaturalNumber> slice;
 
         try {
@@ -851,7 +853,7 @@ public class EntityTests {
                strategy = "Request the first Page of 10 results, expecting to find all 10. " +
                           "From the Page, verify the totalElements and totalPages expected.")
     public void testFirstPageOf10() {
-        Pageable first10 = Pageable.ofSize(10).sortBy(Sort.asc("numericValue"));
+        Pageable<AsciiCharacter> first10 = Order.by(_AsciiCharacter.numericValue.asc()).pageSize(10);
         Page<AsciiCharacter> page;
         try {
             page = characters.findByNumericValueBetween(48, 90, first10); // '0' to 'Z'
@@ -875,7 +877,7 @@ public class EntityTests {
 
     @Assertion(id = "133", strategy = "Request the first Slice of 5 results, expecting to find all 5.")
     public void testFirstSliceOf5() {
-        Pageable first5 = Pageable.ofSize(5).sortBy(Sort.desc("id"));
+        Pageable<NaturalNumber> first5 = Pageable.of(NaturalNumber.class).size(5).sortBy(Sort.desc("id"));
         Slice<NaturalNumber> slice = numbers.findByNumTypeAndFloorOfSquareRootLessThanEqual(NumberType.PRIME, 8L,
                 first5);
         assertEquals(5, slice.numberOfElements());
@@ -939,7 +941,7 @@ public class EntityTests {
     public void testIgnoreCase() {
         Stream<AsciiCharacter> found = characters.findByHexadecimalIgnoreCaseBetweenAndHexadecimalNotIn("4c", "5A",
                                                                                                         Set.of("5"),
-                                                                                                        Sort.asc("hexadecimal"));
+                                                                                                        Order.by(Sort.asc("hexadecimal")));
 
         assertEquals(List.of(Character.valueOf('L'), // 4c
                              Character.valueOf('M'), // 4d
@@ -971,7 +973,7 @@ public class EntityTests {
         //                                        ^^^ previous page ^^
         //                                                                                  ^^^^^ next page ^^^^
 
-        Pageable middle7 = Pageable.ofSize(7)
+        Pageable<NaturalNumber> middle7 = Pageable.of(NaturalNumber.class).size(7)
                         .sortBy(Sort.desc("numBitsRequired"), Sort.asc("floorOfSquareRoot"), Sort.desc("id"))
                         .afterKeyset((short) 5, 5L, 26L); // 20th result is 26; it requires 5 bits and its square root rounds down to 5.
 
@@ -1058,7 +1060,7 @@ public class EntityTests {
         //                                  ^^^^^^^^ slice 2 ^^^^^^^^^
         //                                                                                        ^^^^^^^^ slice 3 ^^^^^^^^^
 
-        Pageable middle9 = Pageable.ofSize(9)
+        Pageable<NaturalNumber> middle9 = Pageable.of(NaturalNumber.class).size(9)
                              .sortBy(Sort.desc("floorOfSquareRoot"), Sort.asc("id"))
                              .afterKeyset(6L, 46L); // 20th result is 46; its square root rounds down to 6.
 
@@ -1112,7 +1114,7 @@ public class EntityTests {
     @Assertion(id = "133", strategy = "Request a KeysetAwareSlice of results where none match the query, expecting an empty KeysetAwareSlice with 0 results.")
     public void testKeysetAwareSliceOfNothing() {
         // There are no numbers larger than 30 which have a square root that rounds down to 3.
-        Pageable pagination = Pageable.ofSize(33).afterKeyset(30L);
+        Pageable<NaturalNumber> pagination = Pageable.of(NaturalNumber.class).size(33).afterKeyset(30L);
 
         KeysetAwareSlice<NaturalNumber> slice;
         try {
@@ -1140,8 +1142,8 @@ public class EntityTests {
     public void testLimit() {
         Collection<NaturalNumber> nums = numbers.findByIdGreaterThanEqual(60L,
                                                         Limit.of(10),
-                                                        Sort.asc("floorOfSquareRoot"),
-                                                        Sort.desc("id"));
+                                                        Order.by(Sort.asc("floorOfSquareRoot"),
+                                                                   Sort.desc("id")));
 
         assertEquals(Arrays.toString(new Long[] { 63L, 62L, 61L, 60L, // square root rounds down to 7
                                 80L, 79L, 78L, 77L, 76L, 75L }), // square root rounds down to 8
@@ -1159,8 +1161,8 @@ public class EntityTests {
 
         Collection<NaturalNumber> nums = numbers.findByIdGreaterThanEqual(40L,
                                                         Limit.range(6, 10),
-                                                        Sort.asc("numType"), // primes first
-                                                        Sort.asc("id"));
+                                                        Order.by(Sort.asc("numType"), // primes first
+                                                                   Sort.asc("id")));
 
         assertEquals(Arrays.toString(new Long[] { 61L, 67L, 71L, 73L, 79L }),
         Arrays.toString(nums.stream().map(number -> number.getId()).toArray()));
@@ -1169,7 +1171,7 @@ public class EntityTests {
     @Assertion(id = "133", strategy = "Use a repository method with Limit and verify that the Limit caps " +
                                       "the number of results to the amount that is specified.")
     public void testLimitToOneResult() {
-        Collection<NaturalNumber> nums = numbers.findByIdGreaterThanEqual(80L, Limit.of(1));
+        Collection<NaturalNumber> nums = numbers.findByIdGreaterThanEqual(80L, Limit.of(1), Order.by());
 
         Iterator<NaturalNumber> it = nums.iterator();
         assertEquals(true, it.hasNext());
@@ -1209,7 +1211,7 @@ public class EntityTests {
 
     @Assertion(id = "133", strategy = "Use a repository method with the Not keyword.")
     public void testNot() {
-        NaturalNumber[] n = numbers.findByNumTypeNot(NumberType.COMPOSITE, Limit.of(8), Sort.asc("id"));
+        NaturalNumber[] n = numbers.findByNumTypeNot(NumberType.COMPOSITE, Limit.of(8), Order.by(Sort.asc("id")));
         assertEquals(8, n.length);
         assertEquals(1L, n[0].getId());
         assertEquals(2L, n[1].getId());
@@ -1241,7 +1243,7 @@ public class EntityTests {
                           "verfying that all results are returned and are ordered first by the static sort criteria, " +
                           "followed by the dynamic sort criteria when the value(s) being compared by the static criteria match.")
     public void testOrderByHasPrecedenceOverPageableSorts() {
-        Pageable pagination = Pageable.ofSize(8).sortBy(Sort.asc("numType"), Sort.desc("id"));
+        Pageable<NaturalNumber> pagination = Pageable.of(NaturalNumber.class).size(8).sortBy(Sort.asc("numType"), Sort.desc("id"));
         Slice<NaturalNumber> slice = numbers.findByIdLessThanOrderByFloorOfSquareRootDesc(25L, pagination);
 
         assertEquals(Arrays.toString(new Long[] { 23L, 19L, 17L, // square root rounds down to 4; prime
@@ -1278,8 +1280,8 @@ public class EntityTests {
                           "followed by the dynamic sort criteria when the value(s) being compared by the static criteria match.")
     public void testOrderByHasPrecedenceOverSorts() {
         Stream<NaturalNumber> nums = numbers.findByIdBetweenOrderByNumTypeAsc(5L, 24L,
-                                                                              Sort.desc("floorOfSquareRoot"),
-                                                                              Sort.asc("id"));
+                                                                              Order.by(Sort.desc("floorOfSquareRoot"),
+                                                                                       Sort.asc("id")));
 
         assertEquals(Arrays.toString(new Long[] { 17L, 19L, 23L, // prime; square root rounds down to 4
                                                   11L, 13L, // prime; square root rounds down to 3
@@ -1292,7 +1294,7 @@ public class EntityTests {
 
     @Assertion(id = "133", strategy = "Request a Page of results where none match the query, expecting an empty Page with 0 results.")
     public void testPageOfNothing() {
-        Pageable pagination = Pageable.ofSize(6).sortBy(Sort.asc("id"));
+        Pageable<AsciiCharacter> pagination = Order.by(_AsciiCharacter.id.asc()).pageSize(6);
         Page<AsciiCharacter> page;
         try {
             page = characters.findByNumericValueBetween(150, 160, pagination);
@@ -1331,7 +1333,7 @@ public class EntityTests {
 
     @Assertion(id = "133", strategy = "Request a Slice of results where none match the query, expecting an empty Slice with 0 results.")
     public void testSliceOfNothing() {
-        Pageable pagination = Pageable.ofSize(5).sortBy(Sort.desc("id"));
+        Pageable<NaturalNumber> pagination =  Pageable.of(NaturalNumber.class).size(5).sortBy(Sort.desc("id"));
         Slice<NaturalNumber> slice = numbers.findByNumTypeAndFloorOfSquareRootLessThanEqual(NumberType.COMPOSITE, 1L,
                 pagination);
 
@@ -1346,7 +1348,7 @@ public class EntityTests {
         assertEquals(Sort.ascIgnoreCase(_AsciiChar.HEXADECIMAL), _AsciiChar.hexadecimal.ascIgnoreCase());
         assertEquals(Sort.ascIgnoreCase("thisCharacter"), _AsciiChar.thisCharacter.ascIgnoreCase());
 
-        Pageable pageRequest = Pageable.ofSize(6).sortBy(_AsciiChar.numericValue.asc());
+        Pageable<AsciiCharacter> pageRequest = Order.by(_AsciiChar.numericValue.asc()).pageSize(6);
         Page<AsciiCharacter> page1 = characters.findByNumericValueBetween(68, 90, pageRequest);
 
         assertEquals(List.of('D', 'E', 'F', 'G', 'H', 'I'),
@@ -1362,7 +1364,7 @@ public class EntityTests {
         assertEquals(Sort.ascIgnoreCase(_AsciiCharacter.HEXADECIMAL), _AsciiCharacter.hexadecimal.ascIgnoreCase());
         assertEquals(Sort.ascIgnoreCase("thisCharacter"), _AsciiCharacter.thisCharacter.ascIgnoreCase());
 
-        Pageable pageRequest = Pageable.ofSize(7).sortBy(_AsciiCharacter.numericValue.asc());
+        Pageable<AsciiCharacter> pageRequest = Order.by(_AsciiCharacter.numericValue.asc()).pageSize(7);
         Page<AsciiCharacter> page1 = characters.findByNumericValueBetween(100, 122, pageRequest);
 
         assertEquals(List.of('d', 'e', 'f', 'g', 'h', 'i', 'j'),
@@ -1395,7 +1397,7 @@ public class EntityTests {
         assertEquals(Sort.descIgnoreCase("hexadecimal"), _AsciiChar.hexadecimal.descIgnoreCase());
         assertEquals(Sort.descIgnoreCase("thisCharacter"), _AsciiChar.thisCharacter.descIgnoreCase());
 
-        Sort sort = _AsciiChar.numericValue.desc();
+        Sort<AsciiCharacter> sort = _AsciiChar.numericValue.desc();
         AsciiCharacter[] found = characters.findFirst3ByNumericValueGreaterThanEqualAndHexadecimalEndsWith(30, "1", sort);
         assertEquals(3, found.length);
         assertEquals('q', found[0].getThisCharacter());
@@ -1410,7 +1412,7 @@ public class EntityTests {
         assertEquals(Sort.descIgnoreCase("hexadecimal"), _AsciiCharacter.hexadecimal.descIgnoreCase());
         assertEquals(Sort.descIgnoreCase("thisCharacter"), _AsciiCharacter.thisCharacter.descIgnoreCase());
 
-        Sort sort = _AsciiCharacter.numericValue.desc();
+        Sort<AsciiCharacter> sort = _AsciiCharacter.numericValue.desc();
         AsciiCharacter[] found = characters.findFirst3ByNumericValueGreaterThanEqualAndHexadecimalEndsWith(30, "4", sort);
         assertEquals(3, found.length);
         assertEquals('t', found[0].getThisCharacter());
@@ -1450,7 +1452,7 @@ public class EntityTests {
                strategy = "Request the third Page of 10 results, expecting to find all 10. " +
                           "Request the next Page via nextPageable, expecting page number 4 and another 10 results.")
     public void testThirdAndFourthPagesOf10() {
-        Pageable third10 = Pageable.ofPage(3).size(10).sortBy(Sort.asc("numericValue"));
+        Pageable<AsciiCharacter> third10 = Order.by(_AsciiCharacter.numericValue.asc()).page(3).size(10);
         Page<AsciiCharacter> page;
         try {
             page = characters.findByNumericValueBetween(48, 90, third10); // 'D' to 'M'
@@ -1471,7 +1473,7 @@ public class EntityTests {
                                      .map(c -> c.getHexadecimal() + ':' + c.getThisCharacter() + ';')
                                      .reduce("", String::concat));
 
-        Pageable fourth10 = third10.next();
+        Pageable<AsciiCharacter> fourth10 = third10.next();
         page = characters.findByNumericValueBetween(48, 90, fourth10); // 'N' to 'W'
 
         assertEquals(4, page.pageable().page());
@@ -1490,7 +1492,7 @@ public class EntityTests {
                strategy = "Request the third Slice of 5 results, expecting to find all 5. "
                        +  "Request the next Slice via nextPageable, expecting page number 4 and another 5 results.")
     public void testThirdAndFourthSlicesOf5() {
-        Pageable third5 = Pageable.ofPage(3).size(5).sortBy(Sort.desc("id"));
+        Pageable<NaturalNumber> third5 = Pageable.of(NaturalNumber.class).page(3).size(5).sortBy(Sort.desc("id"));
         Slice<NaturalNumber> slice = numbers.findByNumTypeAndFloorOfSquareRootLessThanEqual(NumberType.PRIME, 8L,
                 third5);
 
@@ -1500,7 +1502,7 @@ public class EntityTests {
         assertEquals(Arrays.toString(new Long[] { 37L, 31L, 29L, 23L, 19L }),
                 Arrays.toString(slice.stream().map(number -> number.getId()).toArray()));
 
-        Pageable fourth5 = third5.next();
+        Pageable<NaturalNumber> fourth5 = third5.next();
 
         slice = numbers.findByNumTypeAndFloorOfSquareRootLessThanEqual(NumberType.PRIME, 8L, fourth5);
 
@@ -1538,10 +1540,10 @@ public class EntityTests {
                strategy = "Use a repository method with varargs Sort... specifying a mixture of ascending and descending order, " +
                           "and verify all results are returned and are ordered according to the sort criteria.")
     public void testVarargsSort() {
-        List<NaturalNumber> list = numbers.findByIdLessThanEqual(12L,
+        List<NaturalNumber> list = numbers.findByIdLessThanEqual(12L, Order.by(
                                                                  Sort.asc("floorOfSquareRoot"),
                                                                  Sort.desc("numBitsRequired"),
-                                                                 Sort.asc("id"));
+                                                                 Sort.asc("id")));
 
         assertEquals(Arrays.toString(new Long[] { 2L, 3L, // square root rounds down to 1; 2 bits
                                                   1L, // square root rounds down to 1; 1 bit
diff --git a/tck/src/main/java/ee/jakarta/tck/data/standalone/persistence/PersistenceEntityTests.java b/tck/src/main/java/ee/jakarta/tck/data/standalone/persistence/PersistenceEntityTests.java
index e0458146..325e1f20 100644
--- a/tck/src/main/java/ee/jakarta/tck/data/standalone/persistence/PersistenceEntityTests.java
+++ b/tck/src/main/java/ee/jakarta/tck/data/standalone/persistence/PersistenceEntityTests.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023 Contributors to the Eclipse Foundation
+ * Copyright (c) 2023,2024 Contributors to the Eclipse Foundation
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -38,6 +38,7 @@ import ee.jakarta.tck.data.framework.junit.anno.Persistence;
 import ee.jakarta.tck.data.framework.junit.anno.Standalone;
 import ee.jakarta.tck.data.standalone.persistence.Product.Department;
 
+import jakarta.data.Order;
 import jakarta.data.Sort;
 import jakarta.data.Streamable;
 import jakarta.data.exceptions.EntityExistsException;
@@ -123,7 +124,7 @@ public class PersistenceEntityTests {
         catalog.save(Product.of("banana", 0.49, "TEST-PROD-17", Department.GROCERY));
         catalog.save(Product.of("plum", 0.89, "TEST-PROD-18", Department.GROCERY));
 
-        Iterable<Product> found = catalog.findByIdBetween("TEST-PROD-13", "TEST-PROD-17", Sort.asc("name"));
+        Iterable<Product> found = catalog.findByIdBetween("TEST-PROD-13", "TEST-PROD-17", Order.by(Sort.asc("name")));
         Iterator<Product> it = found.iterator();
         assertEquals(true, it.hasNext());
         assertEquals("banana", it.next().getName());
@@ -248,7 +249,7 @@ public class PersistenceEntityTests {
         // Remove only the entities that actually exist in the database
         catalog.removeMultiple(strawberries, blueberries, raspberries);
 
-        Iterable<Product> remaining = catalog.findByIdBetween("TEST-PROD-95", "TEST-PROD-99");
+        Iterable<Product> remaining = catalog.findByIdBetween("TEST-PROD-95", "TEST-PROD-99", Order.by());
         assertEquals(false, remaining.iterator().hasNext());
     }