This is an automated email from the ASF dual-hosted git repository. rmaucher pushed a commit to branch 11.0.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 578ecebdd4f49b717c616e888d8adf42a85b1701 Author: opencode <[email protected]> AuthorDate: Sun Oct 18 16:42:19 2026 +0200 Accept null results from EL stream comparator lambdas The max, min and sorted stream operations evaluated their comparator lambda and coerced the result to a number with the boxed integer type as the target. That coercion returns null for a null value, so a comparator lambda that returned null triggered a null pointer exception when the coerced result was unboxed. The Jakarta EL specification types the comparator as a function to the primitive int, and the coercion to a primitive numeric type maps null and the empty string to zero. Coerce to the primitive type instead. A null comparator result now means that the two elements compare as equal, which for sorted leaves a stable sort in its input order and for max and min leaves the current element, while results that cannot be coerced at all continue to be reported as an ELException. The handling of non-null numeric results, including the truncation of fractional values, is unchanged. Adds tests for a null comparator result on the three operations and a test that a non-coercible comparator result is reported as an ELException. The null result tests report the null pointer exception against the previous implementation. --- java/org/apache/el/stream/Stream.java | 10 ++-- .../apache/el/stream/TestCollectionOperations.java | 59 ++++++++++++++++++++++ 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/java/org/apache/el/stream/Stream.java b/java/org/apache/el/stream/Stream.java index f277757e75..4f3a01dee5 100644 --- a/java/org/apache/el/stream/Stream.java +++ b/java/org/apache/el/stream/Stream.java @@ -621,9 +621,11 @@ public class Stream { while (iterator.hasNext()) { Object obj = iterator.next(); - if (isMax && ELSupport.coerceToNumber(null, le.invoke(obj, result), Integer.class).intValue() > 0) { + // Coerce to the primitive type so that a null comparator result + // coerces to zero rather than producing a NullPointerException + if (isMax && ELSupport.coerceToNumber(null, le.invoke(obj, result), Integer.TYPE).intValue() > 0) { result = obj; - } else if (!isMax && ELSupport.coerceToNumber(null, le.invoke(obj, result), Integer.class).intValue() < 0) { + } else if (!isMax && ELSupport.coerceToNumber(null, le.invoke(obj, result), Integer.TYPE).intValue() < 0) { result = obj; } } @@ -639,7 +641,9 @@ public class Stream { private record LambdaExpressionComparator(LambdaExpression le) implements Comparator<Object> { @Override public int compare(Object o1, Object o2) { - return ELSupport.coerceToNumber(null, le.invoke(o1, o2), Integer.class).intValue(); + // Coerce to the primitive type so that a null comparator result + // coerces to zero rather than producing a NullPointerException + return ELSupport.coerceToNumber(null, le.invoke(o1, o2), Integer.TYPE).intValue(); } } diff --git a/test/org/apache/el/stream/TestCollectionOperations.java b/test/org/apache/el/stream/TestCollectionOperations.java index a0527488ff..c21d78d83d 100644 --- a/test/org/apache/el/stream/TestCollectionOperations.java +++ b/test/org/apache/el/stream/TestCollectionOperations.java @@ -214,6 +214,23 @@ public class TestCollectionOperations extends ELBaseTest { } + @Test + public void testSortedLambdaExpression02() { + // A comparator that returns null coerces to zero, treating all + // elements as equal. The sort is stable, so the order is unchanged. + ELProcessor processor = new ELProcessor(); + Object result = processor.getValue( + "[3, 1, 2].stream().sorted((x,y)->null).toList()", + List.class); + List<Long> expected = new ArrayList<>(3); + expected.add(3L); + expected.add(1L); + expected.add(2L); + + Assert.assertEquals(expected, result); + } + + @Test public void testForEach01() { ELProcessor processor = new ELProcessor(); @@ -434,6 +451,34 @@ public class TestCollectionOperations extends ELBaseTest { Assert.assertEquals(bean03, ((Optional) result).get()); } + + + @Test + public void testMaxLambda03() { + // A comparator that returns null coerces to zero, treating all + // elements as equal. The first element is retained. + ELProcessor processor = new ELProcessor(); + + Object result = processor.getValue( + "[1, 2, 3].stream().max((x,y)->null)", + Object.class); + + Assert.assertEquals(1L, ((Optional) result).get()); + } + + + @Test(expected=ELException.class) + public void testMaxLambda04() { + // A comparator result that cannot be coerced to a number is reported + // as an ELException + ELProcessor processor = new ELProcessor(); + + processor.getValue( + "[1, 2, 3].stream().max((x,y)->'abc')", + Object.class); + } + + @Test public void testMin01() { ELProcessor processor = new ELProcessor(); @@ -494,6 +539,20 @@ public class TestCollectionOperations extends ELBaseTest { } + @Test + public void testMinLambda02() { + // A comparator that returns null coerces to zero, treating all + // elements as equal. The first element is retained. + ELProcessor processor = new ELProcessor(); + + Object result = processor.getValue( + "[1, 2, 3].stream().min((x,y)->null)", + Object.class); + + Assert.assertEquals(1L, ((Optional) result).get()); + } + + @Test public void testAverage01() { ELProcessor processor = new ELProcessor(); --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
