Repository: primefaces/primefaces
Pull request #10883 (2023-10-23 16:37:41)

Bump org.owasp:dependency-check-maven from 8.4.0 to 8.4.2
Bumps [org.owasp:dependency-check-maven](https://github.com/jeremylong/DependencyCheck) from 8.4.0 to 8.4.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/jeremylong/DependencyCheck/releases">org.owasp:dependency-check-maven's releases</a>.</em></p>
<blockquote>
<h2>Version 8.4.2</h2>
<ul>
<li>fix: correct log configuration in cli (<a href="https://redirect.github.com/jeremylong/DependencyCheck/issues/6002">#6002</a>)</li>
</ul>
<p>See the full listing of <a href="https://github.com/jeremylong/DependencyCheck/milestone/69?closed=1">changes</a>.</p>
<h2>Version 8.4.1</h2>
<ul>
<li>fix: upgrade to JCS3 (<a href="https://redirect.github.com/jeremylong/DependencyCheck/issues/5114">#5114</a>)</li>
<li>fix: Support ~= version specifier in requirements.txt and pipfile (<a href="https://redirect.github.com/jeremylong/DependencyCheck/issues/5902">#5902</a>)</li>
<li>fix: Version of dependency no longer ignored when CPE product has a 'java' suffix in a product name (<a href="https://redirect.github.com/jeremylong/DependencyCheck/issues/5901">#5901</a>)</li>
<li>fix: Do not filter out evidences added by hints (<a href="https://redirect.github.com/jeremylong/DependencyCheck/issues/5900">#5900</a>)</li>
<li>fix: fixes FP <a href="https://redirect.github.com/jeremylong/DependencyCheck/issues/5925">#5925</a> (<a href="https://redirect.github.com/jeremylong/DependencyCheck/issues/5927">#5927</a>)</li>
</ul>
<p>See the full listing of <a href="https://github.com/jeremylong/DependencyCheck/milestone/67?closed=1">changes</a>.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/jeremylong/DependencyCheck/blob/main/CHANGELOG.md">org.owasp:dependency-check-maven's changelog</a>.</em></p>
<blockquote>
<h2><a href="https://github.com/jeremylong/DependencyCheck/releases/tag/v8.4.2">Version 8.4.2</a> (2023-10-22)</h2>
<ul>
<li>fix: correct log configuration in cli (<a href="https://redirect.github.com/jeremylong/DependencyCheck/issues/6002">#6002</a>)</li>
</ul>
<p>See the full listing of <a href="https://github.com/jeremylong/DependencyCheck/milestone/69?closed=1">changes</a>.</p>
<h2><a href="https://github.com/jeremylong/DependencyCheck/releases/tag/v8.4.1">Version 8.4.1</a> (2023-10-21)</h2>
<h3>Fixed</h3>
<ul>
<li>fix: upgrade to JCS3 (<a href="https://redirect.github.com/jeremylong/DependencyCheck/issues/5114">#5114</a>)</li>
<li>fix: Support ~= version specifier in requirements.txt and pipfile (<a href="https://redirect.github.com/jeremylong/DependencyCheck/issues/5902">#5902</a>)</li>
<li>fix: Version of dependency no longer ignored when CPE product has a 'java' suffix in a product name (<a href="https://redirect.github.com/jeremylong/DependencyCheck/issues/5901">#5901</a>)</li>
<li>fix: Do not filter out evidences added by hints (<a href="https://redirect.github.com/jeremylong/DependencyCheck/issues/5900">#5900</a>)</li>
<li>fix: fixes FP <a href="https://redirect.github.com/jeremylong/DependencyCheck/issues/5925">#5925</a> (<a href="https://redirect.github.com/jeremylong/DependencyCheck/issues/5927">#5927</a>)</li>
</ul>
<p>See the full listing of <a href="https://github.com/jeremylong/DependencyCheck/milestone/67?closed=1">changes</a>.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/jeremylong/DependencyCheck/commit/c15b3b9677e2c64bd69b104809a294b8caf159ac"><code>c15b3b9</code></a> build: prepare release v8.4.2</li>
<li><a href="https://github.com/jeremylong/DependencyCheck/commit/dbdb84d8bf143f5970f8294f1277f1d66b1f599a"><code>dbdb84d</code></a> docs: release 8.4.2</li>
<li><a href="https://github.com/jeremylong/DependencyCheck/commit/74f5156870dec697df97ead8ff92d3b01ec3bb08"><code>74f5156</code></a> fix: dependabot config</li>
<li><a href="https://github.com/jeremylong/DependencyCheck/commit/1162b3ecc8378cfcb73316602521492a8345b2cc"><code>1162b3e</code></a> fix: correct log configuration in cli (<a href="https://redirect.github.com/jeremylong/DependencyCheck/issues/6002">#6002</a>)</li>
<li><a href="https://github.com/jeremylong/DependencyCheck/commit/e1055405ca76b889b771f071565eedbe32b8ffc0"><code>e105540</code></a> chore: Release 8.4.1 (<a href="https://redirect.github.com/jeremylong/DependencyCheck/issues/6000">#6000</a>)</li>
<li><a href="https://github.com/jeremylong/DependencyCheck/commit/5cc8df0f8f04247763d571aeb362af1c4bdfbd62"><code>5cc8df0</code></a> build: prepare for next development iteration</li>
<li><a href="https://github.com/jeremylong/DependencyCheck/commit/61377ad8f1b097025cfb7047abbfb0a2dca01a7b"><code>61377ad</code></a> build: prepare release v8.4.1</li>
<li><a href="https://github.com/jeremylong/DependencyCheck/commit/e2649a6bbee0621794b3f549ed5919f1f3650290"><code>e2649a6</code></a> docs: prepare release</li>
<li><a href="https://github.com/jeremylong/DependencyCheck/commit/778185bce378591be6e0b4642d4d4ab5cbb10510"><code>778185b</code></a> build(deps): bump org.jacoco:jacoco-maven-plugin from 0.8.10 to 0.8.11 (<a href="https://redirect.github.com/jeremylong/DependencyCheck/issues/5994">#5994</a>)</li>
<li><a href="https://github.com/jeremylong/DependencyCheck/commit/81bd7780bc122e5d8eded6ce1f9f4523c12a3cd4"><code>81bd778</code></a> build(deps): bump se.bjurr.violations:violations-lib from 1.156.6 to 1.156.7 ...</li>
<li>Additional commits viewable in <a href="https://github.com/jeremylong/DependencyCheck/compare/v8.4.0...v8.4.2">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.owasp:dependency-check-maven&package-manager=maven&previous-version=8.4.0&new-version=8.4.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/docs/14_0_0/components/datatable.md b/docs/14_0_0/components/datatable.md
index bc21a29a96..636ac888e2 100644
--- a/docs/14_0_0/components/datatable.md
+++ b/docs/14_0_0/components/datatable.md
@@ -173,8 +173,8 @@ We suggest you to not store your data model inside a longer-living scope like `@
 
 So either you use a `@RequestScoped` like in the _Getting started_, or use a `LazyDataModel` to load your data on-the-fly.   
 PrimeFaces comes with 2 built-in `LazyDataModel`:
-- `JpaLazyDataModel`: implementation to load the data via JPA
-- `ReflectionLazyDataModel`: implementation which loads the data via lambda. It also performance better (up to 30%) compared to binding a simple List.
+- `JPALazyDataModel`: implementation to load the data via JPA
+- `DefaultLazyDataModel`: implementation which loads the data via lambda. It also performs better (up to 30%) compared to binding a simple List.
 
 
 ```java
@@ -187,8 +187,9 @@ public class CarBean {
 
     @PostConstruct
     public void init() {
-        cars = ReflectionLazyDataModel.builder(() -> carService.getCars())
-                .rowKeyProvider((car) -> Integer.toString(car.getId())) // required for selection
+        cars = DefaultLazyDataModel.<Car>builder()
+                .valueSupplier(() -> carService.getCars())
+                .rowKeyProvider(Car::getId) // required for selection
                 .build();
     }
 
@@ -994,44 +995,55 @@ public class CarBean {
 }
 ```
 
-### JpaLazyDataModel
+### JPALazyDataModel
 
 PrimeFaces provides a OOTB implementation for JPA users, which supports basic features.
 
 ```java
-new JpaLazyDataModel<>(MyEntity.class, () -> entityManager);
+JPALazyDataModel<MyEntity> lazyDataModel = JPALazyDataModel.<MyEntity>builder())
+        .entityClass(MyEntity.class)
+        .entityManager(() -> entityManager)
+        .build();
 ```
 
 If you have selection enabled, you can either pass the rowKey via JPA metamodel:
 
 ```java
-JpaLazyDataModel<MyEntity> lazyDataModel = JpaLazyDataModel.builder(MyEntity.class, () -> entityManager)
-    .rowKey(MyEntity_.id)
-    .build();
+JPALazyDataModel<MyEntity> lazyDataModel = JPALazyDataModel.<MyEntity>builder())
+        .entityClass(MyEntity.class)
+        .entityManager(() -> entityManager)
+        .rowKeyField(MyEntity_.id)
+        .build();
 ```
 
 or as plain String:
 
 ```java
-JpaLazyDataModel<MyEntity> lazyDataModel = JpaLazyDataModel.builder(MyEntity.class, () -> entityManager)
-    .rowKey("id")
-    .build();
+JPALazyDataModel<MyEntity> lazyDataModel = JPALazyDataModel.<MyEntity>builder())
+        .entityClass(MyEntity.class)
+        .entityManager(() -> entityManager)
+        .rowKeyField("id")
+        .build();
 ```
 
 or provide a existing JSF `Converter`:
 
 ```java
-JpaLazyDataModel<MyEntity> lazyDataModel = JpaLazyDataModel.builder(MyEntity.class, () -> entityManager)
-    .rowKeyConverter(myConverter)
-    .build();
+JPALazyDataModel<MyEntity> lazyDataModel = JPALazyDataModel.<MyEntity>builder())
+        .entityClass(MyEntity.class)
+        .entityManager(() -> entityManager)
+        .rowKeyConverter(myConverter)
+        .build();
 ```
 
 or enable case insensitive searching:
 
 ```java
-JpaLazyDataModel<MyEntity> lazyDataModel = JpaLazyDataModel.builder(MyEntity.class, () -> entityManager)
-    .caseSensitive(false)
-    .build();
+JPALazyDataModel<MyEntity> lazyDataModel = JPALazyDataModel.<MyEntity>builder())
+        .entityClass(MyEntity.class)
+        .entityManager(() -> entityManager)
+        .caseSensitive(false)
+        .build();
 ```
 
 or enable wildcard support for characters `*`, `%`, `?` and `_`. Characters `*` and `%` means any characters 
@@ -1039,23 +1051,27 @@ while `?` and `_` mean replace a single character.  For example `Smith*` would f
 the word `Smith`. For single character replacement like `Te?t` would match words `Tent` and `Test`.
 
 ```java
-JpaLazyDataModel<MyEntity> lazyDataModel = JpaLazyDataModel.builder(MyEntity.class, () -> entityManager)
-    .wildcardSupport(true)
-    .build();
+JPALazyDataModel<MyEntity> lazyDataModel = JPALazyDataModel.<MyEntity>builder())
+        .entityClass(MyEntity.class)
+        .entityManager(() -> entityManager)
+        .wildcardSupport(true)
+        .build();
 ```
 
 Also you can add global filters or manipulate generated predicates (from the DataTable columns) via:
 ```java
-JpaLazyDataModel<MyEntity> lazyDataModel = JpaLazyDataModel.builder(MyEntity.class, () -> entityManager)
-    .filterEnricher((filterBy, cb, cq, root, predicates) -> {
-        predicates.add(cb.isNull(root.get("id")));
-    })
-    .build();
+JPALazyDataModel<MyEntity> lazyDataModel = JPALazyDataModel.<MyEntity>builder())
+        .entityClass(MyEntity.class)
+        .entityManager(() -> entityManager)
+        .filterEnricher((filterBy, cb, cq, root, predicates) -> {
+            predicates.add(cb.isNull(root.get("id")));
+        })
+        .build();
 ```
 
-### ReflectionLazyDataModel
+### DefaultLazyDataModel
 
-`ReflectionLazyDataModel` is a `LazyDataModel` implementation which takes a supplier as datasource and implements filtering and sorting via reflection.
+`DefaultLazyDataModel` is a `LazyDataModel` implementation which takes a supplier as datasource and implements filtering and sorting via reflection.
 It can be used for cases where you would dynamically load the datasource e.g. from a webservice.
 
 Also it can be used as replacement for binding a simple List as it has some benefits:
@@ -1064,12 +1080,13 @@ Also it can be used as replacement for binding a simple List as it has some bene
 3) it allows some additional features like applying additional filtering or sorting via lambda.
 
 ```java
-private ReflectionLazyDataModel<MyPojo> dataModel;
+private DefaultLazyDataModel<MyPojo> dataModel;
 
 @PostConstruct
 public void init() {
-    dataModel = ReflectionLazyDataModel.builder(() -> service.getListOfMyPojos())
-        .rowKeyProvider((o) -> Integer.toString(o.getId())) // required for selection
+    dataModel = DefaultLazyDataModel.<MyPojo>builder()
+        .valueSupplier(() -> service.getListOfMyPojos())
+        .rowKeyProvider(MyPojo::getId) // required for selection
         .build();
 ```
 
diff --git a/docs/migrationguide/14_0_0.md b/docs/migrationguide/14_0_0.md
index 2d90d15e0b..9031e4da5b 100644
--- a/docs/migrationguide/14_0_0.md
+++ b/docs/migrationguide/14_0_0.md
@@ -11,6 +11,7 @@
   
 ## Deprecated
   * Apache Commons FileUpload support has been deprecated for native uploading now that Servlet 3.0 is the minimum requirement. Please report any issues if native uploading does not work for your needs.
+  * `InputSwitch` has been deprecated since 10.0 and will be removed in 15.0.0
 
 ## AutoComplete
   * `dropdownAriaLabel`, `emptyMessage`, `resultsMessage` properties have been moved to client side locale
@@ -20,7 +21,8 @@
   
 ## DataTable
   * Using Column custom `sortFunction` signature change now requires a third parameter `SortMeta` like `public int sortByModel(Object car1, Object car2, SortMeta sortMeta)`
-  * JpaLazyDataModel: deprecated constructors, please use JpaLazyDataModel.builder() now
+  * `JpaLazyDataModel` renamed to `JPALazyDataModel`
+  * `JPALazyDataModel`: deprecated constructors, use `JPALazyDataModel.builder()` instead
 
 ## Paginator
   * All ARIA labels moved from server `Messages.properties` to client `locale-xx.js`
diff --git a/primefaces-selenium/primefaces-selenium-components/src/main/java/org/primefaces/selenium/component/InputSwitch.java b/primefaces-selenium/primefaces-selenium-components/src/main/java/org/primefaces/selenium/component/InputSwitch.java
index 524dcce675..7f198f7287 100644
--- a/primefaces-selenium/primefaces-selenium-components/src/main/java/org/primefaces/selenium/component/InputSwitch.java
+++ b/primefaces-selenium/primefaces-selenium-components/src/main/java/org/primefaces/selenium/component/InputSwitch.java
@@ -30,7 +30,7 @@ import org.primefaces.selenium.component.base.AbstractToggleComponent;
  *
  * @deprecated since 10.0 and removal possible anytime after that
  */
-@Deprecated
+@Deprecated(since = "10.0", forRemoval  =  true)
 public abstract class InputSwitch extends AbstractToggleComponent {
 
 }
diff --git a/primefaces-showcase/pom.xml b/primefaces-showcase/pom.xml
index 00d00458ff..909d37b3a7 100644
--- a/primefaces-showcase/pom.xml
+++ b/primefaces-showcase/pom.xml
@@ -233,6 +233,7 @@
                     <noSourceMap>true</noSourceMap>
                     <style>COMPRESSED</style>
                     <quiet>false</quiet>
+                    <version>1.68.0</version>
                 </configuration>
             </plugin>
             <plugin>
diff --git a/primefaces-showcase/src/main/java/org/primefaces/showcase/view/data/datatable/BasicView.java b/primefaces-showcase/src/main/java/org/primefaces/showcase/view/data/datatable/BasicView.java
index 60fc95b838..1a1abe2a21 100644
--- a/primefaces-showcase/src/main/java/org/primefaces/showcase/view/data/datatable/BasicView.java
+++ b/primefaces-showcase/src/main/java/org/primefaces/showcase/view/data/datatable/BasicView.java
@@ -23,15 +23,15 @@
  */
 package org.primefaces.showcase.view.data.datatable;
 
-import javax.faces.view.ViewScoped;
-import org.primefaces.showcase.domain.Product;
-import org.primefaces.showcase.service.ProductService;
-
+import java.io.Serializable;
+import java.util.List;
 import javax.annotation.PostConstruct;
+import javax.faces.view.ViewScoped;
 import javax.inject.Inject;
 import javax.inject.Named;
-import java.io.Serializable;
-import java.util.List;
+
+import org.primefaces.showcase.domain.Product;
+import org.primefaces.showcase.service.ProductService;
 
 @Named("dtBasicView")
 @ViewScoped
@@ -54,4 +54,5 @@ public class BasicView implements Serializable {
     public void setService(ProductService service) {
         this.service = service;
     }
+
 }
diff --git a/primefaces/pom.xml b/primefaces/pom.xml
index 78b2d4cbfc..b994bb298a 100644
--- a/primefaces/pom.xml
+++ b/primefaces/pom.xml
@@ -1196,7 +1196,7 @@
                     <plugin>
                         <groupId>org.owasp</groupId>
                         <artifactId>dependency-check-maven</artifactId>
-                        <version>8.4.0</version>
+                        <version>8.4.2</version>
                         <configuration>
                             <skipProvidedScope>false</skipProvidedScope>
                             <skipRuntimeScope>false</skipRuntimeScope>
diff --git a/primefaces/src/main/java/org/primefaces/component/api/UITable.java b/primefaces/src/main/java/org/primefaces/component/api/UITable.java
index e577067f97..4dd2e95cd6 100644
--- a/primefaces/src/main/java/org/primefaces/component/api/UITable.java
+++ b/primefaces/src/main/java/org/primefaces/component/api/UITable.java
@@ -23,7 +23,6 @@
  */
 package org.primefaces.component.api;
 
-import java.text.Collator;
 import java.util.*;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Function;
@@ -45,6 +44,7 @@ import org.primefaces.model.FilterMeta;
 import org.primefaces.model.SortMeta;
 import org.primefaces.util.ComponentUtils;
 import org.primefaces.util.LangUtils;
+import org.primefaces.util.LocaleUtils;
 
 public interface UITable<T extends UITableState> extends ColumnAware, MultiViewStateAware<T> {
 
@@ -96,7 +96,7 @@ public interface UITable<T extends UITableState> extends ColumnAware, MultiViewS
             if (FilterMeta.GLOBAL_FILTER_KEY.equals(entry.getKey())) {
                 UIComponent globalFilterComponent = SearchExpressionUtils.contextlessResolveComponent(
                         context, (UIComponent) this, GLOBAL_FILTER_COMPONENT_ID);
-                if (globalFilterComponent != null && globalFilterComponent instanceof ValueHolder) {
+                if (globalFilterComponent instanceof ValueHolder) {
                     ((ValueHolder) globalFilterComponent).setValue(entry.getValue().getFilterValue());
                 }
             }
@@ -404,7 +404,7 @@ public interface UITable<T extends UITableState> extends ColumnAware, MultiViewS
         }
 
         Map<String, ColumnMeta> columMeta = getColumnMeta();
-        columMeta.values().stream().forEach(s -> s.setVisible(null));
+        columMeta.values().forEach(s -> s.setVisible(null));
 
         if (LangUtils.isNotBlank(columnTogglerStateParam)) {
             String[] columnStates = columnTogglerStateParam.split(",");
@@ -416,7 +416,7 @@ public interface UITable<T extends UITableState> extends ColumnAware, MultiViewS
                 String columnKey = columnState.substring(0, seperatorIndex);
                 boolean visible = Boolean.parseBoolean(columnState.substring(seperatorIndex + 1));
 
-                ColumnMeta meta = columMeta.computeIfAbsent(columnKey, k -> new ColumnMeta(k));
+                ColumnMeta meta = columMeta.computeIfAbsent(columnKey, ColumnMeta::new);
                 meta.setVisible(visible);
             }
         }
@@ -435,7 +435,7 @@ public interface UITable<T extends UITableState> extends ColumnAware, MultiViewS
         }
 
         Map<String, ColumnMeta> columMeta = getColumnMeta();
-        columMeta.values().stream().forEach(s -> s.setWidth(null));
+        columMeta.values().forEach(s -> s.setWidth(null));
 
         String tableWidth = null;
 
@@ -455,7 +455,7 @@ public interface UITable<T extends UITableState> extends ColumnAware, MultiViewS
                     String columnKey = columnState.substring(0, seperatorIndex);
                     String width = columnState.substring(seperatorIndex + 1);
 
-                    ColumnMeta meta = columMeta.computeIfAbsent(columnKey, k -> new ColumnMeta(k));
+                    ColumnMeta meta = columMeta.computeIfAbsent(columnKey, ColumnMeta::new);
                     meta.setWidth(width);
                 }
             }
@@ -480,7 +480,7 @@ public interface UITable<T extends UITableState> extends ColumnAware, MultiViewS
         }
 
         Map<String, ColumnMeta> columMeta = getColumnMeta();
-        columMeta.values().stream().forEach(s -> s.setDisplayPriority(0));
+        columMeta.values().forEach(s -> s.setDisplayPriority(0));
 
         String[] columnKeys = columnOrderParam.split(",");
         for (int i = 0; i < columnKeys.length; i++) {
@@ -489,7 +489,7 @@ public interface UITable<T extends UITableState> extends ColumnAware, MultiViewS
                 continue;
             }
 
-            ColumnMeta meta = columMeta.computeIfAbsent(columnKey, k -> new ColumnMeta(k));
+            ColumnMeta meta = columMeta.computeIfAbsent(columnKey, ColumnMeta::new);
             meta.setDisplayPriority(i);
         }
 
@@ -508,8 +508,7 @@ public interface UITable<T extends UITableState> extends ColumnAware, MultiViewS
     }
 
     default Object getFieldValue(FacesContext context, UIColumn column) {
-        Object value = UIColumn.createValueExpressionFromField(context, getVar(), column.getField()).getValue(context.getELContext());
-        return value;
+        return UIColumn.createValueExpressionFromField(context, getVar(), column.getField()).getValue(context.getELContext());
     }
 
     default String getConvertedFieldValue(FacesContext context, UIColumn column) {
@@ -523,57 +522,6 @@ public interface UITable<T extends UITableState> extends ColumnAware, MultiViewS
         return getFilterByAsMap().values().stream().anyMatch(FilterMeta::isActive);
     }
 
-    default int compare(FacesContext context, String var, SortMeta sortMeta, Object o1, Object o2,
-            Collator collator, Locale locale) {
-
-        try {
-            ValueExpression ve = sortMeta.getSortBy();
-
-            context.getExternalContext().getRequestMap().put(var, o1);
-            Object value1 = ve.getValue(context.getELContext());
-
-            context.getExternalContext().getRequestMap().put(var, o2);
-            Object value2 = ve.getValue(context.getELContext());
-
-            int result;
-
-            if (sortMeta.getFunction() == null) {
-                //Empty check
-                if (value1 == null && value2 == null) {
-                    result = 0;
-                }
-                else if (value1 == null) {
-                    result = sortMeta.getNullSortOrder();
-                }
-                else if (value2 == null) {
-                    result = -1 * sortMeta.getNullSortOrder();
-                }
-                else if (value1 instanceof String && value2 instanceof String) {
-                    if (sortMeta.isCaseSensitiveSort()) {
-                        result = collator.compare(value1, value2);
-                    }
-                    else {
-                        String str1 = (((String) value1).toLowerCase(locale));
-                        String str2 = (((String) value2).toLowerCase(locale));
-
-                        result = collator.compare(str1, str2);
-                    }
-                }
-                else {
-                    result = ((Comparable<Object>) value1).compareTo(value2);
-                }
-            }
-            else {
-                result = (Integer) sortMeta.getFunction().invoke(context.getELContext(), new Object[]{value1, value2, sortMeta});
-            }
-
-            return sortMeta.getOrder().isAscending() ? result : -1 * result;
-        }
-        catch (Exception e) {
-            throw new FacesException(e);
-        }
-    }
-
     /**
      * Recalculates filteredValue after adding, updating or removing object to/from a filtered UITable.
      */
@@ -604,7 +552,13 @@ public interface UITable<T extends UITableState> extends ColumnAware, MultiViewS
         return false;
     }
 
+    default Locale resolveDataLocale(FacesContext context) {
+        return LocaleUtils.resolveLocale(context, getDataLocale(), getClientId(context));
+    }
+
     int getChildCount();
 
     List<UIComponent> getChildren();
+
+    Object getDataLocale();
 }
diff --git a/primefaces/src/main/java/org/primefaces/component/datatable/DataTable.java b/primefaces/src/main/java/org/primefaces/component/datatable/DataTable.java
index 8a012a8713..6e5c4de022 100644
--- a/primefaces/src/main/java/org/primefaces/component/datatable/DataTable.java
+++ b/primefaces/src/main/java/org/primefaces/component/datatable/DataTable.java
@@ -856,8 +856,7 @@ public class DataTable extends DataTableBase {
     }
 
     public Locale resolveDataLocale() {
-        FacesContext context = getFacesContext();
-        return LocaleUtils.resolveLocale(context, getDataLocale(), getClientId(context));
+        return resolveDataLocale(getFacesContext());
     }
 
     @Override
diff --git a/primefaces/src/main/java/org/primefaces/component/datatable/feature/SortFeature.java b/primefaces/src/main/java/org/primefaces/component/datatable/feature/SortFeature.java
index 4e2a54fd54..e24303e9a1 100644
--- a/primefaces/src/main/java/org/primefaces/component/datatable/feature/SortFeature.java
+++ b/primefaces/src/main/java/org/primefaces/component/datatable/feature/SortFeature.java
@@ -24,15 +24,10 @@
 package org.primefaces.component.datatable.feature;
 
 import java.io.IOException;
-import java.text.Collator;
 import java.util.List;
-import java.util.Locale;
 import java.util.Map;
-import java.util.concurrent.atomic.AtomicInteger;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
-
-import javax.el.ValueExpression;
 import javax.faces.FacesException;
 import javax.faces.context.FacesContext;
 import javax.faces.model.ListDataModel;
@@ -45,6 +40,7 @@ import org.primefaces.event.data.PostSortEvent;
 import org.primefaces.model.SortMeta;
 import org.primefaces.model.SortOrder;
 import org.primefaces.util.ComponentUtils;
+import org.primefaces.util.SortTableComparator;
 
 public class SortFeature implements DataTableFeature {
 
@@ -141,39 +137,10 @@ public class SortFeature implements DataTableFeature {
         }
 
         List<?> list = resolveList(value);
-        Locale locale = table.resolveDataLocale();
         String var = table.getVar();
-        Collator collator = Collator.getInstance(locale);
-        AtomicInteger comparisonResult = new AtomicInteger();
-        Map<String, SortMeta> sortBy = table.getActiveSortMeta();
-
         Object varBackup = context.getExternalContext().getRequestMap().get(var);
 
-        list.sort((o1, o2) -> {
-            for (SortMeta sortMeta : sortBy.values()) {
-                comparisonResult.set(0);
-
-                if (sortMeta.isHeaderRow()) {
-                    int result = compare(context, var, sortMeta, o1, o2, collator, locale);
-                    comparisonResult.set(result);
-                }
-                else {
-                    // Currently ColumnGrouping supports ui:repeat, therefore we have to use a callback
-                    // and can't use sortMeta.getComponent()
-                    // Later when we refactored ColumnGrouping, we may remove #invokeOnColumn as we dont support ui:repeat in other cases
-                    table.invokeOnColumn(sortMeta.getColumnKey(), column -> {
-                        int result = compare(context, var, sortMeta, o1, o2, collator, locale);
-                        comparisonResult.set(result);
-                    });
-                }
-
-                if (comparisonResult.get() != 0) {
-                    return comparisonResult.get();
-                }
-            }
-
-            return 0;
-        });
+        list.sort(SortTableComparator.comparingSortByVE(context, table));
 
         if (varBackup == null) {
             context.getExternalContext().getRequestMap().remove(var);
@@ -183,63 +150,6 @@ public class SortFeature implements DataTableFeature {
         }
     }
 
-    public static int compare(FacesContext context, String var, SortMeta sortMeta, Object o1, Object o2,
-            Collator collator, Locale locale) {
-
-        ValueExpression ve = sortMeta.getSortBy();
-
-        context.getExternalContext().getRequestMap().put(var, o1);
-        Object value1 = ve.getValue(context.getELContext());
-
-        context.getExternalContext().getRequestMap().put(var, o2);
-        Object value2 = ve.getValue(context.getELContext());
-
-        return compare(context, sortMeta, value1, value2, collator, locale);
-    }
-
-    public static int compare(FacesContext context, SortMeta sortMeta, Object value1, Object value2,
-            Collator collator, Locale locale) {
-
-        try {
-            int result;
-
-            if (sortMeta.getFunction() == null) {
-                //Empty check
-                if (value1 == null && value2 == null) {
-                    result = 0;
-                }
-                else if (value1 == null) {
-                    result = sortMeta.getNullSortOrder();
-                }
-                else if (value2 == null) {
-                    result = -1 * sortMeta.getNullSortOrder();
-                }
-                else if (value1 instanceof String && value2 instanceof String) {
-                    if (sortMeta.isCaseSensitiveSort()) {
-                        result = collator.compare(value1, value2);
-                    }
-                    else {
-                        String str1 = (((String) value1).toLowerCase(locale));
-                        String str2 = (((String) value2).toLowerCase(locale));
-
-                        result = collator.compare(str1, str2);
-                    }
-                }
-                else {
-                    result = ((Comparable<Object>) value1).compareTo(value2);
-                }
-            }
-            else {
-                result = (Integer) sortMeta.getFunction().invoke(context.getELContext(), new Object[]{value1, value2, sortMeta});
-            }
-
-            return sortMeta.getOrder().isAscending() ? result : -1 * result;
-        }
-        catch (Exception e) {
-            throw new FacesException(e);
-        }
-    }
-
     @Override
     public boolean shouldDecode(FacesContext context, DataTable table) {
         return isSortRequest(context, table);
diff --git a/primefaces/src/main/java/org/primefaces/component/inputswitch/InputSwitch.java b/primefaces/src/main/java/org/primefaces/component/inputswitch/InputSwitch.java
index 6c0b621384..7accb89a39 100644
--- a/primefaces/src/main/java/org/primefaces/component/inputswitch/InputSwitch.java
+++ b/primefaces/src/main/java/org/primefaces/component/inputswitch/InputSwitch.java
@@ -31,6 +31,12 @@ import javax.faces.event.BehaviorEvent;
 
 import org.primefaces.util.MapBuilder;
 
+/**
+*
+* @deprecated in 10.0 use ToggleSwitch
+*
+*/
+@Deprecated(since = "10.0", forRemoval  =  true)
 @ResourceDependency(library = "primefaces", name = "components.css")
 @ResourceDependency(library = "primefaces", name = "inputswitch/inputswitch.css")
 @ResourceDependency(library = "primefaces", name = "jquery/jquery.js")
diff --git a/primefaces/src/main/java/org/primefaces/component/inputswitch/InputSwitchBase.java b/primefaces/src/main/java/org/primefaces/component/inputswitch/InputSwitchBase.java
index c3c8e88fb3..23748e889b 100644
--- a/primefaces/src/main/java/org/primefaces/component/inputswitch/InputSwitchBase.java
+++ b/primefaces/src/main/java/org/primefaces/component/inputswitch/InputSwitchBase.java
@@ -29,6 +29,12 @@ import javax.faces.component.behavior.ClientBehaviorHolder;
 import org.primefaces.component.api.PrimeClientBehaviorHolder;
 import org.primefaces.component.api.Widget;
 
+/**
+*
+* @deprecated in 10.0 use ToggleSwitch
+*
+*/
+@Deprecated(since = "10.0", forRemoval = true)
 public abstract class InputSwitchBase extends UIInput implements Widget, ClientBehaviorHolder, PrimeClientBehaviorHolder {
 
     public static final String COMPONENT_FAMILY = "org.primefaces.component";
diff --git a/primefaces/src/main/java/org/primefaces/component/inputswitch/InputSwitchRenderer.java b/primefaces/src/main/java/org/primefaces/component/inputswitch/InputSwitchRenderer.java
index 0a4a39ba51..acdd93edc5 100644
--- a/primefaces/src/main/java/org/primefaces/component/inputswitch/InputSwitchRenderer.java
+++ b/primefaces/src/main/java/org/primefaces/component/inputswitch/InputSwitchRenderer.java
@@ -34,6 +34,12 @@ import org.primefaces.util.ComponentUtils;
 import org.primefaces.util.HTML;
 import org.primefaces.util.WidgetBuilder;
 
+/**
+*
+* @deprecated in 10.0 use ToggleSwitch
+*
+*/
+@Deprecated(since = "10.0", forRemoval = true)
 public class InputSwitchRenderer extends InputRenderer {
 
     @Override
diff --git a/primefaces/src/main/java/org/primefaces/component/treetable/TreeTable.java b/primefaces/src/main/java/org/primefaces/component/treetable/TreeTable.java
index ad9e50ad0d..fd82846270 100644
--- a/primefaces/src/main/java/org/primefaces/component/treetable/TreeTable.java
+++ b/primefaces/src/main/java/org/primefaces/component/treetable/TreeTable.java
@@ -46,7 +46,6 @@ import org.primefaces.model.SortMeta;
 import org.primefaces.model.TreeNode;
 import org.primefaces.util.ComponentUtils;
 import org.primefaces.util.Constants;
-import org.primefaces.util.LocaleUtils;
 import org.primefaces.util.MapBuilder;
 
 @ResourceDependency(library = "primefaces", name = "components.css")
@@ -311,8 +310,7 @@ public class TreeTable extends TreeTableBase {
     }
 
     public Locale resolveDataLocale() {
-        FacesContext context = getFacesContext();
-        return LocaleUtils.resolveLocale(context, getDataLocale(), getClientId(context));
+        return resolveDataLocale(getFacesContext());
     }
 
     @Override
diff --git a/primefaces/src/main/java/org/primefaces/component/treetable/feature/SortFeature.java b/primefaces/src/main/java/org/primefaces/component/treetable/feature/SortFeature.java
index 3bd9b95f79..588eda4b0a 100644
--- a/primefaces/src/main/java/org/primefaces/component/treetable/feature/SortFeature.java
+++ b/primefaces/src/main/java/org/primefaces/component/treetable/feature/SortFeature.java
@@ -23,16 +23,13 @@
  */
 package org.primefaces.component.treetable.feature;
 
-import javax.faces.FacesException;
-import javax.faces.context.FacesContext;
 import java.io.IOException;
-import java.text.Collator;
 import java.util.Arrays;
-import java.util.Locale;
 import java.util.Map;
-import java.util.concurrent.atomic.AtomicInteger;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
+import javax.faces.FacesException;
+import javax.faces.context.FacesContext;
 
 import org.primefaces.PrimeFaces;
 import org.primefaces.component.treetable.TreeTable;
@@ -44,6 +41,7 @@ import org.primefaces.model.SortOrder;
 import org.primefaces.model.TreeNode;
 import org.primefaces.model.TreeNodeList;
 import org.primefaces.util.ComponentUtils;
+import org.primefaces.util.SortTableComparator;
 
 public class SortFeature implements TreeTableFeature {
 
@@ -122,15 +120,10 @@ public class SortFeature implements TreeTableFeature {
             return;
         }
 
-        AtomicInteger comparisonResult = new AtomicInteger();
-
         String var = table.getVar();
-        Locale locale = table.resolveDataLocale();
-        Collator collator = Collator.getInstance(locale);
-
         Object varBackup = context.getExternalContext().getRequestMap().get(var);
 
-        sortNode(table, sortBy, comparisonResult, root, context, var, locale, collator);
+        sortNode(context, table, root);
 
         if (varBackup == null) {
             context.getExternalContext().getRequestMap().remove(var);
@@ -140,38 +133,17 @@ public class SortFeature implements TreeTableFeature {
         }
     }
 
-    protected void sortNode(TreeTable table, Map<String, SortMeta> sortBy, AtomicInteger comparisonResult,
-            TreeNode<?> node, FacesContext context, String var, Locale locale, Collator collator) {
+    protected void sortNode(FacesContext context, TreeTable table, TreeNode<?> node) {
         TreeNodeList<?> children = (TreeNodeList) node.getChildren();
 
         if (children != null && !children.isEmpty()) {
             Object[] childrenArray = children.toArray();
-
-            Arrays.sort(childrenArray, (o1, o2) -> {
-                for (SortMeta sortMeta : sortBy.values()) {
-                    comparisonResult.set(0);
-
-                    table.invokeOnColumn(sortMeta.getColumnKey(), column -> {
-                        int result = table.compare(context, var, sortMeta,
-                                ((TreeNode) o1).getData(),
-                                ((TreeNode) o2).getData(),
-                                collator, locale);
-                        comparisonResult.set(result);
-                    });
-
-                    if (comparisonResult.get() != 0) {
-                        return comparisonResult.get();
-                    }
-                }
-
-                return 0;
-            });
-
+            Arrays.sort(childrenArray, SortTableComparator.comparingTreeNodeSortByVE(context, table));
             for (int i = 0; i < childrenArray.length; i++) {
                 children.set(i, (TreeNode) childrenArray[i]);
             }
             for (int i = 0; i < children.size(); i++) {
-                sortNode(table, sortBy, comparisonResult, children.get(i), context, var, locale, collator);
+                sortNode(context, table, children.get(i));
             }
         }
     }
diff --git a/primefaces/src/main/java/org/primefaces/config/PrimeConfiguration.java b/primefaces/src/main/java/org/primefaces/config/PrimeConfiguration.java
index eac129e99e..1aee92d76c 100644
--- a/primefaces/src/main/java/org/primefaces/config/PrimeConfiguration.java
+++ b/primefaces/src/main/java/org/primefaces/config/PrimeConfiguration.java
@@ -25,7 +25,6 @@ package org.primefaces.config;
 
 import java.util.Map;
 import java.util.Objects;
-
 import javax.faces.component.UIInput;
 import javax.faces.context.ExternalContext;
 import javax.faces.context.FacesContext;
diff --git a/primefaces/src/main/java/org/primefaces/context/PrimeApplicationContext.java b/primefaces/src/main/java/org/primefaces/context/PrimeApplicationContext.java
index 3390ac226a..6bde905bde 100644
--- a/primefaces/src/main/java/org/primefaces/context/PrimeApplicationContext.java
+++ b/primefaces/src/main/java/org/primefaces/context/PrimeApplicationContext.java
@@ -23,7 +23,6 @@
  */
 package org.primefaces.context;
 
-import java.beans.PropertyDescriptor;
 import java.io.IOException;
 import java.lang.reflect.Method;
 import java.nio.file.Files;
@@ -60,10 +59,7 @@ import org.primefaces.component.treetable.export.*;
 import org.primefaces.config.PrimeConfiguration;
 import org.primefaces.config.PrimeEnvironment;
 import org.primefaces.metadata.transformer.MetadataTransformer;
-import org.primefaces.util.Constants;
-import org.primefaces.util.LangUtils;
-import org.primefaces.util.Lazy;
-import org.primefaces.util.MapBuilder;
+import org.primefaces.util.*;
 import org.primefaces.validate.bean.ClientValidationConstraint;
 import org.primefaces.virusscan.VirusScannerService;
 import org.primefaces.webapp.FileUploadChunksServlet;
@@ -98,8 +94,7 @@ public class PrimeApplicationContext {
     private FileTypeDetector fileTypeDetector;
     private FileUploadDecoder fileUploadDecoder;
     private String fileUploadResumeUrl;
-
-    private ConcurrentHashMap<String, Map<String, PropertyDescriptor>> propertyDescriptorCache;
+    private PropertyDescriptorResolver propertyDescriptorResolver;
 
     public PrimeApplicationContext(FacesContext facesContext) {
         environment = new PrimeEnvironment(facesContext);
@@ -111,8 +106,6 @@ public class PrimeApplicationContext {
         beanValidationClientConstraintMapping = new ConcurrentHashMap<>();
         metadataTransformers = new CopyOnWriteArrayList<>();
 
-        propertyDescriptorCache = new ConcurrentHashMap<>();
-
         ClassLoader classLoader = null;
         Object context = facesContext.getExternalContext().getContext();
         if (context != null) {
@@ -177,6 +170,8 @@ public class PrimeApplicationContext {
         resolveFileTypeDetector();
 
         registerDefaultExporters();
+
+        resolvePropertyDescriptorResolver();
     }
 
     private void registerDefaultExporters() {
@@ -259,6 +254,13 @@ public class PrimeApplicationContext {
                 .orElseThrow(() -> new FacesException("FileUploaderDecoder '" + finalUploader + "' not found"));
     }
 
+    private void resolvePropertyDescriptorResolver() {
+        propertyDescriptorResolver = ServiceLoader.load(PropertyDescriptorResolver.class, applicationClassLoader).stream()
+                .findFirst()
+                .map(ServiceLoader.Provider::get)
+                .orElseThrow(() -> new FacesException("No PropertyDescriptorResolver SPI service found"));
+    }
+
     public static PrimeApplicationContext getCurrentInstance(FacesContext facesContext) {
         return getCurrentInstance(facesContext, true);
     }
@@ -355,7 +357,7 @@ public class PrimeApplicationContext {
         return metadataTransformers;
     }
 
-    public ConcurrentHashMap<String, Map<String, PropertyDescriptor>> getPropertyDescriptorCache() {
-        return propertyDescriptorCache;
+    public PropertyDescriptorResolver getPropertyDescriptorResolver() {
+        return propertyDescriptorResolver;
     }
 }
diff --git a/primefaces/src/main/java/org/primefaces/model/AbstractLazyDataModel.java b/primefaces/src/main/java/org/primefaces/model/AbstractLazyDataModel.java
deleted file mode 100644
index cab89b5a10..0000000000
--- a/primefaces/src/main/java/org/primefaces/model/AbstractLazyDataModel.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * The MIT License
- *
- * Copyright (c) 2009-2023 PrimeTek Informatics
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-package org.primefaces.model;
-
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import javax.el.ELException;
-import javax.faces.component.UIComponent;
-import javax.faces.context.FacesContext;
-import javax.faces.convert.Converter;
-import javax.faces.convert.ConverterException;
-import org.primefaces.util.BeanUtils;
-
-public abstract class AbstractLazyDataModel<T> extends LazyDataModel<T> {
-
-    private static final Logger LOG = Logger.getLogger(AbstractLazyDataModel.class.getName());
-
-    public AbstractLazyDataModel() {
-        super();
-    }
-
-    public AbstractLazyDataModel(Converter<T> rowKeyConverter) {
-        super(rowKeyConverter);
-    }
-
-    protected Object convertToType(Object value, Class valueType) {
-        // skip null
-        if (value == null) {
-            return null;
-        }
-
-        // its already the same type
-        if (valueType.isAssignableFrom(value.getClass())) {
-            return value;
-        }
-
-        FacesContext context = FacesContext.getCurrentInstance();
-
-        // primivites dont need complex conversion, try the ELContext first
-        if (BeanUtils.isPrimitiveOrPrimitiveWrapper(valueType)) {
-            try {
-                return context.getELContext().convertToType(value, valueType);
-            }
-            catch (ELException e) {
-                LOG.log(Level.INFO, e, () -> "Could not convert '" + value + "' to " + valueType + " via ELContext!");
-            }
-        }
-
-        Converter targetConverter = context.getApplication().createConverter(valueType);
-        if (targetConverter == null) {
-            LOG.log(Level.FINE, () -> "Skip conversion as no converter was found for " + valueType
-                    + "; Create a JSF Converter for it or overwrite Object convertToType(String value, Class<?> valueType)!");
-            return value;
-        }
-
-        Converter sourceConverter = context.getApplication().createConverter(value.getClass());
-        if (sourceConverter == null) {
-            LOG.log(Level.FINE, () -> "Skip conversion as no converter was found for " + value.getClass()
-                    + "; Create a JSF Converter for it or overwrite Object convertToType(String value, Class<?> valueType)!");
-        }
-
-        // first convert the object to string
-        String stringValue = sourceConverter == null
-                ? value.toString()
-                : sourceConverter.getAsString(context, UIComponent.getCurrentComponent(context), value);
-
-        // now convert the string to the required target
-        try {
-            return targetConverter.getAsObject(context, UIComponent.getCurrentComponent(context), stringValue);
-        }
-        catch (ConverterException e) {
-            LOG.log(Level.INFO, e, () -> "Could not convert '" + stringValue + "' to " + valueType + " via " + targetConverter.getClass().getName());
-            return value;
-        }
-    }
-}
diff --git a/primefaces/src/main/java/org/primefaces/model/DefaultLazyDataModel.java b/primefaces/src/main/java/org/primefaces/model/DefaultLazyDataModel.java
new file mode 100644
index 0000000000..e8edecab52
--- /dev/null
+++ b/primefaces/src/main/java/org/primefaces/model/DefaultLazyDataModel.java
@@ -0,0 +1,243 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009-2023 PrimeTek Informatics
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.primefaces.model;
+
+import java.io.Serializable;
+import java.util.*;
+import java.util.stream.Collectors;
+import javax.faces.component.UIComponent;
+import javax.faces.context.FacesContext;
+import javax.faces.convert.Converter;
+
+import org.primefaces.component.api.UITable;
+import org.primefaces.context.PrimeApplicationContext;
+import org.primefaces.model.filter.FilterConstraint;
+import org.primefaces.util.*;
+
+public class DefaultLazyDataModel<T> extends LazyDataModel<T> {
+
+    private String rowKeyField;
+    private FilterConstraint filter;
+    private Sorter<T> sorter;
+    private SerializableSupplier<List<T>> valuesSupplier;
+    private SerializableFunction<T, Object> rowKeyProvider;
+    private SerializablePredicate<T> skipFiltering;
+
+    /**
+     * For serialization only
+     */
+    public DefaultLazyDataModel() {
+        // NOOP
+    }
+
+    @Override
+    public int count(Map<String, FilterMeta> filterBy) {
+        List<T> values = valuesSupplier.get();
+        List<T> filteredValues = filter(values, filterBy);
+        return filteredValues.size();
+    }
+
+    @Override
+    public List<T> load(int first, int pageSize, Map<String, SortMeta> sortBy, Map<String, FilterMeta> filterBy) {
+        List<T> values = valuesSupplier.get();
+        List<T> filteredValues = filter(values, filterBy);
+
+        sort(filteredValues);
+
+        if (pageSize == 0) {
+            return filteredValues;
+        }
+
+        int last = first + pageSize;
+        if (last > filteredValues.size()) {
+            last = filteredValues.size();
+        }
+        return filteredValues.subList(first, last);
+    }
+
+    protected void sort(List<T> values) {
+        if (values == null || values.isEmpty()) {
+            return;
+        }
+
+        FacesContext context = FacesContext.getCurrentInstance();
+        values.sort(SortTableComparator.comparingField(context, (UITable) UIComponent.getCurrentComponent(context)));
+
+        if (sorter != null) {
+            values.sort(sorter);
+        }
+    }
+
+    protected List<T> filter(List<T> values, Map<String, FilterMeta> filterBy) {
+        if (values == null || values.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        FacesContext context = FacesContext.getCurrentInstance();
+        PropertyDescriptorResolver propResolver = PrimeApplicationContext.getCurrentInstance(context).getPropertyDescriptorResolver();
+        Locale locale = LocaleUtils.getCurrentLocale(context);
+
+        FilterMeta globalFilter = filterBy.get(FilterMeta.GLOBAL_FILTER_KEY);
+        boolean hasGlobalFilter = (globalFilter != null && globalFilter.isActive()) || filter != null;
+
+        return values.stream()
+                .filter(obj -> {
+                    if (skipFiltering != null && skipFiltering.test(obj)) {
+                        return true;
+                    }
+
+                    boolean localMatch = true;
+                    boolean globalMatch = false;
+
+                    if (hasGlobalFilter) {
+                        if (globalFilter != null && globalFilter.isActive()) {
+                            globalMatch = globalFilter.getConstraint().isMatching(context, obj, globalFilter.getFilterValue(), locale);
+                        }
+                        if (filter != null && !globalMatch) {
+                            globalMatch = filter.isMatching(context, obj, null, locale);
+                        }
+                    }
+
+                    for (FilterMeta filterMeta : filterBy.values()) {
+                        if (filterMeta.getField() == null || filterMeta.getFilterValue() == null || filterMeta.isGlobalFilter()) {
+                            continue;
+                        }
+
+                        Object fieldValue = propResolver.getValue(obj, filterMeta.getField());
+                        Object filterValue = filterMeta.getFilterValue();
+                        Object convertedFilterValue = null;
+
+                        if (fieldValue != null) {
+                            Class<?> filterValueClass = filterValue.getClass();
+                            if (filterValueClass.isArray() || Collection.class.isAssignableFrom(filterValueClass)) {
+                                convertedFilterValue = filterValue;
+                            }
+                            else {
+                                convertedFilterValue = ComponentUtils.convertToType(filterValue, fieldValue.getClass(), getClass());
+                            }
+                        }
+
+                        localMatch = filterMeta.getConstraint().isMatching(context, fieldValue, convertedFilterValue, locale);
+                        if (!localMatch) {
+                            break;
+                        }
+                    }
+
+                    boolean matches = localMatch;
+                    if (hasGlobalFilter) {
+                        matches = matches && globalMatch;
+                    }
+                    return matches;
+                })
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public T getRowData(String rowKey) {
+        List<T> values = Objects.requireNonNullElseGet(valuesSupplier.get(), Collections::emptyList);
+        for (T obj : values) {
+            if (Objects.equals(rowKey, getRowKey(obj))) {
+                return obj;
+            }
+        }
+
+        return null;
+    }
+
+    @Override
+    public String getRowKey(T obj) {
+        return String.valueOf(rowKeyProvider.apply(obj));
+    }
+
+    public static <T> Builder<T> builder() {
+        return new Builder<>();
+    }
+
+    public static class Builder<T> {
+        final DefaultLazyDataModel<T> model;
+
+        public Builder() {
+            model = new DefaultLazyDataModel<>();
+        }
+
+        public Builder<T> valueSupplier(SerializableSupplier<List<T>> valuesSupplier) {
+            model.valuesSupplier = valuesSupplier;
+            return this;
+        }
+
+        public Builder<T> rowKeyField(String rowKey) {
+            model.rowKeyField = rowKey;
+            return this;
+        }
+
+        public Builder<T> rowKeyProvider(SerializableFunction<T, Object> rowKeyProvider) {
+            model.rowKeyProvider = rowKeyProvider;
+            return this;
+        }
+
+        public Builder<T> rowKeyConverter(Converter<T> rowKeyConverter) {
+            model.rowKeyConverter = rowKeyConverter;
+            return this;
+        }
+
+        public Builder<T> filter(FilterConstraint filter) {
+            model.filter = filter;
+            return this;
+        }
+
+        public Builder<T> skipFiltering(SerializablePredicate<T> skipFiltering) {
+            model.skipFiltering = skipFiltering;
+            return this;
+        }
+
+        public Builder<T> sorter(Sorter<T> sorter) {
+            model.sorter = sorter;
+            return this;
+        }
+
+        public DefaultLazyDataModel<T> build() {
+            Objects.requireNonNull(model.valuesSupplier, "Value supplier not set");
+
+            boolean requiresRowKeyProvider = model.rowKeyProvider == null && (model.rowKeyConverter != null || model.rowKeyField != null);
+            if (requiresRowKeyProvider) {
+                if (model.rowKeyConverter != null) {
+                    model.rowKeyProvider = model::getRowKeyFromConverter;
+                }
+                else {
+                    Objects.requireNonNull(model.rowKeyField, "rowKeyField is mandatory if no rowKeyProvider nor converter is provided");
+
+                    PropertyDescriptorResolver propResolver =
+                            PrimeApplicationContext.getCurrentInstance(FacesContext.getCurrentInstance()).getPropertyDescriptorResolver();
+                    model.rowKeyProvider = obj -> propResolver.getValue(obj, model.rowKeyField);
+                }
+            }
+            return model;
+        }
+    }
+
+    @FunctionalInterface
+    public interface Sorter<T> extends Comparator<T>, Serializable {
+
+    }
+}
diff --git a/primefaces/src/main/java/org/primefaces/model/JpaLazyDataModel.java b/primefaces/src/main/java/org/primefaces/model/JPALazyDataModel.java
similarity index 64%
rename from primefaces/src/main/java/org/primefaces/model/JpaLazyDataModel.java
rename to primefaces/src/main/java/org/primefaces/model/JPALazyDataModel.java
index 576fbd05cd..6a1d2bbed1 100644
--- a/primefaces/src/main/java/org/primefaces/model/JpaLazyDataModel.java
+++ b/primefaces/src/main/java/org/primefaces/model/JPALazyDataModel.java
@@ -23,17 +23,11 @@
  */
 package org.primefaces.model;
 
-import java.beans.IntrospectionException;
 import java.beans.PropertyDescriptor;
 import java.io.Serializable;
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
 import java.util.*;
-import java.util.logging.Logger;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
-
-import javax.faces.FacesException;
 import javax.faces.context.FacesContext;
 import javax.faces.convert.Converter;
 import javax.persistence.EntityManager;
@@ -41,37 +35,32 @@ import javax.persistence.TypedQuery;
 import javax.persistence.criteria.*;
 import javax.persistence.metamodel.SingularAttribute;
 
-import org.primefaces.util.Constants;
-import org.primefaces.util.LangUtils;
-import org.primefaces.util.Lazy;
-import org.primefaces.util.LocaleUtils;
-import org.primefaces.util.SerializableSupplier;
+import org.primefaces.context.PrimeApplicationContext;
+import org.primefaces.util.*;
 
 /**
  * Basic {@link LazyDataModel} implementation with JPA and Criteria API.
  *
  * @param <T> The model class.
  */
-public class JpaLazyDataModel<T> extends AbstractLazyDataModel<T> implements Serializable {
-
-    private static final Logger LOG = Logger.getLogger(JpaLazyDataModel.class.getName());
+public class JPALazyDataModel<T> extends LazyDataModel<T> implements Serializable {
 
     protected Class<T> entityClass;
-    protected SerializableSupplier<EntityManager> entityManager;
-    protected String rowKey;
+    protected String rowKeyField;
     protected boolean caseSensitive = true;
     protected boolean wildcardSupport = false;
+    protected Class<?> rowKeyType = Long.class;
     protected QueryEnricher<T> queryEnricher;
     protected FilterEnricher<T> filterEnricher;
     protected SortEnricher<T> sortEnricher;
-
-    private transient Lazy<Method> rowKeyGetter;
+    protected SerializableSupplier<EntityManager> entityManager;
+    protected SerializableFunction<T, Object> rowKeyProvider;
 
     /**
      * For serialization only
      */
-    public JpaLazyDataModel() {
-
+    public JPALazyDataModel() {
+        // NOOP
     }
 
     /**
@@ -80,7 +69,8 @@ public class JpaLazyDataModel<T> extends AbstractLazyDataModel<T> implements Ser
      * @param entityClass The entity class
      * @param entityManager The {@link EntityManager}
      */
-    public JpaLazyDataModel(Class<T> entityClass, SerializableSupplier<EntityManager> entityManager) {
+    @Deprecated
+    public JPALazyDataModel(Class<T> entityClass, SerializableSupplier<EntityManager> entityManager) {
         this.entityClass = entityClass;
         this.entityManager = entityManager;
     }
@@ -90,13 +80,13 @@ public class JpaLazyDataModel<T> extends AbstractLazyDataModel<T> implements Ser
      *
      * @param entityClass The entity class
      * @param entityManager The {@link EntityManager}
-     * @param rowKey The name of the rowKey property (e.g. "id")
+     * @param rowKeyField The name of the rowKey property (e.g. "id")
      * @deprecated use the builder instead
      */
     @Deprecated
-    public JpaLazyDataModel(Class<T> entityClass, SerializableSupplier<EntityManager> entityManager, String rowKey) {
+    public JPALazyDataModel(Class<T> entityClass, SerializableSupplier<EntityManager> entityManager, String rowKeyField) {
         this(entityClass, entityManager);
-        this.rowKey = rowKey;
+        this.rowKeyField = rowKeyField;
     }
 
     /**
@@ -108,7 +98,7 @@ public class JpaLazyDataModel<T> extends AbstractLazyDataModel<T> implements Ser
      * @deprecated use the builder instead
      */
     @Deprecated
-    public JpaLazyDataModel(Class<T> entityClass, SerializableSupplier<EntityManager> entityManager, Converter<T> rowKeyConverter) {
+    public JPALazyDataModel(Class<T> entityClass, SerializableSupplier<EntityManager> entityManager, Converter<T> rowKeyConverter) {
         super(rowKeyConverter);
         this.entityClass = entityClass;
         this.entityManager = entityManager;
@@ -116,7 +106,7 @@ public class JpaLazyDataModel<T> extends AbstractLazyDataModel<T> implements Ser
 
     @Override
     public int count(Map<String, FilterMeta> filterBy) {
-        EntityManager em = this.entityManager.get();
+        EntityManager em = entityManager.get();
 
         CriteriaBuilder cb = em.getCriteriaBuilder();
         CriteriaQuery<Long> cq = cb.createQuery(Long.class);
@@ -131,7 +121,7 @@ public class JpaLazyDataModel<T> extends AbstractLazyDataModel<T> implements Ser
 
     @Override
     public List<T> load(int first, int pageSize, Map<String, SortMeta> sortBy, Map<String, FilterMeta> filterBy) {
-        EntityManager em = this.entityManager.get();
+        EntityManager em = entityManager.get();
 
         CriteriaBuilder cb = em.getCriteriaBuilder();
         CriteriaQuery<T> cq = cb.createQuery(entityClass);
@@ -149,9 +139,7 @@ public class JpaLazyDataModel<T> extends AbstractLazyDataModel<T> implements Ser
             queryEnricher.enrich(query);
         }
 
-        List<T> result = query.getResultList();
-
-        return result;
+        return query.getResultList();
     }
 
     protected void applyFilters(CriteriaBuilder cb,
@@ -166,13 +154,13 @@ public class JpaLazyDataModel<T> extends AbstractLazyDataModel<T> implements Ser
         if (filterBy != null) {
             FacesContext context = FacesContext.getCurrentInstance();
             Locale locale = LocaleUtils.getCurrentLocale(context);
-
+            PropertyDescriptorResolver propResolver = PrimeApplicationContext.getCurrentInstance(context).getPropertyDescriptorResolver();
             for (FilterMeta filter : filterBy.values()) {
                 if (filter.getField() == null || filter.getFilterValue() == null || filter.isGlobalFilter()) {
                     continue;
                 }
 
-                Field filterField = LangUtils.getFieldRecursive(entityClass, filter.getField());
+                PropertyDescriptor pd = propResolver.get(entityClass, filter.getField());
                 Object filterValue = filter.getFilterValue();
                 Object convertedFilterValue;
 
@@ -181,12 +169,12 @@ public class JpaLazyDataModel<T> extends AbstractLazyDataModel<T> implements Ser
                     convertedFilterValue = filterValue;
                 }
                 else {
-                    convertedFilterValue = convertToType(filterValue, filterField.getType());
+                    convertedFilterValue = ComponentUtils.convertToType(filterValue, pd.getPropertyType(), getClass());
                 }
 
                 Expression fieldExpression = resolveFieldExpression(cb, cq, root, filter.getField());
 
-                Predicate predicate = createPredicate(filter, filterField, root, cb, fieldExpression, convertedFilterValue, locale);
+                Predicate predicate = createPredicate(filter, pd, root, cb, fieldExpression, convertedFilterValue, locale);
                 predicates.add(predicate);
             }
         }
@@ -197,7 +185,7 @@ public class JpaLazyDataModel<T> extends AbstractLazyDataModel<T> implements Ser
 
         if (!predicates.isEmpty()) {
             cq.where(
-                cb.and(predicates.toArray(new Predicate[predicates.size()])));
+                cb.and(predicates.toArray(new Predicate[0])));
         }
     }
 
@@ -210,15 +198,20 @@ public class JpaLazyDataModel<T> extends AbstractLazyDataModel<T> implements Ser
 
     }
 
-    protected Predicate createPredicate(FilterMeta filter, Field filterField,
-            Root<T> root, CriteriaBuilder cb, Expression fieldExpression, Object filterValue, Locale locale) {
+    protected Predicate createPredicate(FilterMeta filter,
+                                        PropertyDescriptor pd,
+                                        Root<T> root,
+                                        CriteriaBuilder cb,
+                                        Expression fieldExpression,
+                                        Object filterValue,
+                                        Locale locale) {
 
-        Lazy<Expression<String>> fieldExpressionAsString = new Lazy(() -> caseSensitive
+        Supplier<Expression<String>> fieldExpressionAsString = () -> caseSensitive
                 ? fieldExpression.as(String.class)
-                : cb.upper(fieldExpression.as(String.class)));
-        Lazy<Collection<Object>> filterValueAsCollection = new Lazy(
-                () -> filterValue.getClass().isArray() ? Arrays.asList((Object[]) filterValue)
-                        : (Collection<Object>) filterValue);
+                : cb.upper(fieldExpression.as(String.class));
+        Supplier<Collection<Object>> filterValueAsCollection = () -> filterValue.getClass().isArray()
+                        ? Arrays.asList((Object[]) filterValue)
+                        : (Collection<Object>) filterValue;
 
         switch (filter.getMatchMode()) {
             case STARTS_WITH:
@@ -335,214 +328,150 @@ public class JpaLazyDataModel<T> extends AbstractLazyDataModel<T> implements Ser
 
     @Override
     public T getRowData(String rowKey) {
-        Converter<T> rowKeyConverter = getRowKeyConverter();
         if (rowKeyConverter != null) {
             return super.getRowData(rowKey);
         }
 
-        if (this.rowKey != null) {
-            Object convertedRowKey = convertToType(rowKey, getRowKeyGetter().getReturnType());
-
-            EntityManager em = this.entityManager.get();
+        Object convertedRowKey = ComponentUtils.convertToType(rowKey, rowKeyType, getClass());
 
-            CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
-            CriteriaQuery<T> cq = criteriaBuilder.createQuery(entityClass);
-            Root<T> root = cq.from(entityClass);
-            cq.select(root).where(criteriaBuilder.equal(root.get(this.rowKey), convertedRowKey));
+        EntityManager em = entityManager.get();
 
-            TypedQuery<T> query = em.createQuery(cq);
-            return query.getSingleResult();
-        }
+        CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
+        CriteriaQuery<T> cq = criteriaBuilder.createQuery(entityClass);
+        Root<T> root = cq.from(entityClass);
+        cq.select(root).where(criteriaBuilder.equal(root.get(rowKeyField), convertedRowKey));
 
-        throw new UnsupportedOperationException(
-                getMessage("Provide a rowKey / rowKey-Converter or implement getRowData(String rowKey) in %s"
-                        + ", when basic rowKey algorithm is not used [component=%s,view=%s]."));
+        TypedQuery<T> query = em.createQuery(cq);
+        return query.getSingleResult();
     }
 
     @Override
-    public String getRowKey(T object) {
-        Converter<T> rowKeyConverter = getRowKeyConverter();
-        if (rowKeyConverter != null) {
-            return super.getRowKey(object);
-        }
-
-        if (this.rowKey != null) {
-            try {
-                Object rowKeyValue = getRowKeyGetter().invoke(object);
-                return rowKeyValue == null ? null : rowKeyValue.toString();
-            }
-            catch (InvocationTargetException | IllegalAccessException e) {
-                throw new FacesException("Could not invoke getter for " + this.rowKey + " on " + entityClass.getName(), e);
-            }
-        }
-
-        throw new UnsupportedOperationException(
-                getMessage("Provide a rowKey / rowKey-Converter or implement getRowKey(T object) in %s"
-                        + ", when basic rowKey algorithm is not used [component=%s,view=%s]."));
+    public String getRowKey(T obj) {
+        return String.valueOf(rowKeyProvider.apply(obj));
     }
 
-    protected Method getRowKeyGetter() {
-        if (rowKeyGetter == null) {
-            rowKeyGetter = new Lazy<>(() -> {
-                try {
-                    return new PropertyDescriptor(rowKey, entityClass).getReadMethod();
-                }
-                catch (IntrospectionException e) {
-                    throw new FacesException("Could not access " + rowKey + " on " + entityClass.getName(), e);
-                }
-            });
-        }
-        return rowKeyGetter.get();
+    public static <T> Builder<T> builder() {
+        return new Builder<>();
     }
 
-    public String getRowKey() {
-        return rowKey;
-    }
+    public static class Builder<T> {
+        private final JPALazyDataModel<T> model;
 
-    public void setRowKey(String rowKey) {
-        // reset cache
-        if (!Objects.equals(rowKey, this.rowKey)) {
-            rowKeyGetter = null;
+        public Builder() {
+            model = new JPALazyDataModel<>(null, null, (String) null);
         }
-        this.rowKey = rowKey;
-    }
-
-    public void setRowKey(SingularAttribute<T, ?> rowKeyMetamodel) {
-        setRowKey(rowKeyMetamodel.getName());
-    }
-
-    public boolean isCaseSensitive() {
-        return caseSensitive;
-    }
-
-    public void setCaseSensitive(boolean caseSensitive) {
-        this.caseSensitive = caseSensitive;
-    }
-
-    public boolean isWildcardSupport() {
-        return wildcardSupport;
-    }
-
-    public void setWildcardSupport(boolean wildcardSupport) {
-        this.wildcardSupport = wildcardSupport;
-    }
-
-    public QueryEnricher<T> getQueryEnricher() {
-        return queryEnricher;
-    }
-
-    public void setQueryEnricher(QueryEnricher<T> queryEnricher) {
-        this.queryEnricher = queryEnricher;
-    }
 
-    public FilterEnricher<T> getFilterEnricher() {
-        return filterEnricher;
-    }
-
-    public void setFilterEnricher(FilterEnricher<T> filterEnricher) {
-        this.filterEnricher = filterEnricher;
-    }
-
-    public SortEnricher<T> getSortEnricher() {
-        return sortEnricher;
-    }
-
-    public void setSortEnricher(SortEnricher<T> sortEnricher) {
-        this.sortEnricher = sortEnricher;
-    }
-
-
-
-
-
-    public static <T> Builder<T> builder(Class<T> entityClass, SerializableSupplier<EntityManager> entityManager) {
-        return new Builder<>(entityClass, entityManager);
-    }
+        public Builder<T> entityClass(Class<T> entityClass) {
+            model.entityClass = entityClass;
+            return this;
+        }
 
-    public static class Builder<T> {
-        private final Class<T> entityClass;
-        private final SerializableSupplier<EntityManager> entityManager;
-        private String rowKey;
-        private Converter<T> rowKeyConverter;
-        private boolean caseSensitive = true;
-        private boolean wildcardSupport = false;
-        private QueryEnricher<T> queryEnricher;
-        private FilterEnricher<T> filterEnricher;
-        private SortEnricher<T> sortEnricher;
-
-        public Builder(Class<T> entityClass, SerializableSupplier<EntityManager> entityManager) {
-            this.entityClass = entityClass;
-            this.entityManager = entityManager;
+        public Builder<T> entityManager(SerializableSupplier<EntityManager> entityManager) {
+            model.entityManager = entityManager;
+            return this;
         }
 
         public Builder<T> rowKeyConverter(Converter<T> rowKeyConverter) {
-            this.rowKeyConverter = rowKeyConverter;
+            model.rowKeyConverter = rowKeyConverter;
+            return this;
+        }
+
+        public Builder<T> rowKeyProvider(SerializableFunction<T, Object> rowKeyProvider) {
+            model.rowKeyProvider = rowKeyProvider;
             return this;
         }
 
+        @Deprecated
         public Builder<T> rowKey(String rowKey) {
-            this.rowKey = rowKey;
+            model.rowKeyField = rowKey;
             return this;
         }
 
+        @Deprecated
         public Builder<T> rowKey(SingularAttribute<T, ?> rowKeyMetamodel) {
-            this.rowKey = rowKeyMetamodel.getName();
+            model.rowKeyField = rowKeyMetamodel.getName();
+            return this;
+        }
+
+        public Builder<T> rowKeyField(String rowKey) {
+            model.rowKeyField = rowKey;
+            return this;
+        }
+
+        public Builder<T> rowKeyField(SingularAttribute<T, ?> rowKeyMetamodel) {
+            model.rowKeyField = rowKeyMetamodel.getName();
             return this;
         }
 
         public Builder<T> caseSensitive(boolean caseSensitive) {
-            this.caseSensitive = caseSensitive;
+            model.caseSensitive = caseSensitive;
             return this;
         }
 
         public Builder<T> wildcardSupport(boolean wildcardSupport) {
-            this.wildcardSupport = wildcardSupport;
+            model.wildcardSupport = wildcardSupport;
             return this;
         }
 
         public Builder<T> queryEnricher(QueryEnricher<T> queryEnricher) {
-            this.queryEnricher = queryEnricher;
+            model.queryEnricher = queryEnricher;
             return this;
         }
 
         public Builder<T> filterEnricher(FilterEnricher<T> filterEnricher) {
-            this.filterEnricher = filterEnricher;
+            model.filterEnricher = filterEnricher;
             return this;
         }
 
         public Builder<T> sortEnricher(SortEnricher<T> sortEnricher) {
-            this.sortEnricher = sortEnricher;
+            model.sortEnricher = sortEnricher;
             return this;
         }
 
-        public JpaLazyDataModel<T> build() {
-            JpaLazyDataModel<T> model = new JpaLazyDataModel<>(entityClass, entityManager);
-            model.rowKey = rowKey;
-            model.caseSensitive = caseSensitive;
-            model.wildcardSupport = wildcardSupport;
-            model.queryEnricher = queryEnricher;
-            model.filterEnricher = filterEnricher;
-            model.sortEnricher = sortEnricher;
-            model.setRowKeyConverter(rowKeyConverter);
+        public Builder<T> rowKeyType(Class<?> rowKeyType) {
+            model.rowKeyType = rowKeyType;
+            return this;
+        }
 
+        public JPALazyDataModel<T> build() {
+            Objects.requireNonNull(model.entityClass, "entityClass not set");
+            Objects.requireNonNull(model.entityManager, "entityManager not set");
+
+            boolean selectionEnabled = model.rowKeyProvider != null || model.rowKeyConverter != null || model.rowKeyField != null;
+            if (selectionEnabled) {
+                Objects.requireNonNull(model.rowKeyField, "rowKeyField is mandatory for selection");
+
+                if (model.rowKeyProvider == null) {
+                    if (model.rowKeyConverter != null) {
+                        model.rowKeyProvider = model::getRowKeyFromConverter;
+                    }
+                    else {
+                        PropertyDescriptorResolver propResolver =
+                                PrimeApplicationContext.getCurrentInstance(FacesContext.getCurrentInstance()).getPropertyDescriptorResolver();
+                        model.rowKeyType = Objects.requireNonNullElseGet(model.rowKeyType,
+                                () -> propResolver.get(model.entityClass, model.rowKeyField).getPropertyType());
+                        model.rowKeyProvider = obj -> propResolver.getValue(obj, model.rowKeyField);
+                    }
+                }
+            }
             return model;
         }
     }
 
     @FunctionalInterface
-    public static interface QueryEnricher<T> extends Serializable {
+    public interface QueryEnricher<T> extends Serializable {
 
         void enrich(TypedQuery<T> query);
     }
 
     @FunctionalInterface
-    public static interface SortEnricher<T> extends Serializable {
+    public interface SortEnricher<T> extends Serializable {
 
         void enrich(Map<String, SortMeta> sortBy, CriteriaBuilder cb, CriteriaQuery<T> cq, Root<T> root, List<Order> orders);
     }
 
     @FunctionalInterface
-    public static interface FilterEnricher<T> extends Serializable {
+    public interface FilterEnricher<T> extends Serializable {
 
         void enrich(Map<String, FilterMeta> filterBy, CriteriaBuilder cb, CriteriaQuery<?> cq, Root<T> root, List<Predicate> predicates);
     }
diff --git a/primefaces/src/main/java/org/primefaces/model/LazyDataModel.java b/primefaces/src/main/java/org/primefaces/model/LazyDataModel.java
index ad7449c52c..5e57b59740 100644
--- a/primefaces/src/main/java/org/primefaces/model/LazyDataModel.java
+++ b/primefaces/src/main/java/org/primefaces/model/LazyDataModel.java
@@ -23,27 +23,32 @@
  */
 package org.primefaces.model;
 
-import javax.faces.component.UIComponent;
-import javax.faces.context.FacesContext;
-import javax.faces.model.ListDataModel;
 import java.io.Serializable;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import javax.faces.component.UIComponent;
+import javax.faces.context.FacesContext;
 import javax.faces.convert.Converter;
+import javax.faces.model.DataModel;
 import javax.faces.model.DataModelEvent;
 import javax.faces.model.DataModelListener;
 
 /**
  * DataModel to deal with huge datasets with by lazy loading, page by page.
  *
+ * As long {@link DataModel} is not serializable,
+ * see <a href="https://github.com/jakartaee/faces/issues/1585">...</a>,
+ * do no extend from {@link javax.faces.model.ListDataModel}
+ * see #7699
+ *
  * @param <T> The model class.
  */
-public abstract class LazyDataModel<T> extends ListDataModel<T> implements SelectableDataModel<T>, Serializable {
+public abstract class LazyDataModel<T> extends DataModel<T> implements SelectableDataModel<T>, Serializable {
 
     private static final long serialVersionUID = 1L;
 
-    private Converter<T> rowKeyConverter;
+    protected Converter<T> rowKeyConverter;
     private int rowCount;
     private int pageSize;
 
@@ -99,7 +104,7 @@ public abstract class LazyDataModel<T> extends ListDataModel<T> implements Selec
     public T getRowData(String rowKey) {
         if (rowKeyConverter != null) {
             FacesContext context = FacesContext.getCurrentInstance();
-            return (T) rowKeyConverter.getAsObject(context, UIComponent.getCurrentComponent(context), rowKey);
+            return rowKeyConverter.getAsObject(context, UIComponent.getCurrentComponent(context), rowKey);
         }
 
         throw new UnsupportedOperationException(
@@ -135,8 +140,7 @@ public abstract class LazyDataModel<T> extends ListDataModel<T> implements Selec
     @Override
     public String getRowKey(T object) {
         if (rowKeyConverter != null) {
-            FacesContext context = FacesContext.getCurrentInstance();
-            return rowKeyConverter.getAsString(context, UIComponent.getCurrentComponent(context), object);
+            return getRowKeyFromConverter(object);
         }
 
         throw new UnsupportedOperationException(
@@ -144,6 +148,11 @@ public abstract class LazyDataModel<T> extends ListDataModel<T> implements Selec
                         + ", when basic rowKey algorithm is not used [component=%s,view=%s]."));
     }
 
+    protected String getRowKeyFromConverter(T object) {
+        FacesContext context = FacesContext.getCurrentInstance();
+        return rowKeyConverter.getAsString(context, UIComponent.getCurrentComponent(context), object);
+    }
+
     protected String getMessage(String msg) {
         FacesContext facesContext = FacesContext.getCurrentInstance();
         String viewId = facesContext.getViewRoot().getViewId();
@@ -152,7 +161,6 @@ public abstract class LazyDataModel<T> extends ListDataModel<T> implements Selec
         return String.format(msg, getClass().getName(), clientId, viewId);
     }
 
-
     @Override
     public boolean isRowAvailable() {
         if (data == null) {
@@ -239,32 +247,4 @@ public abstract class LazyDataModel<T> extends ListDataModel<T> implements Selec
     public void setPageSize(int pageSize) {
         this.pageSize = pageSize;
     }
-
-    public Converter<T> getRowKeyConverter() {
-        return rowKeyConverter;
-    }
-
-    public void setRowKeyConverter(Converter<T> rowKeyConverter) {
-        this.rowKeyConverter = rowKeyConverter;
-    }
-
-    /**
-     *
-     * @return
-     * @deprecated please use {@link #getRowKeyConverter()}
-     */
-    @Deprecated
-    public Converter<T> getConverter() {
-        return rowKeyConverter;
-    }
-
-    /**
-     *
-     * @param rowKeyConverter
-     * @@deprecated please use {@link #setRowKeyConverter(javax.faces.convert.Converter)}
-     */
-    @Deprecated
-    public void setConverter(Converter<T> rowKeyConverter) {
-        this.rowKeyConverter = rowKeyConverter;
-    }
 }
diff --git a/primefaces/src/main/java/org/primefaces/model/ReflectionLazyDataModel.java b/primefaces/src/main/java/org/primefaces/model/ReflectionLazyDataModel.java
deleted file mode 100644
index 23591e3488..0000000000
--- a/primefaces/src/main/java/org/primefaces/model/ReflectionLazyDataModel.java
+++ /dev/null
@@ -1,454 +0,0 @@
-/*
- * The MIT License
- *
- * Copyright (c) 2009-2023 PrimeTek Informatics
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-package org.primefaces.model;
-
-import org.primefaces.model.filter.FilterConstraint;
-import org.primefaces.util.SerializableFunction;
-import org.primefaces.util.SerializableSupplier;
-
-import javax.faces.FacesException;
-import javax.faces.context.FacesContext;
-import java.beans.IntrospectionException;
-import java.beans.PropertyDescriptor;
-import java.io.Serializable;
-import java.lang.reflect.InvocationTargetException;
-import java.text.Collator;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-import javax.faces.convert.Converter;
-import org.primefaces.context.PrimeApplicationContext;
-import org.primefaces.util.LocaleUtils;
-
-public class ReflectionLazyDataModel<T> extends AbstractLazyDataModel<T> {
-
-    private SerializableSupplier<List<T>> valuesSupplier;
-    private String rowKey;
-    private Function<T, String> rowKeyProvider;
-    private FilterConstraint filter;
-    private SerializableFunction<T, Boolean> skipFiltering;
-    private Sorter<T> sorter;
-
-    /**
-     * For serialization only
-     */
-    public ReflectionLazyDataModel() {
-
-    }
-
-    public ReflectionLazyDataModel(SerializableSupplier<List<T>> valuesSupplier) {
-        this.valuesSupplier = valuesSupplier;
-    }
-
-    @Override
-    public int count(Map<String, FilterMeta> filterBy) {
-        List<T> values = this.valuesSupplier.get();
-        List<T> filteredValues = filter(values, filterBy);
-
-        return filteredValues.size();
-    }
-
-    @Override
-    public List<T> load(int first, int pageSize, Map<String, SortMeta> sortBy, Map<String, FilterMeta> filterBy) {
-        List<T> values = this.valuesSupplier.get();
-        List<T> filteredValues = filter(values, filterBy);
-
-        sort(sortBy, filteredValues);
-
-        if (pageSize == 0) {
-            return filteredValues;
-        }
-
-        int last = first + pageSize;
-        if (last > filteredValues.size()) {
-            last = filteredValues.size();
-        }
-        return filteredValues.subList(first, last);
-    }
-
-    public void sort(Map<String, SortMeta> sortBy, List<T> values) {
-        if (values == null || values.isEmpty()) {
-            return;
-        }
-
-        FacesContext context = FacesContext.getCurrentInstance();
-        PrimeApplicationContext primeApplicationContext = PrimeApplicationContext.getCurrentInstance(context);
-        Locale locale = LocaleUtils.getCurrentLocale(context);
-        Collator collator = Collator.getInstance(locale);
-
-        values.sort((obj1, obj2) -> {
-            for (SortMeta sortMeta : sortBy.values()) {
-                Object value1 = getFieldValue(primeApplicationContext, obj1, sortMeta.getField());
-                Object value2 = getFieldValue(primeApplicationContext, obj2, sortMeta.getField());
-
-                try {
-                    int result;
-
-                    if (sortMeta.getFunction() == null) {
-                        //Empty check
-                        if (value1 == null && value2 == null) {
-                            result = 0;
-                        }
-                        else if (value1 == null) {
-                            result = sortMeta.getNullSortOrder();
-                        }
-                        else if (value2 == null) {
-                            result = -1 * sortMeta.getNullSortOrder();
-                        }
-                        else if (value1 instanceof String && value2 instanceof String) {
-                            if (sortMeta.isCaseSensitiveSort()) {
-                                result = collator.compare(value1, value2);
-                            }
-                            else {
-                                String str1 = (((String) value1).toLowerCase(locale));
-                                String str2 = (((String) value2).toLowerCase(locale));
-
-                                result = collator.compare(str1, str2);
-                            }
-                        }
-                        else {
-                            result = ((Comparable<Object>) value1).compareTo(value2);
-                        }
-                    }
-                    else {
-                        result = (Integer) sortMeta.getFunction().invoke(context.getELContext(), new Object[] {value1, value2});
-                    }
-
-                    return sortMeta.getOrder().isAscending() ? result : -1 * result;
-                }
-                catch (Exception e) {
-                    throw new FacesException(e);
-                }
-            }
-            return 0;
-        });
-
-        if (sorter != null) {
-            values.sort(sorter);
-        }
-    }
-
-    public List<T> filter(List<T> values, Map<String, FilterMeta> filterBy) {
-        if (values == null || values.isEmpty()) {
-            return Collections.emptyList();
-        }
-
-        FacesContext context = FacesContext.getCurrentInstance();
-        PrimeApplicationContext primeApplicationContext = PrimeApplicationContext.getCurrentInstance(context);
-        Locale locale = LocaleUtils.getCurrentLocale(context);
-
-        FilterMeta globalFilter = filterBy.get(FilterMeta.GLOBAL_FILTER_KEY);
-        boolean hasGlobalFilter = (globalFilter != null && globalFilter.isActive()) || filter != null;
-
-        return values.stream()
-            .filter(obj -> {
-                if (skipFiltering != null && Boolean.TRUE.equals(skipFiltering.apply(obj))) {
-                    return true;
-                }
-
-                boolean localMatch = true;
-                boolean globalMatch = false;
-
-                if (hasGlobalFilter) {
-                    if (globalFilter != null && globalFilter.isActive()) {
-                        globalMatch = globalFilter.getConstraint().isMatching(context, obj, globalFilter.getFilterValue(), locale);
-                    }
-                    if (filter != null && !globalMatch) {
-                        globalMatch = filter.isMatching(context, obj, null, locale);
-                    }
-                }
-
-                for (FilterMeta filterMeta : filterBy.values()) {
-                    if (filterMeta.getField() == null || filterMeta.getFilterValue() == null || filterMeta.isGlobalFilter()) {
-                        continue;
-                    }
-
-                    Object fieldValue = getFieldValue(primeApplicationContext, obj, filterMeta.getField());
-                    Object filterValue = filterMeta.getFilterValue();
-                    Object convertedFilterValue;
-
-                    Class<?> filterValueClass = filterValue.getClass();
-                    if (filterValueClass.isArray() || Collection.class.isAssignableFrom(filterValueClass)) {
-                        convertedFilterValue = filterValue;
-                    }
-                    else {
-                        Class<?> fieldType = getFieldType(primeApplicationContext, fieldValue, filterMeta.getField());
-                        convertedFilterValue = convertToType(filterValue, fieldType);
-                    }
-
-                    localMatch = filterMeta.getConstraint().isMatching(context, fieldValue, convertedFilterValue, locale);
-                    if (!localMatch) {
-                        break;
-                    }
-                }
-
-                boolean matches = localMatch;
-                if (hasGlobalFilter) {
-                    matches = matches && globalMatch;
-                }
-                return matches;
-            })
-            .collect(Collectors.toList());
-    }
-
-    protected Class<?> getFieldType(PrimeApplicationContext primeApplicationContext, Object obj, String fieldName) {
-        // join if required; e.g. company.name -> join to company and get "name" field from the joined object
-        while (fieldName.contains(".")) {
-            String currentName = fieldName.substring(0, fieldName.indexOf("."));
-            fieldName = fieldName.substring(currentName.length() + 1);
-
-            try {
-                obj = getPropertyDescriptor(primeApplicationContext, obj.getClass(), fieldName).getReadMethod().invoke(obj);
-            }
-            catch (IllegalAccessException | InvocationTargetException e) {
-                throw new FacesException(e);
-            }
-        }
-
-        return getPropertyDescriptor(primeApplicationContext, obj.getClass(), fieldName).getReadMethod().getReturnType();
-    }
-
-    protected Object getFieldValue(PrimeApplicationContext primeApplicationContext, Object obj, String fieldName) {
-        // join if required; e.g. company.name -> join to company and get "name" field from the joined object
-        while (fieldName.contains(".")) {
-            String currentName = fieldName.substring(0, fieldName.indexOf("."));
-            fieldName = fieldName.substring(currentName.length() + 1);
-
-            try {
-                obj = getPropertyDescriptor(primeApplicationContext, obj.getClass(), fieldName).getReadMethod().invoke(obj);
-            }
-            catch (IllegalAccessException | InvocationTargetException e) {
-                throw new FacesException(e);
-            }
-        }
-
-        try {
-            return getPropertyDescriptor(primeApplicationContext, obj.getClass(), fieldName).getReadMethod().invoke(obj);
-        }
-        catch (IllegalAccessException | InvocationTargetException e) {
-            throw new FacesException(e);
-        }
-    }
-
-    @Override
-    public T getRowData(String rowKey) {
-        Converter<T> rowKeyConverter = getRowKeyConverter();
-        if (rowKeyConverter != null) {
-            return super.getRowData(rowKey);
-        }
-
-        if (this.rowKeyProvider != null || this.rowKey != null) {
-            List<T> values = this.valuesSupplier.get();
-            if (values == null || values.isEmpty()) {
-                return null;
-            }
-
-            PrimeApplicationContext primeApplicationContext =
-                    PrimeApplicationContext.getCurrentInstance(FacesContext.getCurrentInstance());
-
-            for (T obj : values) {
-                String currentRowKey;
-                if (this.rowKeyProvider != null) {
-                    currentRowKey = this.rowKeyProvider.apply(obj);
-                }
-                else {
-                    Object temp = getFieldValue(primeApplicationContext, obj, this.rowKey);
-                    currentRowKey = temp == null ? null : temp.toString();
-                }
-
-                if (Objects.equals(rowKey, currentRowKey)) {
-                    return obj;
-                }
-            }
-
-            return null;
-        }
-
-        throw new UnsupportedOperationException(
-                getMessage("Provide a rowKey / rowKey-Provider / rowKey-Converter or implement getRowData(String rowKey) in %s"
-                        + ", when basic rowKey algorithm is not used [component=%s,view=%s]."));
-    }
-
-    @Override
-    public String getRowKey(T obj) {
-        Converter<T> rowKeyConverter = getRowKeyConverter();
-        if (rowKeyConverter != null) {
-            return super.getRowKey(obj);
-        }
-
-        if (this.rowKeyProvider != null) {
-            return rowKeyProvider.apply(obj);
-        }
-
-        if (this.rowKey != null) {
-            PrimeApplicationContext primeApplicationContext =
-                    PrimeApplicationContext.getCurrentInstance(FacesContext.getCurrentInstance());
-
-            Object rowKeyValue = getFieldValue(primeApplicationContext, obj, rowKey);
-            return rowKeyValue == null ? null : rowKeyValue.toString();
-        }
-
-        throw new UnsupportedOperationException(
-                getMessage("Provide a rowKey / rowKey-Provider / rowKey-Converter or implement getRowKey(T object) in %s"
-                        + ", when basic rowKey algorithm is not used [component=%s,view=%s]."));
-    }
-
-    protected PropertyDescriptor getPropertyDescriptor(PrimeApplicationContext primeApplicationContext, Class<?> clazz, String fieldName) {
-        // use classname instead of clazz, its faster
-        Map<String, PropertyDescriptor> classCache = primeApplicationContext.getPropertyDescriptorCache().get(clazz.getClass().getName());
-
-        // dont use computeIfAbsent, its bit slower
-        if (classCache == null) {
-            classCache = new ConcurrentHashMap<>();
-            primeApplicationContext.getPropertyDescriptorCache().put(clazz.getClass().getName(), classCache);
-        }
-
-        PropertyDescriptor pd = classCache.get(fieldName);
-        if (pd == null) {
-            try {
-                pd = new PropertyDescriptor(fieldName, clazz);
-                classCache.put(fieldName, pd);
-            }
-            catch (IntrospectionException e) {
-                throw new FacesException(e);
-            }
-        }
-
-        return pd;
-    }
-
-    public String getRowKey() {
-        return rowKey;
-    }
-
-    public void setRowKey(String rowKey) {
-        this.rowKey = rowKey;
-    }
-
-    public Function<T, String> getRowKeyProvider() {
-        return rowKeyProvider;
-    }
-
-    public void setRowKeyProvider(Function<T, String> rowKeyProvider) {
-        this.rowKeyProvider = rowKeyProvider;
-    }
-
-    public FilterConstraint getFilter() {
-        return filter;
-    }
-
-    public void setFilter(FilterConstraint filter) {
-        this.filter = filter;
-    }
-
-    public SerializableFunction<T, Boolean> getSkipFiltering() {
-        return skipFiltering;
-    }
-
-    public void setSkipFiltering(SerializableFunction<T, Boolean> skipFiltering) {
-        this.skipFiltering = skipFiltering;
-    }
-
-    public Sorter<T> getSorter() {
-        return sorter;
-    }
-
-    public void setSorter(Sorter<T> sorter) {
-        this.sorter = sorter;
-    }
-
-
-
-    public static <T> Builder<T> builder(SerializableSupplier<List<T>> valuesSupplier) {
-        return new Builder<>(valuesSupplier);
-    }
-
-    public static class Builder<T> {
-        private final SerializableSupplier<List<T>> valuesSupplier;
-        private String rowKey;
-        private SerializableFunction<T, String> rowKeyProvider;
-        private Converter<T> rowKeyConverter;
-        private FilterConstraint filter;
-        private SerializableFunction<T, Boolean> skipFiltering;
-        private Sorter<T> sorter;
-
-        public Builder(SerializableSupplier<List<T>> valuesSupplier) {
-            this.valuesSupplier = valuesSupplier;
-        }
-
-        public Builder<T> rowKey(String rowKey) {
-            this.rowKey = rowKey;
-            return this;
-        }
-
-        public Builder<T> rowKeyProvider(SerializableFunction<T, String> rowKeyProvider) {
-            this.rowKeyProvider = rowKeyProvider;
-            return this;
-        }
-
-        public Builder<T> rowKeyConverter(Converter<T> rowKeyConverter) {
-            this.rowKeyConverter = rowKeyConverter;
-            return this;
-        }
-
-        public Builder<T> filter(FilterConstraint filter) {
-            this.filter = filter;
-            return this;
-        }
-
-        public Builder<T> skipFiltering(SerializableFunction<T, Boolean> skipFiltering) {
-            this.skipFiltering = skipFiltering;
-            return this;
-        }
-
-        public Builder<T> sorter(Sorter<T> sorter) {
-            this.sorter = sorter;
-            return this;
-        }
-
-        public ReflectionLazyDataModel<T> build() {
-            ReflectionLazyDataModel<T> model = new ReflectionLazyDataModel<>(valuesSupplier);
-            model.rowKey = rowKey;
-            model.rowKeyProvider = rowKeyProvider;
-            model.filter = filter;
-            model.skipFiltering = skipFiltering;
-            model.sorter = sorter;
-            model.setRowKeyConverter(rowKeyConverter);
-
-            return model;
-        }
-    }
-
-    @FunctionalInterface
-    public static interface Sorter<T> extends Comparator<T>, Serializable {
-
-    }
-}
\ No newline at end of file
diff --git a/primefaces/src/main/java/org/primefaces/model/SortMeta.java b/primefaces/src/main/java/org/primefaces/model/SortMeta.java
index 4d40056da8..db64f9ca9d 100644
--- a/primefaces/src/main/java/org/primefaces/model/SortMeta.java
+++ b/primefaces/src/main/java/org/primefaces/model/SortMeta.java
@@ -25,7 +25,6 @@ package org.primefaces.model;
 
 import java.io.Serializable;
 import java.util.Objects;
-
 import javax.el.MethodExpression;
 import javax.el.ValueExpression;
 import javax.faces.FacesException;
@@ -54,13 +53,14 @@ public class SortMeta implements Serializable, Comparable<SortMeta> {
     private int nullSortOrder;
     private boolean caseSensitiveSort;
     private boolean headerRow;
+    private boolean dynamic;
 
     public SortMeta() {
         // NOOP
     }
 
     SortMeta(String columnKey, String sortField, SortOrder sortOrder, MethodExpression sortFunction,
-             ValueExpression sortBy, int priority, int nullSortOrder, boolean caseSensitiveSort, boolean headerRow) {
+             ValueExpression sortBy, int priority, int nullSortOrder, boolean caseSensitiveSort, boolean headerRow, boolean dynamic) {
         this.columnKey = columnKey;
         this.field = sortField;
         this.order = sortOrder;
@@ -70,10 +70,12 @@ public class SortMeta implements Serializable, Comparable<SortMeta> {
         this.nullSortOrder = nullSortOrder;
         this.caseSensitiveSort = caseSensitiveSort;
         this.headerRow = headerRow;
+        this.dynamic = dynamic;
     }
 
     public static SortMeta of(FacesContext context, String var, UIColumn column) {
-        if (column instanceof DynamicColumn) {
+        boolean dynamic = column instanceof DynamicColumn;
+        if (dynamic) {
             ((DynamicColumn) column).applyStatelessModel();
         }
 
@@ -107,7 +109,8 @@ public class SortMeta implements Serializable, Comparable<SortMeta> {
                             column.getSortPriority(),
                             column.getNullSortOrder(),
                             column.isCaseSensitiveSort(),
-                            false);
+                            false,
+                            dynamic);
     }
 
     public static SortMeta of(FacesContext context, String var, HeaderRow headerRow) {
@@ -128,7 +131,8 @@ public class SortMeta implements Serializable, Comparable<SortMeta> {
                             MAX_PRIORITY,
                             SortOrder.ASCENDING.intValue(),
                             false,
-                            true);
+                            true,
+                            false);
     }
 
     @Override
@@ -196,6 +200,10 @@ public class SortMeta implements Serializable, Comparable<SortMeta> {
         return caseSensitiveSort;
     }
 
+    public boolean isDynamic() {
+        return dynamic;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/primefaces/src/main/java/org/primefaces/util/ComponentUtils.java b/primefaces/src/main/java/org/primefaces/util/ComponentUtils.java
index da6d464689..a8c5347baa 100644
--- a/primefaces/src/main/java/org/primefaces/util/ComponentUtils.java
+++ b/primefaces/src/main/java/org/primefaces/util/ComponentUtils.java
@@ -23,8 +23,6 @@
  */
 package org.primefaces.util;
 
-import static org.primefaces.renderkit.RendererUtils.getRenderKit;
-
 import java.io.IOException;
 import java.io.Serializable;
 import java.io.UncheckedIOException;
@@ -32,9 +30,11 @@ import java.io.UnsupportedEncodingException;
 import java.net.URLEncoder;
 import java.util.*;
 import java.util.function.Supplier;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
-
+import javax.el.ELException;
 import javax.el.ValueExpression;
 import javax.faces.FacesException;
 import javax.faces.FacesWrapper;
@@ -50,6 +50,7 @@ import javax.faces.context.ExternalContext;
 import javax.faces.context.FacesContext;
 import javax.faces.context.ResponseWriter;
 import javax.faces.convert.Converter;
+import javax.faces.convert.ConverterException;
 import javax.faces.render.Renderer;
 
 import org.primefaces.component.api.*;
@@ -57,6 +58,7 @@ import org.primefaces.config.PrimeConfiguration;
 import org.primefaces.context.PrimeApplicationContext;
 import org.primefaces.context.PrimeRequestContext;
 import org.primefaces.csp.CspResponseWriter;
+import static org.primefaces.renderkit.RendererUtils.getRenderKit;
 
 public class ComponentUtils {
 
@@ -741,4 +743,56 @@ public class ComponentUtils {
     public static boolean isUIRepeat(UIComponent component) {
         return component.getClass().getName().endsWith("UIRepeat");
     }
+
+    public static Object convertToType(Object value, Class<?> valueType, Class<?> logContext) {
+        // skip null
+        if (value == null) {
+            return null;
+        }
+
+        // its already the same type
+        if (valueType.isAssignableFrom(value.getClass())) {
+            return value;
+        }
+
+        FacesContext context = FacesContext.getCurrentInstance();
+        Logger logger = Logger.getLogger(logContext.getName());
+
+        // primivites dont need complex conversion, try the ELContext first
+        if (BeanUtils.isPrimitiveOrPrimitiveWrapper(valueType)) {
+            try {
+                return context.getELContext().convertToType(value, valueType);
+            }
+            catch (ELException e) {
+                logger.log(Level.INFO, e, () -> "Could not convert '" + value + "' to " + valueType + " via ELContext!");
+            }
+        }
+
+        Converter targetConverter = context.getApplication().createConverter(valueType);
+        if (targetConverter == null) {
+            logger.log(Level.FINE, () -> "Skip conversion as no converter was found for " + valueType
+                    + "; Create a JSF Converter for it or overwrite Object convertToType(String value, Class<?> valueType)!");
+            return value;
+        }
+
+        Converter sourceConverter = context.getApplication().createConverter(value.getClass());
+        if (sourceConverter == null) {
+            logger.log(Level.FINE, () -> "Skip conversion as no converter was found for " + value.getClass()
+                    + "; Create a JSF Converter for it or overwrite Object convertToType(String value, Class<?> valueType)!");
+        }
+
+        // first convert the object to string
+        String stringValue = sourceConverter == null
+                ? value.toString()
+                : sourceConverter.getAsString(context, UIComponent.getCurrentComponent(context), value);
+
+        // now convert the string to the required target
+        try {
+            return targetConverter.getAsObject(context, UIComponent.getCurrentComponent(context), stringValue);
+        }
+        catch (ConverterException e) {
+            logger.log(Level.INFO, e, () -> "Could not convert '" + stringValue + "' to " + valueType + " via " + targetConverter.getClass().getName());
+            return value;
+        }
+    }
 }
diff --git a/primefaces/src/main/java/org/primefaces/util/LangUtils.java b/primefaces/src/main/java/org/primefaces/util/LangUtils.java
index d14670ddaf..5cac9f165d 100644
--- a/primefaces/src/main/java/org/primefaces/util/LangUtils.java
+++ b/primefaces/src/main/java/org/primefaces/util/LangUtils.java
@@ -27,7 +27,10 @@ import java.beans.BeanInfo;
 import java.beans.IntrospectionException;
 import java.beans.Introspector;
 import java.beans.PropertyDescriptor;
-import java.lang.reflect.*;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.util.*;
@@ -505,52 +508,4 @@ public class LangUtils {
         }
         return true;
     }
-
-    public static Field getFieldRecursive(Class<?> clazz, String name) {
-        if (clazz == null) {
-            throw new IllegalArgumentException("clazz must not be null!");
-        }
-        if (name == null) {
-            throw new IllegalArgumentException("name must not be null!");
-        }
-
-        Class<?> nextClazz = clazz;
-        String nextName = name;
-        while (nextName.contains(".")) {
-            String currentName = nextName.substring(0, nextName.indexOf("."));
-            nextName = nextName.substring(currentName.length() + 1, nextName.length());
-            Field field = getField(nextClazz, currentName);
-            nextClazz = field.getType();
-        }
-
-        return getField(nextClazz, nextName);
-    }
-
-    public static Field getField(Class<?> clazz, String name) {
-        if (clazz == null) {
-            throw new IllegalArgumentException("clazz must not be null!");
-        }
-        if (name == null) {
-            throw new IllegalArgumentException("name must not be null!");
-        }
-
-        Class<?> current = clazz;
-        while (current != null && current != Object.class) {
-            try {
-                Field field = current.getDeclaredField(name);
-                field.setAccessible(true);
-                return field;
-            }
-            catch (NoSuchFieldException e) {
-                // Try parent
-            }
-            catch (Exception e) {
-                throw new IllegalArgumentException("Cannot access field " + name + " in " + clazz.getName(), e);
-            }
-
-            current = current.getSuperclass();
-        }
-
-        throw new IllegalArgumentException("Cannot find field " + name + " in " + clazz.getName());
-    }
 }
diff --git a/primefaces/src/main/java/org/primefaces/util/PropertyDescriptorResolver.java b/primefaces/src/main/java/org/primefaces/util/PropertyDescriptorResolver.java
new file mode 100644
index 0000000000..4b8a01b2ec
--- /dev/null
+++ b/primefaces/src/main/java/org/primefaces/util/PropertyDescriptorResolver.java
@@ -0,0 +1,99 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009-2023 PrimeTek Informatics
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.primefaces.util;
+
+import java.beans.IntrospectionException;
+import java.beans.PropertyDescriptor;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.regex.Pattern;
+import javax.faces.FacesException;
+
+public interface PropertyDescriptorResolver {
+
+    PropertyDescriptor get(Class<?> klazz, String name);
+
+    Object getValue(Object bean, String name);
+
+    void flush();
+
+    class DefaultResolver implements PropertyDescriptorResolver {
+
+        private static final Pattern NESTED_EXPRESSION_PATTERN = Pattern.compile("\\.");
+        private final Map<String, Map<String, PropertyDescriptor>> pdCache;
+
+        public DefaultResolver() {
+            pdCache = new ConcurrentHashMap<>();
+        }
+
+        @Override
+        public PropertyDescriptor get(Class<?> klazz, String expression) {
+            PropertyDescriptor pd = null;
+            Class<?> parent = klazz;
+
+            for (String field : NESTED_EXPRESSION_PATTERN.split(expression)) {
+                pd = getSimpleProperty(parent, field);
+                parent = pd.getPropertyType();
+            }
+
+            return Objects.requireNonNull(pd);
+        }
+
+        @Override
+        public Object getValue(Object bean, String expression) {
+            try {
+                for (String field : NESTED_EXPRESSION_PATTERN.split(expression)) {
+                    bean = getSimpleProperty(bean.getClass(), field).getReadMethod().invoke(bean);
+                    if (bean == null) {
+                        break;
+                    }
+                }
+
+                return bean;
+            }
+            catch (ReflectiveOperationException e) {
+                throw new FacesException(e);
+            }
+        }
+
+        @Override
+        public void flush() {
+            pdCache.clear();
+        }
+
+        private PropertyDescriptor getSimpleProperty(Class<?> klazz, String field) {
+            String cacheKey = klazz.getName();
+            Map<String, PropertyDescriptor> classCache = pdCache.computeIfAbsent(cacheKey, k -> new ConcurrentHashMap<>());
+            return classCache.computeIfAbsent(field, k -> {
+                try {
+                    return new PropertyDescriptor(k, klazz);
+                }
+                catch (IntrospectionException e) {
+                    throw new FacesException(e);
+                }
+            });
+        }
+    }
+}
diff --git a/primefaces/src/main/java/org/primefaces/util/SerializablePredicate.java b/primefaces/src/main/java/org/primefaces/util/SerializablePredicate.java
new file mode 100644
index 0000000000..3232ee4203
--- /dev/null
+++ b/primefaces/src/main/java/org/primefaces/util/SerializablePredicate.java
@@ -0,0 +1,31 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009-2023 PrimeTek Informatics
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.primefaces.util;
+
+import java.io.Serializable;
+import java.util.function.Predicate;
+
+@FunctionalInterface
+public interface SerializablePredicate<T> extends Predicate<T>, Serializable {
+}
diff --git a/primefaces/src/main/java/org/primefaces/util/SortTableComparator.java b/primefaces/src/main/java/org/primefaces/util/SortTableComparator.java
new file mode 100644
index 0000000000..14da06a85e
--- /dev/null
+++ b/primefaces/src/main/java/org/primefaces/util/SortTableComparator.java
@@ -0,0 +1,192 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009-2023 PrimeTek Informatics
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.primefaces.util;
+
+import java.text.Collator;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
+import javax.el.ValueExpression;
+import javax.faces.FacesException;
+import javax.faces.context.FacesContext;
+
+import org.primefaces.component.api.UITable;
+import org.primefaces.context.PrimeApplicationContext;
+import org.primefaces.model.SortMeta;
+import org.primefaces.model.TreeNode;
+
+public class SortTableComparator implements Comparator<Object> {
+
+    public static final BeanPropertyMapper SORT_BY_VE_MAPPER = new SortByVEMapper();
+    public static final BeanPropertyMapper FIELD_MAPPER = new FieldMapper();
+    public static final BeanPropertyMapper TREE_NODE_MAPPER = new TreeNodeSortByVEMapper();
+
+    private final FacesContext context;
+    private final Collection<SortMeta> sortBy;
+    private final UITable<?> table;
+    private final Locale locale;
+    private final String var;
+    private final Collator collator;
+    private final BeanPropertyMapper mapper;
+    private final AtomicInteger compareResult = new AtomicInteger(0);
+
+    public SortTableComparator(FacesContext context, UITable<?> table, BeanPropertyMapper mapper) {
+        this.context = context;
+        this.table = table;
+        this.sortBy = table.getActiveSortMeta().values();
+        this.var = table.getVar();
+        this.locale = table.resolveDataLocale(context);
+        this.collator = Collator.getInstance(locale);
+        this.mapper = Objects.requireNonNull(mapper, "mapper is necessary to extract property value");
+    }
+
+    @Override
+    public int compare(Object o1, Object o2) {
+        compareResult.set(0); //no local variable, avoid redundant creation: reset to 0 instead
+
+        for (SortMeta sortMeta : sortBy) {
+            if (mapper.isValueExprBased() && sortMeta.isDynamic()) {
+                table.invokeOnColumn(sortMeta.getColumnKey(), column -> {
+                    compareResult.set(compareWithMapper(sortMeta, o1, o2));
+                });
+            }
+            else {
+                compareResult.set(compareWithMapper(sortMeta, o1, o2));
+            }
+
+            if (compareResult.get() != 0) {
+                return compareResult.get();
+            }
+        }
+
+        return 0;
+    }
+
+    private int compareWithMapper(SortMeta sortMeta, Object o1, Object o2) {
+        final Object fv1 = mapper.map(context, var, sortMeta, o1);
+        final Object fv2 = mapper.map(context, var, sortMeta, o2);
+        return compare(context, sortMeta, fv1, fv2, collator, locale);
+    }
+
+    public static int compare(FacesContext context, SortMeta sortMeta, Object value1, Object value2,
+                              Collator collator, Locale locale) {
+        try {
+            int result;
+
+            if (sortMeta.getFunction() == null) {
+                //Empty check
+                if (value1 == null && value2 == null) {
+                    result = 0;
+                }
+                else if (value1 == null) {
+                    result = sortMeta.getNullSortOrder();
+                }
+                else if (value2 == null) {
+                    result = -1 * sortMeta.getNullSortOrder();
+                }
+                else if (value1 instanceof String && value2 instanceof String) {
+                    if (sortMeta.isCaseSensitiveSort()) {
+                        result = collator.compare(value1, value2);
+                    }
+                    else {
+                        String str1 = (((String) value1).toLowerCase(locale));
+                        String str2 = (((String) value2).toLowerCase(locale));
+
+                        result = collator.compare(str1, str2);
+                    }
+                }
+                else {
+                    result = ((Comparable<Object>) value1).compareTo(value2);
+                }
+            }
+            else {
+                result = (Integer) sortMeta.getFunction().invoke(context.getELContext(), new Object[]{value1, value2, sortMeta});
+            }
+
+            return sortMeta.getOrder().isAscending() ? result : -1 * result;
+        }
+        catch (Exception e) {
+            throw new FacesException(e);
+        }
+    }
+
+    public static Comparator<Object> comparingSortByVE(FacesContext context, UITable<?> table) {
+        return new SortTableComparator(context, table, SORT_BY_VE_MAPPER);
+    }
+
+    public static Comparator<Object> comparingField(FacesContext context, UITable<?> table) {
+        return new SortTableComparator(context, table, FIELD_MAPPER);
+    }
+
+    public static Comparator<Object> comparingTreeNodeSortByVE(FacesContext context, UITable<?> table) {
+        return new SortTableComparator(context, table, TREE_NODE_MAPPER);
+    }
+
+    public static class SortByVEMapper implements BeanPropertyMapper {
+
+        @Override
+        public boolean isValueExprBased() {
+            return true;
+        }
+
+        @Override
+        public Object map(FacesContext context, String var, SortMeta sortMeta, Object obj) {
+            ValueExpression ve = sortMeta.getSortBy();
+            context.getExternalContext().getRequestMap().put(var, obj);
+            return ve.getValue(context.getELContext());
+        }
+    }
+
+    public static class TreeNodeSortByVEMapper extends SortByVEMapper {
+
+        @Override
+        public Object map(FacesContext context, String var, SortMeta sortMeta, Object obj) {
+            return super.map(context, var, sortMeta, ((TreeNode) obj).getData());
+        }
+    }
+
+    public static class FieldMapper implements BeanPropertyMapper {
+
+        @Override
+        public boolean isValueExprBased() {
+            return false;
+        }
+
+        @Override
+        public Object map(FacesContext context, String var, SortMeta sortMeta, Object obj) {
+            PropertyDescriptorResolver propResolver =
+                    PrimeApplicationContext.getCurrentInstance(context).getPropertyDescriptorResolver();
+            return propResolver.getValue(obj, sortMeta.getField());
+        }
+    }
+
+    public interface BeanPropertyMapper {
+
+        boolean isValueExprBased();
+
+        Object map(FacesContext context, String var, SortMeta sortMeta, Object obj);
+    }
+}
diff --git a/primefaces/src/main/resources/META-INF/primefaces-p.taglib.xml b/primefaces/src/main/resources/META-INF/primefaces-p.taglib.xml
index e74aea39bc..7596bf1c55 100644
--- a/primefaces/src/main/resources/META-INF/primefaces-p.taglib.xml
+++ b/primefaces/src/main/resources/META-INF/primefaces-p.taglib.xml
@@ -14099,7 +14099,7 @@
     </tag>
     <tag>
         <description>
-            <![CDATA[]]>
+            <![CDATA[Deprecated since 10.0 for removal in 15.0.0)]]>
         </description>
         <tag-name>inputSwitch</tag-name>
         <component>
diff --git a/primefaces/src/main/resources/META-INF/resources/primefaces/core/core.utils.js b/primefaces/src/main/resources/META-INF/resources/primefaces/core/core.utils.js
index e39a4b439b..2f41d31df1 100644
--- a/primefaces/src/main/resources/META-INF/resources/primefaces/core/core.utils.js
+++ b/primefaces/src/main/resources/META-INF/resources/primefaces/core/core.utils.js
@@ -601,7 +601,7 @@ if (!PrimeFaces.utils) {
          * @return {boolean} `true` if the key is a printable key, or `false` otherwise.
          */
         isPrintableKey: function(e) {
-            return e.key.length === 1 || e.key === 'Unidentified';
+            return e && e.key && (e.key.length === 1 || e.key === 'Unidentified');
         },
 
         /**
diff --git a/primefaces/src/main/resources/META-INF/resources/primefaces/datatable/datatable.js b/primefaces/src/main/resources/META-INF/resources/primefaces/datatable/datatable.js
index d588da0151..94902350d3 100644
--- a/primefaces/src/main/resources/META-INF/resources/primefaces/datatable/datatable.js
+++ b/primefaces/src/main/resources/META-INF/resources/primefaces/datatable/datatable.js
@@ -755,7 +755,7 @@ PrimeFaces.widget.DataTable = PrimeFaces.widget.DeferredWidget.extend({
         filter.off('keydown.dataTable-blockenter ' + filterEventName)
         .on('keydown.dataTable-blockenter', PrimeFaces.utils.blockEnterKey)
         .on(filterEventName, function(e) {
-            if (PrimeFaces.utils.ignoreFilterKey(e)) {
+            if (e.key && PrimeFaces.utils.ignoreFilterKey(e)) {
                 return;
             }
 
diff --git a/primefaces/src/main/resources/META-INF/services/org.primefaces.util.PropertyDescriptorResolver b/primefaces/src/main/resources/META-INF/services/org.primefaces.util.PropertyDescriptorResolver
new file mode 100644
index 0000000000..5b7b8d9ad1
--- /dev/null
+++ b/primefaces/src/main/resources/META-INF/services/org.primefaces.util.PropertyDescriptorResolver
@@ -0,0 +1,1 @@
+org.primefaces.util.PropertyDescriptorResolver$DefaultResolver


Test patch:
diff --git a/primefaces-integration-tests/src/main/java/org/primefaces/integrationtests/datatable/DataTable001.java b/primefaces-integration-tests/src/main/java/org/primefaces/integrationtests/datatable/DataTable001.java
index 857370a78f..615c59fe11 100644
--- a/primefaces-integration-tests/src/main/java/org/primefaces/integrationtests/datatable/DataTable001.java
+++ b/primefaces-integration-tests/src/main/java/org/primefaces/integrationtests/datatable/DataTable001.java
@@ -34,7 +34,7 @@ import javax.inject.Named;
 import lombok.Data;
 import org.primefaces.component.datatable.DataTable;
 import org.primefaces.model.LazyDataModel;
-import org.primefaces.model.ReflectionLazyDataModel;
+import org.primefaces.model.DefaultLazyDataModel;
 
 @Named
 @ViewScoped
@@ -55,7 +55,10 @@ public class DataTable001 implements Serializable {
     @PostConstruct
     public void init() {
         progLanguages = service.getLangs();
-        progLanguagesLazyDataModel = ReflectionLazyDataModel.builder(() -> service.getLangs()).build();
+        progLanguagesLazyDataModel = DefaultLazyDataModel.<ProgrammingLanguage>builder()
+                .valueSupplier(() -> service.getLangs())
+                .rowKeyProvider(ProgrammingLanguage::getId)
+                .build();
         globalFilterOnly = false;
     }
 
diff --git a/primefaces-integration-tests/src/main/java/org/primefaces/integrationtests/datatable/DataTable002.java b/primefaces-integration-tests/src/main/java/org/primefaces/integrationtests/datatable/DataTable002.java
index b02643ba12..71aa76d911 100644
--- a/primefaces-integration-tests/src/main/java/org/primefaces/integrationtests/datatable/DataTable002.java
+++ b/primefaces-integration-tests/src/main/java/org/primefaces/integrationtests/datatable/DataTable002.java
@@ -23,16 +23,17 @@
  */
 package org.primefaces.integrationtests.datatable;
 
-import lombok.Data;
-import org.primefaces.event.SelectEvent;
-import org.primefaces.integrationtests.general.utilities.TestUtils;
-
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
 import javax.annotation.PostConstruct;
 import javax.faces.view.ViewScoped;
 import javax.inject.Named;
-import java.io.Serializable;
-import java.util.ArrayList;
-import org.primefaces.model.ReflectionLazyDataModel;
+
+import lombok.Data;
+import org.primefaces.event.SelectEvent;
+import org.primefaces.integrationtests.general.utilities.TestUtils;
+import org.primefaces.model.DefaultLazyDataModel;
 
 @Named
 @ViewScoped
@@ -41,9 +42,9 @@ public class DataTable002 implements Serializable {
 
     private static final long serialVersionUID = -7518459955779385834L;
 
-    protected ArrayList<ProgrammingLanguage> programmingLanguages;
+    protected List<ProgrammingLanguage> programmingLanguages;
     protected ProgrammingLanguageLazyDataModel lazyDataModel;
-    protected ReflectionLazyDataModel<ProgrammingLanguage> reflectionLazyDataModel;
+    protected DefaultLazyDataModel<ProgrammingLanguage> reflectionLazyDataModel;
     protected ProgrammingLanguage selectedProgrammingLanguage;
 
     @PostConstruct
@@ -54,8 +55,9 @@ public class DataTable002 implements Serializable {
                     ((i % 2) == 0) ? ProgrammingLanguage.ProgrammingLanguageType.COMPILED : ProgrammingLanguage.ProgrammingLanguageType.INTERPRETED));
         }
         lazyDataModel = new ProgrammingLanguageLazyDataModel();
-        reflectionLazyDataModel = ReflectionLazyDataModel.builder(() -> programmingLanguages)
-                .rowKeyProvider((obj) -> Integer.toString(obj.getId()))
+        reflectionLazyDataModel = DefaultLazyDataModel.<ProgrammingLanguage>builder()
+                .valueSupplier(() -> programmingLanguages)
+                .rowKeyProvider(ProgrammingLanguage::getId)
                 .build();
     }
 
diff --git a/primefaces/src/test/java/org/primefaces/util/LangUtilsTest.java b/primefaces/src/test/java/org/primefaces/util/LangUtilsTest.java
index 67d5352672..bf6b0d5436 100644
--- a/primefaces/src/test/java/org/primefaces/util/LangUtilsTest.java
+++ b/primefaces/src/test/java/org/primefaces/util/LangUtilsTest.java
@@ -23,16 +23,10 @@
  */
 package org.primefaces.util;
 
-import java.lang.reflect.Field;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
 import java.util.List;
-import org.junit.jupiter.api.Assertions;
 
 import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
 
 public class LangUtilsTest {
 
@@ -125,41 +119,6 @@ public class LangUtilsTest {
         assertEquals("My Über String", LangUtils.toCapitalCase("myÜberString"));
     }
 
-    @Test
-    public void getField() {
-        Field field = null;
-
-        field = LangUtils.getFieldRecursive(AbstractClass.class, "container.string");
-        Assertions.assertNotNull(field);
-        Assertions.assertEquals(Container.class, field.getDeclaringClass());
-        Assertions.assertEquals("string", field.getName());
-
-        field = LangUtils.getFieldRecursive(AbstractClass.class, "container.container.string");
-        Assertions.assertNotNull(field);
-        Assertions.assertEquals(Container.class, field.getDeclaringClass());
-        Assertions.assertEquals("string", field.getName());
-
-        field = LangUtils.getFieldRecursive(AbstractClass.class, "container.container");
-        Assertions.assertNotNull(field);
-        Assertions.assertEquals(Container.class, field.getDeclaringClass());
-        Assertions.assertEquals("container", field.getName());
-
-        field = LangUtils.getField(SimpleClass.class, "strings");
-        Assertions.assertNotNull(field);
-        Assertions.assertEquals(SimpleClass.class, field.getDeclaringClass());
-        Assertions.assertEquals("strings", field.getName());
-
-        Assertions.assertNotNull(LangUtils.getField(AbstractClass.class, "ints"));
-        Assertions.assertNotNull(LangUtils.getField(ConcreteClass.class, "ints"));
-        Assertions.assertNotNull(LangUtils.getField(AbstractGenericClass.class, "values"));
-        Assertions.assertNotNull(LangUtils.getField(ConcreteGenericClass.class, "values"));
-        Assertions.assertNotNull(LangUtils.getField(DetailedConcreteGenericClass.class, "values"));
-
-        Assertions.assertThrows(IllegalArgumentException.class, () -> LangUtils.getField(DetailedConcreteGenericClass.class, "rasdasd"));
-        Assertions.assertThrows(IllegalArgumentException.class, () -> LangUtils.getFieldRecursive(AbstractClass.class, "container2.stringss"));
-        Assertions.assertThrows(IllegalArgumentException.class, () -> LangUtils.getFieldRecursive(AbstractClass.class, "container.stringss"));
-    }
-
     class SimpleClass {
 
         private List<String> strings;
diff --git a/primefaces/src/test/java/org/primefaces/util/PropertyDescriptorResolverTest.java b/primefaces/src/test/java/org/primefaces/util/PropertyDescriptorResolverTest.java
new file mode 100644
index 0000000000..15654f4282
--- /dev/null
+++ b/primefaces/src/test/java/org/primefaces/util/PropertyDescriptorResolverTest.java
@@ -0,0 +1,171 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009-2023 PrimeTek Informatics
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.primefaces.util;
+
+import java.beans.PropertyDescriptor;
+import javax.faces.FacesException;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class PropertyDescriptorResolverTest {
+
+    @Test
+    public void testGetPropertyDescriptor() {
+        PropertyDescriptorResolver propResolver = new PropertyDescriptorResolver.DefaultResolver();
+
+        PropertyDescriptor pd = propResolver.get(A.class, "name");
+        Assertions.assertNotNull(pd);
+        Assertions.assertEquals(String.class, pd.getPropertyType());
+
+        pd = propResolver.get(B.class, "bool1");
+        Assertions.assertNotNull(pd);
+        Assertions.assertEquals(boolean.class, pd.getPropertyType());
+
+        pd = propResolver.get(B.class, "bool2");
+        Assertions.assertNotNull(pd);
+        Assertions.assertEquals(Boolean.class, pd.getPropertyType());
+
+        pd = propResolver.get(C.class, "b.dummyEnum");
+        Assertions.assertNotNull(pd);
+        Assertions.assertEquals(DummyEnum.class, pd.getPropertyType());
+
+        Assertions.assertThrows(FacesException.class, () -> propResolver.get(C.class, "unknown"));
+    }
+
+    @Test
+    public void testGetValueFromPropertyDescriptor() {
+        PropertyDescriptorResolver propResolver = new PropertyDescriptorResolver.DefaultResolver();
+
+        B b = new B("b", true, Boolean.FALSE, DummyEnum.X);
+        D d = new D("d", false, Boolean.FALSE, DummyEnum.Z, 123);
+        C c = new C(d);
+
+        Object obj = propResolver.getValue(c, "b");
+        Assertions.assertEquals(d, obj);
+
+        obj = propResolver.getValue(c, "b.dummyEnum");
+        Assertions.assertEquals(DummyEnum.Z, obj);
+
+        obj = propResolver.getValue(c, "b.foo");
+        Assertions.assertEquals(123, obj);
+
+        Assertions.assertThrows(FacesException.class, () -> propResolver.getValue(b, "unknown"));
+    }
+
+    private abstract class A {
+
+        private String name;
+        private boolean bool1;
+        private Boolean bool2;
+
+        public A(String name, boolean bool1, Boolean bool2) {
+            this.name = name;
+            this.bool1 = bool1;
+            this.bool2 = bool2;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+
+        public boolean isBool1() {
+            return bool1;
+        }
+
+        public void setBool1(boolean bool1) {
+            this.bool1 = bool1;
+        }
+
+        public Boolean getBool2() {
+            return bool2;
+        }
+
+        public void setBool2(Boolean bool2) {
+            this.bool2 = bool2;
+        }
+    }
+
+    private class B extends A {
+
+        private DummyEnum dummyEnum;
+
+        public B(String name, boolean bool1, Boolean bool2, DummyEnum dummyEnum) {
+            super(name, bool1, bool2);
+            this.dummyEnum = dummyEnum;
+        }
+
+        public DummyEnum getDummyEnum() {
+            return dummyEnum;
+        }
+
+        public void setDummyEnum(DummyEnum dummyEnum) {
+            this.dummyEnum = dummyEnum;
+        }
+    }
+
+    private class D extends B {
+
+        private int foo;
+
+        public D(String name, boolean bool1, Boolean bool2, DummyEnum dummyEnum, int foo) {
+            super(name, bool1, bool2, dummyEnum);
+            this.foo = foo;
+        }
+
+        public int getFoo() {
+            return foo;
+        }
+
+        public void setFoo(int foo) {
+            this.foo = foo;
+        }
+    }
+
+    private class C {
+        private B b;
+
+        public C(B b) {
+            this.b = b;
+        }
+
+        public B getB() {
+            return b;
+        }
+
+        public void setB(B b) {
+            this.b = b;
+        }
+    }
+
+    private enum DummyEnum {
+        X,
+        Y,
+        Z;
+    }
+}