Repository: webforj/webforj (java)

Issue:
[BUG] When trying to sort a column with some of its values being null an error happens
Trying to sort the Approval date or the Expected delivery date columns will lead to the crash.
![table](https://github.com/user-attachments/assets/16c9c3e5-caf0-42a4-9651-b25489a71991)

The error which is printed into the console.
![error](https://github.com/user-attachments/assets/8fc4120f-3a44-43c1-be51-9e77b1ab8ce8)


Pull request description:
fix: `CompositeComparator` does not handle null values gracefully
closes #732

Patch:
diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml
new file mode 100644
index 00000000..e57cd86e
--- /dev/null
+++ b/.github/workflows/labeler.yml
@@ -0,0 +1,12 @@
+name: "Pull Request Labeler"
+on:
+- pull_request_target
+
+jobs:
+  labeler:
+    permissions:
+      contents: read
+      pull-requests: write
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/labeler@v5
diff --git a/webforj-data/src/main/java/com/webforj/data/repository/CompositeComparator.java b/webforj-data/src/main/java/com/webforj/data/repository/CompositeComparator.java
index 1c498b1a..9a924b60 100644
--- a/webforj-data/src/main/java/com/webforj/data/repository/CompositeComparator.java
+++ b/webforj-data/src/main/java/com/webforj/data/repository/CompositeComparator.java
@@ -62,6 +62,20 @@ public class CompositeComparator<T> implements Comparator<T> {
   @SuppressWarnings({"rawtypes", "unchecked"})
   protected Comparator<T> createComparator(OrderCriteria<T, ?> criterion) {
     return (o1, o2) -> {
+      OrderCriteria.Direction direction = criterion.getDirection();
+
+      if (o1 == null && o2 == null) {
+        return 0;
+      }
+
+      if (o1 == null) {
+        return direction == OrderCriteria.Direction.ASC ? -1 : 1;
+      }
+
+      if (o2 == null) {
+        return direction == OrderCriteria.Direction.ASC ? 1 : -1;
+      }
+
       try {
         if (criterion.getComparator() != null) {
           // Use the custom comparator if provided
@@ -77,7 +91,7 @@ public class CompositeComparator<T> implements Comparator<T> {
           Comparable value1 = (Comparable) criterion.getValueProvider().apply(o1);
           Comparable value2 = (Comparable) criterion.getValueProvider().apply(o2);
 
-          return compareWithDirection(value1, value2, criterion.getDirection());
+          return compareWithDirection(value1, value2, direction);
         }
       } catch (ClassCastException e) {
         if (fallbackToStringComparison) {
@@ -85,7 +99,7 @@ public class CompositeComparator<T> implements Comparator<T> {
           String value1Str = String.valueOf(criterion.getValueProvider().apply(o1));
           String value2Str = String.valueOf(criterion.getValueProvider().apply(o2));
 
-          return compareWithDirection(value1Str, value2Str, criterion.getDirection());
+          return compareWithDirection(value1Str, value2Str, direction);
         } else {
           throw new IllegalArgumentException("Values are not comparable", e);
         }


Test patch:
diff --git a/webforj-data/src/test/java/com/webforj/data/repository/CompositeComparatorTest.java b/webforj-data/src/test/java/com/webforj/data/repository/CompositeComparatorTest.java
index a42558e9..c9a3b21b 100644
--- a/webforj-data/src/test/java/com/webforj/data/repository/CompositeComparatorTest.java
+++ b/webforj-data/src/test/java/com/webforj/data/repository/CompositeComparatorTest.java
@@ -139,6 +139,39 @@ class CompositeComparatorTest {
     assertTrue(comparator.compare(person2, person3) < 0);
   }
 
+  @Test
+  void shouldHandleNullValuesInComparison() {
+    Function<String, Integer> lengthProvider = String::length;
+    OrderCriteria<String, Integer> lengthCriteria =
+        new OrderCriteria<>(lengthProvider, OrderCriteria.Direction.ASC);
+    CompositeComparator<String> comparator =
+        new CompositeComparator<>(new OrderCriteriaList<>(lengthCriteria));
+
+    assertTrue(comparator.compare(null, "abc") < 0);
+    assertTrue(comparator.compare("abc", null) > 0);
+    assertEquals(0, comparator.compare(null, null));
+  }
+
+  @Test
+  void shouldHandleNullValuesWithCustomComparator() {
+    Function<Person, String> nameProvider = Person::getName;
+    Comparator<Person> customNameLengthComparator =
+        Comparator.comparingInt(person -> person.getName() != null ? person.getName().length() : 0);
+
+    OrderCriteria<Person, String> customNameCriteria =
+        new OrderCriteria<>(nameProvider, OrderCriteria.Direction.ASC, customNameLengthComparator);
+
+    CompositeComparator<Person> comparator =
+        new CompositeComparator<>(new OrderCriteriaList<>(customNameCriteria));
+
+    Person person1 = new Person("Bob");
+    Person person2 = new Person("Charlotte");
+
+    assertTrue(comparator.compare(null, person1) < 0);
+    assertTrue(comparator.compare(person2, null) > 0);
+    assertEquals(0, comparator.compare(person1, person1));
+  }
+
   class NonComparableClass {
     private String value;