This is an automated email from the ASF dual-hosted git repository.

garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-lang.git


The following commit(s) were added to refs/heads/master by this push:
     new 692587487 JavaVersion.get() throws NumberFormatException where javadoc 
promises null (f015).
692587487 is described below

commit 6925874874a9e3ed306dc5fa1a2e1435384860c3
Author: Gary Gregory <[email protected]>
AuthorDate: Sat Sep 5 13:46:16 2026 -0400

    JavaVersion.get() throws NumberFormatException where javadoc promises
    null (f015).
---
 src/changes/changes.xml                                |  1 +
 .../java/org/apache/commons/lang3/JavaVersion.java     | 18 +++++++++++++++---
 .../java/org/apache/commons/lang3/JavaVersionTest.java | 11 +++++++++++
 3 files changed, 27 insertions(+), 3 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 8229b62b3..9c072d2af 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -264,6 +264,7 @@ java.lang.NullPointerException: Cannot invoke
     <action                   type="fix" dev="ggregory" due-to="Gary 
Gregory">StringEscapeUtils.escapeEcmaScript() misses backtick/template-literal 
(`, ${) and inline-script parser-state sequences (&lt;!--, &lt;script) - claim 
'Deals correctly with quotes' is falsified by ES6 (f012).</action>
     <action                   type="fix" dev="ggregory" due-to="Gary 
Gregory">StrSubstitutor (deprecated) recursive expansion has a cycle check but 
no fan-out, depth, or size bound (f013).</action>
     <action                   type="fix" dev="ggregory" due-to="Gary 
Gregory">DurationFormatUtils.formatPeriod walks the calendar one year per 
iteration, Long.MAX_VALUE endMillis forces ~292 million Calendar round trips on 
one thread (f014).</action>
+    <action                   type="fix" dev="ggregory" due-to="Gary 
Gregory">JavaVersion.get() throws NumberFormatException where javadoc promises 
null (f015).</action>
     <!-- ADD -->
     <action                   type="add" dev="ggregory" due-to="Gary 
Gregory">Add JavaVersion.JAVA_27.</action>
     <action                   type="add" dev="ggregory" due-to="Gary 
Gregory">Add SystemUtils.IS_JAVA_27.</action>
diff --git a/src/main/java/org/apache/commons/lang3/JavaVersion.java 
b/src/main/java/org/apache/commons/lang3/JavaVersion.java
index 0e1e06d8c..106672b3b 100644
--- a/src/main/java/org/apache/commons/lang3/JavaVersion.java
+++ b/src/main/java/org/apache/commons/lang3/JavaVersion.java
@@ -288,9 +288,21 @@ static JavaVersion get(final String versionStr) {
             final float v = toFloatVersion(versionStr);
             if (v - 1. < 1.) { // then we need to check decimals > .9
                 final int firstComma = Math.max(versionStr.indexOf('.'), 
versionStr.indexOf(','));
-                final int end = Math.max(versionStr.length(), 
versionStr.indexOf(',', firstComma));
-                if (Float.parseFloat(versionStr.substring(firstComma + 1, 
end)) > .9f) {
-                    return JAVA_RECENT;
+                // read up to the next separator if present, otherwise to the 
end of the string
+                // (this was previously an inverted Math.max that always 
selected the full string)
+                int end = versionStr.indexOf(',', firstComma + 1);
+                if (end == -1) {
+                    end = versionStr.length();
+                }
+                try {
+                    if (Float.parseFloat(versionStr.substring(firstComma + 1, 
end)) > .9f) {
+                        return JAVA_RECENT;
+                    }
+                } catch (final NumberFormatException e) {
+                    // malformed version string ("1.", "bogus"): the 
documented contract is to return null
+                    // for unknown versions rather than propagate an 
exception, which would otherwise poison
+                    // SystemUtils' static initializer for the class loader 
lifetime.
+                    return null;
                 }
             } else if (v > 10) {
                 return JAVA_RECENT;
diff --git a/src/test/java/org/apache/commons/lang3/JavaVersionTest.java 
b/src/test/java/org/apache/commons/lang3/JavaVersionTest.java
index e60e091d9..133928374 100644
--- a/src/test/java/org/apache/commons/lang3/JavaVersionTest.java
+++ b/src/test/java/org/apache/commons/lang3/JavaVersionTest.java
@@ -20,6 +20,7 @@
 
 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 org.junit.jupiter.api.Test;
@@ -69,11 +70,21 @@ void testGetJavaVersion() throws Exception {
         assertEquals(JavaVersion.JAVA_25, JavaVersion.get("25"));
         assertEquals(JavaVersion.JAVA_26, JavaVersion.get("26"));
         assertEquals(JavaVersion.JAVA_27, JavaVersion.get("27"));
+    }
+
+    @Test
+    void testGetJavaVersionMalformed() throws Exception {
         // Failures
         assertEquals(JavaVersion.JAVA_RECENT, JavaVersion.get("1.10"), "1.10 
failed");
         // assertNull("2.10 unexpectedly worked", JavaVersion.get("2.10"));
         assertEquals(JavaVersion.get("1.5"), 
JavaVersion.getJavaVersion("1.5"), "Wrapper method failed");
         assertEquals(JavaVersion.JAVA_RECENT, JavaVersion.get("99"), 
"Unhandled"); // LANG-1384
+        // Unknown or malformed versions must return null per the Javadoc, 
never throw
+        // (a NumberFormatException here poisons SystemUtils' static 
initializer).
+        assertNull(JavaVersion.get("1."));
+        assertNull(JavaVersion.get("bogus"));
+        assertNull(JavaVersion.get("1.x"));
+        assertNull(JavaVersion.get(""));
     }
 
     @Test

Reply via email to