From 856e1d5dc8c8c27c4d71a041d915d855937186a7 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 8 May 2026 15:59:46 +0200 Subject: [PATCH 1/3] Avoid ResolvableType#forType contention for implicit cache cleanup Closes gh-36745 --- .../springframework/core/ResolvableType.java | 3 --- .../util/ConcurrentReferenceHashMap.java | 17 ++++++++++++----- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/ResolvableType.java b/spring-core/src/main/java/org/springframework/core/ResolvableType.java index af01421dc27..ca5f880beb7 100644 --- a/spring-core/src/main/java/org/springframework/core/ResolvableType.java +++ b/spring-core/src/main/java/org/springframework/core/ResolvableType.java @@ -1533,9 +1533,6 @@ public class ResolvableType implements Serializable { return new ResolvableType(type, null, typeProvider, variableResolver); } - // Purge empty entries on access since we don't have a clean-up thread or the like. - cache.purgeUnreferencedEntries(); - // Check the cache - we may have a ResolvableType which has been resolved before... ResolvableType resultType = new ResolvableType(type, typeProvider, variableResolver); ResolvableType cachedType = cache.get(resultType); diff --git a/spring-core/src/main/java/org/springframework/util/ConcurrentReferenceHashMap.java b/spring-core/src/main/java/org/springframework/util/ConcurrentReferenceHashMap.java index 6abdda58274..808b57dbba9 100644 --- a/spring-core/src/main/java/org/springframework/util/ConcurrentReferenceHashMap.java +++ b/spring-core/src/main/java/org/springframework/util/ConcurrentReferenceHashMap.java @@ -499,10 +499,17 @@ public class ConcurrentReferenceHashMap extends AbstractMap implemen } /** - * Remove any entries that have been garbage collected and are no longer referenced. - * Under normal circumstances garbage collected entries are automatically purged as - * items are added or removed from the Map. This method can be used to force a purge, - * and is useful when the Map is read frequently but updated less often. + * Remove any entries that have been garbage-collected and are no longer referenced. + * Note that this call implies segment locking and can lead to thread contention. + *

Under normal circumstances, garbage-collected entries are automatically purged as + * items are added or removed from the Map. This method can be used to force a purge + * which is useful when the Map is read frequently but hardly ever updated anymore. + *

Note that it may be preferable to simply {@link #clear() clear} the entire cache at + * certain points of the lifecycle, not just dropping unreferenced entries but even the + * entire cache content: assuming that most entries in the cache won't be needed anymore + * after certain processing phases, therefore rather rebuilding the cache going forward. + * @since 4.1.1 + * @see #clear() */ public void purgeUnreferencedEntries() { for (Segment segment : this.segments) { @@ -716,7 +723,7 @@ public class ConcurrentReferenceHashMap extends AbstractMap implemen int currCount = this.count.get(); boolean needsResize = allowResize && (currCount > 0 && currCount >= this.resizeThreshold); Reference ref = this.referenceManager.pollForPurge(); - if (ref != null || (needsResize)) { + if (ref != null || needsResize) { restructure(allowResize, ref); } } From d3152c11c72c411356d577e6b91665506eb0147b Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 8 May 2026 15:59:57 +0200 Subject: [PATCH 2/3] Consistently expose map key quotes Closes gh-36765 --- .../beans/AbstractNestablePropertyAccessor.java | 4 ++-- .../java/org/springframework/beans/PropertyAccessorUtils.java | 3 ++- .../springframework/beans/AbstractPropertyAccessorTests.java | 2 ++ .../org/springframework/beans/PropertyAccessorUtilsTests.java | 4 ++++ .../beans/testfixture/beans/IndexedTestBean.java | 3 +++ 5 files changed, 13 insertions(+), 3 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java b/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java index d4675d2c032..3912156671b 100644 --- a/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java @@ -956,8 +956,8 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA actualName = propertyName.substring(0, keyStart); } String key = propertyName.substring(keyStart + PROPERTY_KEY_PREFIX.length(), keyEnd); - if (key.length() > 1 && (key.startsWith("'") && key.endsWith("'")) || - (key.startsWith("\"") && key.endsWith("\""))) { + if (key.length() > 1 && ((key.startsWith("'") && key.endsWith("'")) || + (key.startsWith("\"") && key.endsWith("\"")))) { key = key.substring(1, key.length() - 1); } keys.add(key); diff --git a/spring-beans/src/main/java/org/springframework/beans/PropertyAccessorUtils.java b/spring-beans/src/main/java/org/springframework/beans/PropertyAccessorUtils.java index 4a14b85ca4b..f527f83bd93 100644 --- a/spring-beans/src/main/java/org/springframework/beans/PropertyAccessorUtils.java +++ b/spring-beans/src/main/java/org/springframework/beans/PropertyAccessorUtils.java @@ -154,7 +154,8 @@ public abstract class PropertyAccessorUtils { PropertyAccessor.PROPERTY_KEY_SUFFIX, keyStart + PropertyAccessor.PROPERTY_KEY_PREFIX.length()); if (keyEnd != -1) { String key = sb.substring(keyStart + PropertyAccessor.PROPERTY_KEY_PREFIX.length(), keyEnd); - if ((key.startsWith("'") && key.endsWith("'")) || (key.startsWith("\"") && key.endsWith("\""))) { + if (key.length() > 1 && ((key.startsWith("'") && key.endsWith("'")) || + (key.startsWith("\"") && key.endsWith("\"")))) { sb.delete(keyStart + 1, keyStart + 2); sb.delete(keyEnd - 2, keyEnd - 1); keyEnd = keyEnd - 2; diff --git a/spring-beans/src/test/java/org/springframework/beans/AbstractPropertyAccessorTests.java b/spring-beans/src/test/java/org/springframework/beans/AbstractPropertyAccessorTests.java index 3bb83600abb..e6a17ddd2c5 100644 --- a/spring-beans/src/test/java/org/springframework/beans/AbstractPropertyAccessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/AbstractPropertyAccessorTests.java @@ -1413,6 +1413,8 @@ abstract class AbstractPropertyAccessorTests { assertThat(accessor.getPropertyValue("map[key5[foo]].name")).isEqualTo("name8"); assertThat(accessor.getPropertyValue("map['key5[foo]'].name")).isEqualTo("name8"); assertThat(accessor.getPropertyValue("map[\"key5[foo]\"].name")).isEqualTo("name8"); + assertThat(accessor.getPropertyValue("map['].name")).isEqualTo("name9"); + assertThat(accessor.getPropertyValue("map[\"].name")).isEqualTo("name9"); assertThat(accessor.getPropertyValue("iterableMap[key1].name")).isEqualTo("nameC"); assertThat(accessor.getPropertyValue("iterableMap[key2][0].name")).isEqualTo("nameA"); assertThat(accessor.getPropertyValue("iterableMap[key2][1].name")).isEqualTo("nameB"); diff --git a/spring-beans/src/test/java/org/springframework/beans/PropertyAccessorUtilsTests.java b/spring-beans/src/test/java/org/springframework/beans/PropertyAccessorUtilsTests.java index 012aa731eb8..0af975de003 100644 --- a/spring-beans/src/test/java/org/springframework/beans/PropertyAccessorUtilsTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/PropertyAccessorUtilsTests.java @@ -79,6 +79,10 @@ class PropertyAccessorUtilsTests { assertThat(PropertyAccessorUtils.canonicalPropertyName("map[key1].name")).isEqualTo("map[key1].name"); assertThat(PropertyAccessorUtils.canonicalPropertyName("map['key1'].name")).isEqualTo("map[key1].name"); assertThat(PropertyAccessorUtils.canonicalPropertyName("map[\"key1\"].name")).isEqualTo("map[key1].name"); + assertThat(PropertyAccessorUtils.canonicalPropertyName("map['key1]")).isEqualTo("map['key1]"); + assertThat(PropertyAccessorUtils.canonicalPropertyName("map[\"key1]")).isEqualTo("map[\"key1]"); + assertThat(PropertyAccessorUtils.canonicalPropertyName("map[']")).isEqualTo("map[']"); + assertThat(PropertyAccessorUtils.canonicalPropertyName("map[\"]")).isEqualTo("map[\"]"); } @Test diff --git a/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/IndexedTestBean.java b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/IndexedTestBean.java index 5de9303787d..ffcfa287e5b 100644 --- a/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/IndexedTestBean.java +++ b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/IndexedTestBean.java @@ -76,6 +76,7 @@ public class IndexedTestBean { TestBean tb6 = new TestBean("name6", 0); TestBean tb7 = new TestBean("name7", 0); TestBean tb8 = new TestBean("name8", 0); + TestBean tb9 = new TestBean("name9", 0); TestBean tbA = new TestBean("nameA", 0); TestBean tbB = new TestBean("nameB", 0); TestBean tbC = new TestBean("nameC", 0); @@ -104,6 +105,8 @@ public class IndexedTestBean { list.add(tbY); this.map.put("key4", list); this.map.put("key5[foo]", tb8); + this.map.put("'", tb9); + this.map.put("\"", tb9); this.myTestBeans = new MyTestBeans(tbZ); } From 665c9ad7df65b8071df9e0fa6df81c2d14f3a283 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 8 May 2026 16:00:14 +0200 Subject: [PATCH 3/3] Upgrade to Netty 4.2.13 and Hibernate ORM 7.2.13 --- framework-platform/framework-platform.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework-platform/framework-platform.gradle b/framework-platform/framework-platform.gradle index 8fce3ed3215..8505d50f233 100644 --- a/framework-platform/framework-platform.gradle +++ b/framework-platform/framework-platform.gradle @@ -9,7 +9,7 @@ javaPlatform { dependencies { api(platform("com.fasterxml.jackson:jackson-bom:2.20.2")) api(platform("io.micrometer:micrometer-bom:1.16.5")) - api(platform("io.netty:netty-bom:4.2.12.Final")) + api(platform("io.netty:netty-bom:4.2.13.Final")) api(platform("io.projectreactor:reactor-bom:2025.0.5")) api(platform("io.rsocket:rsocket-bom:1.1.5")) api(platform("org.apache.groovy:groovy-bom:5.0.5")) @@ -120,7 +120,7 @@ dependencies { api("org.glassfish:jakarta.el:4.0.2") api("org.graalvm.sdk:graal-sdk:22.3.1") api("org.hamcrest:hamcrest:3.0") - api("org.hibernate.orm:hibernate-core:7.2.11.Final") + api("org.hibernate.orm:hibernate-core:7.2.13.Final") api("org.hibernate.validator:hibernate-validator:9.1.0.Final") api("org.hsqldb:hsqldb:2.7.4") api("org.htmlunit:htmlunit:4.21.0")