Add nullability annotations to module/spring-boot-cache
Build and Deploy Snapshot / Build and Deploy Snapshot (push) Waiting to run Details
Build and Deploy Snapshot / Trigger Docs Build (push) Blocked by required conditions Details
Build and Deploy Snapshot / Verify (push) Blocked by required conditions Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:24], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:24], map[id:windows-latest name:Windows]) (push) Waiting to run Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:17], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:17], map[id:windows-latest name:Windows]) (push) Waiting to run Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:21], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:21], map[id:windows-latest name:Windows]) (push) Waiting to run Details
Run CodeQL Analysis / run-analysis (push) Waiting to run Details
Run System Tests / Java ${{ matrix.java.version}} (map[toolchain:true version:17]) (push) Waiting to run Details
Run System Tests / Java ${{ matrix.java.version}} (map[toolchain:true version:21]) (push) Waiting to run Details

See gh-46587
This commit is contained in:
Moritz Halbritter 2025-07-29 17:11:49 +02:00
parent f135a2d6ca
commit fccc768fdd
18 changed files with 92 additions and 48 deletions

View File

@ -22,6 +22,8 @@ import java.util.Map;
import java.util.Objects;
import java.util.function.Predicate;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.actuate.endpoint.OperationResponseBody;
import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
@ -30,6 +32,7 @@ import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.util.Assert;
/**
* {@link Endpoint @Endpoint} to expose available {@link Cache caches}.
@ -79,7 +82,8 @@ public class CachesEndpoint {
* {@code cacheManager} was provided to identify a unique candidate
*/
@ReadOperation
public CacheEntryDescriptor cache(@Selector String cache, @OptionalParameter String cacheManager) {
public @Nullable CacheEntryDescriptor cache(@Selector String cache,
@OptionalParameter @Nullable String cacheManager) {
return extractUniqueCacheEntry(cache, getCacheEntries((name) -> name.equals(cache), isNameMatch(cacheManager)));
}
@ -101,7 +105,7 @@ public class CachesEndpoint {
* {@code cacheManager} was provided to identify a unique candidate
*/
@DeleteOperation
public boolean clearCache(@Selector String cache, @OptionalParameter String cacheManager) {
public boolean clearCache(@Selector String cache, @OptionalParameter @Nullable String cacheManager) {
CacheEntryDescriptor entry = extractUniqueCacheEntry(cache,
getCacheEntries((name) -> name.equals(cache), isNameMatch(cacheManager)));
return (entry != null && clearCache(entry));
@ -118,6 +122,7 @@ public class CachesEndpoint {
private List<CacheEntryDescriptor> getCacheEntries(String cacheManagerName, Predicate<String> cacheNamePredicate) {
CacheManager cacheManager = this.cacheManagers.get(cacheManagerName);
Assert.state(cacheManager != null, "'cacheManager' must not be null");
return cacheManager.getCacheNames()
.stream()
.filter(cacheNamePredicate)
@ -127,7 +132,7 @@ public class CachesEndpoint {
.toList();
}
private CacheEntryDescriptor extractUniqueCacheEntry(String cache, List<CacheEntryDescriptor> entries) {
private @Nullable CacheEntryDescriptor extractUniqueCacheEntry(String cache, List<CacheEntryDescriptor> entries) {
if (entries.size() > 1) {
throw new NonUniqueCacheException(cache,
entries.stream().map(CacheEntryDescriptor::getCacheManager).distinct().toList());
@ -137,8 +142,10 @@ public class CachesEndpoint {
private boolean clearCache(CacheEntryDescriptor entry) {
String cacheName = entry.getName();
String cacheManager = entry.getCacheManager();
Cache cache = this.cacheManagers.get(cacheManager).getCache(cacheName);
String cacheManagerName = entry.getCacheManager();
CacheManager cacheManager = this.cacheManagers.get(cacheManagerName);
Assert.state(cacheManager != null, "'cacheManager' must not be null");
Cache cache = cacheManager.getCache(cacheName);
if (cache != null) {
cache.clear();
return true;
@ -146,7 +153,7 @@ public class CachesEndpoint {
return false;
}
private Predicate<String> isNameMatch(String name) {
private Predicate<String> isNameMatch(@Nullable String name) {
return (name != null) ? ((requested) -> requested.equals(name)) : matchAll();
}

View File

@ -17,4 +17,7 @@
/**
* Actuator endpoint for caches.
*/
@NullMarked
package org.springframework.boot.cache.actuate.endpoint;
import org.jspecify.annotations.NullMarked;

View File

@ -20,6 +20,8 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.util.LambdaSafe;
import org.springframework.cache.CacheManager;
@ -34,7 +36,7 @@ public class CacheManagerCustomizers {
private final List<CacheManagerCustomizer<?>> customizers;
public CacheManagerCustomizers(List<? extends CacheManagerCustomizer<?>> customizers) {
public CacheManagerCustomizers(@Nullable List<? extends CacheManagerCustomizer<?>> customizers) {
this.customizers = (customizers != null) ? new ArrayList<>(customizers) : Collections.emptyList();
}

View File

@ -20,6 +20,8 @@ import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.autoconfigure.cache.CacheType;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.io.Resource;
@ -39,7 +41,7 @@ public class CacheProperties {
/**
* Cache type. By default, auto-detected according to the environment.
*/
private CacheType type;
private @Nullable CacheType type;
/**
* List of cache names to create if supported by the underlying cache manager.
@ -57,11 +59,11 @@ public class CacheProperties {
private final Redis redis = new Redis();
public CacheType getType() {
public @Nullable CacheType getType() {
return this.type;
}
public void setType(CacheType mode) {
public void setType(@Nullable CacheType mode) {
this.type = mode;
}
@ -100,7 +102,7 @@ public class CacheProperties {
* @throws IllegalArgumentException if the config attribute is set to an unknown
* location
*/
public Resource resolveConfigLocation(Resource config) {
public @Nullable Resource resolveConfigLocation(@Nullable Resource config) {
if (config != null) {
Assert.isTrue(config.exists(),
() -> "'config' resource [%s] must exist".formatted(config.getDescription()));
@ -118,13 +120,13 @@ public class CacheProperties {
* The spec to use to create caches. See CaffeineSpec for more details on the spec
* format.
*/
private String spec;
private @Nullable String spec;
public String getSpec() {
public @Nullable String getSpec() {
return this.spec;
}
public void setSpec(String spec) {
public void setSpec(@Nullable String spec) {
this.spec = spec;
}
@ -139,13 +141,13 @@ public class CacheProperties {
* Entry expiration. By default the entries never expire. Note that this value is
* ultimately converted to seconds.
*/
private Duration expiration;
private @Nullable Duration expiration;
public Duration getExpiration() {
public @Nullable Duration getExpiration() {
return this.expiration;
}
public void setExpiration(Duration expiration) {
public void setExpiration(@Nullable Duration expiration) {
this.expiration = expiration;
}
@ -159,13 +161,13 @@ public class CacheProperties {
/**
* The location of the configuration file to use to initialize Infinispan.
*/
private Resource config;
private @Nullable Resource config;
public Resource getConfig() {
public @Nullable Resource getConfig() {
return this.config;
}
public void setConfig(Resource config) {
public void setConfig(@Nullable Resource config) {
this.config = config;
}
@ -180,28 +182,28 @@ public class CacheProperties {
* The location of the configuration file to use to initialize the cache manager.
* The configuration file is dependent of the underlying cache implementation.
*/
private Resource config;
private @Nullable Resource config;
/**
* Fully qualified name of the CachingProvider implementation to use to retrieve
* the JSR-107 compliant cache manager. Needed only if more than one JSR-107
* implementation is available on the classpath.
*/
private String provider;
private @Nullable String provider;
public String getProvider() {
public @Nullable String getProvider() {
return this.provider;
}
public void setProvider(String provider) {
public void setProvider(@Nullable String provider) {
this.provider = provider;
}
public Resource getConfig() {
public @Nullable Resource getConfig() {
return this.config;
}
public void setConfig(Resource config) {
public void setConfig(@Nullable Resource config) {
this.config = config;
}
@ -215,7 +217,7 @@ public class CacheProperties {
/**
* Entry expiration. By default the entries never expire.
*/
private Duration timeToLive;
private @Nullable Duration timeToLive;
/**
* Allow caching null values.
@ -225,7 +227,7 @@ public class CacheProperties {
/**
* Key prefix.
*/
private String keyPrefix;
private @Nullable String keyPrefix;
/**
* Whether to use the key prefix when writing to Redis.
@ -237,11 +239,11 @@ public class CacheProperties {
*/
private boolean enableStatistics;
public Duration getTimeToLive() {
public @Nullable Duration getTimeToLive() {
return this.timeToLive;
}
public void setTimeToLive(Duration timeToLive) {
public void setTimeToLive(@Nullable Duration timeToLive) {
this.timeToLive = timeToLive;
}
@ -253,11 +255,11 @@ public class CacheProperties {
this.cacheNullValues = cacheNullValues;
}
public String getKeyPrefix() {
public @Nullable String getKeyPrefix() {
return this.keyPrefix;
}
public void setKeyPrefix(String keyPrefix) {
public void setKeyPrefix(@Nullable String keyPrefix) {
this.keyPrefix = keyPrefix;
}

View File

@ -21,6 +21,7 @@ import java.util.List;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.CaffeineSpec;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@ -65,8 +66,8 @@ class CaffeineCacheConfiguration {
return cacheManager;
}
private void setCacheBuilder(CacheProperties cacheProperties, CaffeineSpec caffeineSpec,
Caffeine<Object, Object> caffeine, CaffeineCacheManager cacheManager) {
private void setCacheBuilder(CacheProperties cacheProperties, @Nullable CaffeineSpec caffeineSpec,
@Nullable Caffeine<Object, Object> caffeine, CaffeineCacheManager cacheManager) {
String specification = cacheProperties.getCaffeine().getSpec();
if (StringUtils.hasText(specification)) {
cacheManager.setCacheSpecification(specification);

View File

@ -21,6 +21,7 @@ import java.net.URI;
import java.util.Properties;
import com.hazelcast.core.HazelcastInstance;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@ -45,11 +46,11 @@ class HazelcastJCacheCustomizationConfiguration {
static class HazelcastPropertiesCustomizer implements JCachePropertiesCustomizer {
private final HazelcastInstance hazelcastInstance;
private final @Nullable HazelcastInstance hazelcastInstance;
private final CacheProperties cacheProperties;
HazelcastPropertiesCustomizer(HazelcastInstance hazelcastInstance, CacheProperties cacheProperties) {
HazelcastPropertiesCustomizer(@Nullable HazelcastInstance hazelcastInstance, CacheProperties cacheProperties) {
this.hazelcastInstance = hazelcastInstance;
this.cacheProperties = cacheProperties;
}

View File

@ -24,6 +24,7 @@ import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.manager.DefaultCacheManager;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.spring.embedded.provider.SpringEmbeddedCacheManager;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@ -80,7 +81,7 @@ public class InfinispanCacheConfiguration {
}
private org.infinispan.configuration.cache.Configuration getDefaultCacheConfiguration(
ConfigurationBuilder defaultConfigurationBuilder) {
@Nullable ConfigurationBuilder defaultConfigurationBuilder) {
if (defaultConfigurationBuilder != null) {
return defaultConfigurationBuilder.build();
}

View File

@ -26,6 +26,8 @@ import javax.cache.Caching;
import javax.cache.configuration.MutableConfiguration;
import javax.cache.spi.CachingProvider;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
@ -61,6 +63,7 @@ import org.springframework.util.StringUtils;
@Import(HazelcastJCacheCustomizationConfiguration.class)
class JCacheCacheConfiguration implements BeanClassLoaderAware {
@SuppressWarnings("NullAway.Init")
private ClassLoader beanClassLoader;
@Override
@ -103,7 +106,7 @@ class JCacheCacheConfiguration implements BeanClassLoaderAware {
return cachingProvider.getCacheManager(null, this.beanClassLoader, properties);
}
private CachingProvider getCachingProvider(String cachingProviderFqn) {
private CachingProvider getCachingProvider(@Nullable String cachingProviderFqn) {
if (StringUtils.hasText(cachingProviderFqn)) {
return Caching.getCachingProvider(cachingProviderFqn);
}

View File

@ -19,6 +19,8 @@ package org.springframework.boot.cache.autoconfigure;
import java.util.LinkedHashSet;
import java.util.List;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@ -73,12 +75,12 @@ class RedisCacheConfiguration {
private org.springframework.data.redis.cache.RedisCacheConfiguration determineConfiguration(
CacheProperties cacheProperties,
ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration,
ClassLoader classLoader) {
@Nullable ClassLoader classLoader) {
return redisCacheConfiguration.getIfAvailable(() -> createConfiguration(cacheProperties, classLoader));
}
private org.springframework.data.redis.cache.RedisCacheConfiguration createConfiguration(
CacheProperties cacheProperties, ClassLoader classLoader) {
CacheProperties cacheProperties, @Nullable ClassLoader classLoader) {
Redis redisProperties = cacheProperties.getRedis();
org.springframework.data.redis.cache.RedisCacheConfiguration config = org.springframework.data.redis.cache.RedisCacheConfiguration
.defaultCacheConfig();

View File

@ -17,4 +17,7 @@
/**
* Auto-configuration for the cache abstraction endpoints.
*/
@NullMarked
package org.springframework.boot.cache.autoconfigure.endpoint;
import org.jspecify.annotations.NullMarked;

View File

@ -31,6 +31,7 @@ import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
@ -69,8 +70,11 @@ class CacheMetricsRegistrarConfiguration {
}
private void bindCacheManagerToRegistry(String beanName, CacheManager cacheManager) {
cacheManager.getCacheNames()
.forEach((cacheName) -> bindCacheToRegistry(beanName, cacheManager.getCache(cacheName)));
cacheManager.getCacheNames().forEach((cacheName) -> {
Cache cache = cacheManager.getCache(cacheName);
Assert.state(cache != null, () -> "'cache' must not be null. 'cacheName' is '%s'".formatted(cacheName));
bindCacheToRegistry(beanName, cache);
});
}
private void bindCacheToRegistry(String beanName, Cache cache) {

View File

@ -17,4 +17,7 @@
/**
* Auto-configuration for the cache abstraction metrics.
*/
@NullMarked
package org.springframework.boot.cache.autoconfigure.metrics;
import org.jspecify.annotations.NullMarked;

View File

@ -17,4 +17,7 @@
/**
* Auto-configuration for the cache abstraction.
*/
@NullMarked
package org.springframework.boot.cache.autoconfigure;
import org.jspecify.annotations.NullMarked;

View File

@ -18,6 +18,7 @@ package org.springframework.boot.cache.metrics;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.binder.MeterBinder;
import org.jspecify.annotations.Nullable;
import org.springframework.cache.Cache;
@ -38,6 +39,6 @@ public interface CacheMeterBinderProvider<C extends Cache> {
* @param tags tags to apply to all recorded metrics
* @return a {@link MeterBinder} handling the specified {@link Cache} or {@code null}
*/
MeterBinder getMeterBinder(C cache, Iterable<Tag> tags);
@Nullable MeterBinder getMeterBinder(C cache, Iterable<Tag> tags);
}

View File

@ -23,6 +23,7 @@ import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.binder.MeterBinder;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.util.LambdaSafe;
import org.springframework.cache.Cache;
@ -68,8 +69,9 @@ public class CacheMetricsRegistrar {
return false;
}
@SuppressWarnings({ "unchecked" })
private MeterBinder getMeterBinder(Cache cache, Tags tags) {
// Lambda isn't detected with the correct nullability
@SuppressWarnings({ "unchecked", "NullAway" })
private @Nullable MeterBinder getMeterBinder(Cache cache, Tags tags) {
Tags cacheTags = tags.and(getAdditionalTags(cache));
return LambdaSafe.callbacks(CacheMeterBinderProvider.class, this.binderProviders, cache)
.withLogger(CacheMetricsRegistrar.class)

View File

@ -23,6 +23,7 @@ import com.hazelcast.spring.cache.HazelcastCache;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.binder.MeterBinder;
import io.micrometer.core.instrument.binder.cache.HazelcastCacheMetrics;
import org.jspecify.annotations.Nullable;
import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.RuntimeHints;
@ -55,6 +56,7 @@ public class HazelcastCacheMeterBinderProvider implements CacheMeterBinderProvid
private MeterBinder createHazelcast4CacheMetrics(HazelcastCache cache, Iterable<Tag> tags) {
try {
Method nativeCacheAccessor = ReflectionUtils.findMethod(HazelcastCache.class, "getNativeCache");
Assert.state(nativeCacheAccessor != null, "'nativeCacheAccessor' must not be null");
Object nativeCache = ReflectionUtils.invokeMethod(nativeCacheAccessor, cache);
return HazelcastCacheMetrics.class.getConstructor(Object.class, Iterable.class)
.newInstance(nativeCache, tags);
@ -67,7 +69,7 @@ public class HazelcastCacheMeterBinderProvider implements CacheMeterBinderProvid
static class HazelcastCacheMeterBinderProviderRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
try {
Method getNativeCacheMethod = ReflectionUtils.findMethod(HazelcastCache.class, "getNativeCache");
Assert.state(getNativeCacheMethod != null, "Unable to find 'getNativeCache' method");

View File

@ -23,6 +23,7 @@ import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.TimeGauge;
import io.micrometer.core.instrument.binder.cache.CacheMeterBinder;
import org.jspecify.annotations.Nullable;
import org.springframework.data.redis.cache.RedisCache;
@ -42,7 +43,7 @@ public class RedisCacheMetrics extends CacheMeterBinder<RedisCache> {
}
@Override
protected Long size() {
protected @Nullable Long size() {
return null;
}
@ -57,7 +58,7 @@ public class RedisCacheMetrics extends CacheMeterBinder<RedisCache> {
}
@Override
protected Long evictionCount() {
protected @Nullable Long evictionCount() {
return null;
}

View File

@ -17,4 +17,7 @@
/**
* Metrics for caches.
*/
@NullMarked
package org.springframework.boot.cache.metrics;
import org.jspecify.annotations.NullMarked;