From 35d8c4d06ffdeb0714874ac094da84952cdf9f66 Mon Sep 17 00:00:00 2001
From: Sam Brannen <104798+sbrannen@users.noreply.github.com>
Date: Sat, 5 Sep 2026 13:31:10 +0200
Subject: [PATCH] Align synthesized annotation toString() with JDK for
NaN/Infinity
Closes gh-37244
---
...izedMergedAnnotationInvocationHandler.java | 28 +++++++++++++++----
.../annotation/MergedAnnotationsTests.java | 24 ++++++++++++++++
2 files changed, 47 insertions(+), 5 deletions(-)
diff --git a/spring-core/src/main/java/org/springframework/core/annotation/SynthesizedMergedAnnotationInvocationHandler.java b/spring-core/src/main/java/org/springframework/core/annotation/SynthesizedMergedAnnotationInvocationHandler.java
index 24c21acf6af..1b60933326a 100644
--- a/spring-core/src/main/java/org/springframework/core/annotation/SynthesizedMergedAnnotationInvocationHandler.java
+++ b/spring-core/src/main/java/org/springframework/core/annotation/SynthesizedMergedAnnotationInvocationHandler.java
@@ -160,14 +160,12 @@ final class SynthesizedMergedAnnotationInvocationHandler i
}
/**
- * This method currently does not address the following issues which we may
+ * This method currently does not address the following issue which we may
* choose to address at a later point in time.
*
*
* - non-ASCII, non-visible, and non-printable characters within a character
* or String literal are not escaped.
- * - formatting for float and double values does not take into account whether
- * a value is not a number (NaN) or infinite.
*
* @param value the attribute value to format
* @return the formatted string representation
@@ -199,10 +197,30 @@ final class SynthesizedMergedAnnotationInvocationHandler i
return Long.toString((Long) value) + 'L';
}
if (type == Float.class) {
- return Float.toString((Float) value) + 'f';
+ float floatValue = (Float) value;
+ if (Float.isNaN(floatValue)) {
+ return "0.0f/0.0f";
+ }
+ if (floatValue == Float.POSITIVE_INFINITY) {
+ return "1.0f/0.0f";
+ }
+ if (floatValue == Float.NEGATIVE_INFINITY) {
+ return "-1.0f/0.0f";
+ }
+ return Float.toString(floatValue) + 'f';
}
if (type == Double.class) {
- return Double.toString((Double) value);
+ double doubleValue = (Double) value;
+ if (Double.isNaN(doubleValue)) {
+ return "0.0/0.0";
+ }
+ if (doubleValue == Double.POSITIVE_INFINITY) {
+ return "1.0/0.0";
+ }
+ if (doubleValue == Double.NEGATIVE_INFINITY) {
+ return "-1.0/0.0";
+ }
+ return Double.toString(doubleValue);
}
if (value instanceof Enum> e) {
return e.name();
diff --git a/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java
index c5c386e71d6..61fc0ffe367 100644
--- a/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java
+++ b/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java
@@ -2060,6 +2060,30 @@ class MergedAnnotationsTests {
.isEqualTo("@%s({\"FromValueAttributeMeta\"})", ValueAttribute.class.getCanonicalName());
}
+ @Test // gh-37244
+ void toStringForSynthesizedAnnotationsWithNonFiniteFloatingPointValues() {
+ Map attributes = Map.of(
+ "name", "test",
+ "floatValue", Float.NaN,
+ "doubleValue", Double.NaN);
+ RequestMapping mapping = MergedAnnotation.of(RequestMapping.class, attributes).synthesize();
+ assertThat(mapping).asString().contains("floatValue=0.0f/0.0f", "doubleValue=0.0/0.0");
+
+ attributes = Map.of(
+ "name", "test",
+ "floatValue", Float.POSITIVE_INFINITY,
+ "doubleValue", Double.POSITIVE_INFINITY);
+ mapping = MergedAnnotation.of(RequestMapping.class, attributes).synthesize();
+ assertThat(mapping).asString().contains("floatValue=1.0f/0.0f", "doubleValue=1.0/0.0");
+
+ attributes = Map.of(
+ "name", "test",
+ "floatValue", Float.NEGATIVE_INFINITY,
+ "doubleValue", Double.NEGATIVE_INFINITY);
+ mapping = MergedAnnotation.of(RequestMapping.class, attributes).synthesize();
+ assertThat(mapping).asString().contains("floatValue=-1.0f/0.0f", "doubleValue=-1.0/0.0");
+ }
+
@Test
void equalsForSynthesizedAnnotations() throws Exception {
Method methodWithPath = WebController.class.getMethod("handleMappedWithPathAttribute");