This is an automated email from the ASF dual-hosted git repository. rmaucher pushed a commit to branch 9.0.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit b07764950ed8790f02495e0e8a991f95fac858c3 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 a25a8cfdc4..28cd3a93da 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; } } @@ -646,7 +648,9 @@ public class Stream { @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 1a37d1ab70..b2a04926be 100644 --- a/test/org/apache/el/stream/TestCollectionOperations.java +++ b/test/org/apache/el/stream/TestCollectionOperations.java @@ -213,6 +213,23 @@ public class TestCollectionOperations { } + @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(); @@ -433,6 +450,34 @@ public class TestCollectionOperations { 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(); @@ -493,6 +538,20 @@ public class TestCollectionOperations { } + @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]
