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 (<!--, <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