Improve null-safety of core/spring-boot

See gh-46926
This commit is contained in:
Moritz Halbritter
2025-09-18 11:35:52 +02:00
parent 7689389349
commit aac1845cac
23 changed files with 57 additions and 43 deletions
@@ -20,6 +20,8 @@ import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
@@ -50,9 +52,9 @@ class ConfigurationPropertiesScanRegistrar implements ImportBeanDefinitionRegist
private final Environment environment;
private final ResourceLoader resourceLoader;
private final @Nullable ResourceLoader resourceLoader;
ConfigurationPropertiesScanRegistrar(Environment environment, ResourceLoader resourceLoader) {
ConfigurationPropertiesScanRegistrar(Environment environment, @Nullable ResourceLoader resourceLoader) {
this.environment = environment;
this.resourceLoader = resourceLoader;
}
@@ -38,7 +38,7 @@ interface CachingConfigurationPropertySource {
* @return a {@link ConfigurationPropertyCaching} instance or {@code null} if the
* source does not support caching.
*/
static @Nullable ConfigurationPropertyCaching find(ConfigurationPropertySource source) {
static @Nullable ConfigurationPropertyCaching find(@Nullable ConfigurationPropertySource source) {
if (source instanceof CachingConfigurationPropertySource cachingSource) {
return cachingSource.getCaching();
}
@@ -22,6 +22,7 @@ import org.springframework.boot.origin.Origin;
import org.springframework.boot.origin.OriginProvider;
import org.springframework.boot.origin.OriginTrackedValue;
import org.springframework.core.style.ToStringCreator;
import org.springframework.lang.Contract;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
@@ -124,6 +125,7 @@ public final class ConfigurationProperty implements OriginProvider, Comparable<C
return this.name.compareTo(other.name);
}
@Contract("_, !null -> !null")
static @Nullable ConfigurationProperty of(ConfigurationPropertyName name, @Nullable OriginTrackedValue value) {
if (value == null) {
return null;
@@ -131,6 +133,7 @@ public final class ConfigurationProperty implements OriginProvider, Comparable<C
return new ConfigurationProperty(name, value.getValue(), value.getOrigin());
}
@Contract("_, _, !null, _ -> !null")
static @Nullable ConfigurationProperty of(@Nullable ConfigurationPropertySource source,
ConfigurationPropertyName name, @Nullable Object value, @Nullable Origin origin) {
if (value == null) {
@@ -210,7 +210,7 @@ public final class ConfigurationPropertyName implements Comparable<Configuration
* @return a new {@link ConfigurationPropertyName}
* @throws InvalidConfigurationPropertyNameException if the result is not valid
*/
public ConfigurationPropertyName append(String suffix) {
public ConfigurationPropertyName append(@Nullable String suffix) {
if (!StringUtils.hasLength(suffix)) {
return this;
}
@@ -625,7 +625,7 @@ public final class ConfigurationPropertyName implements Comparable<Configuration
* @param name the name to test
* @return {@code true} if the name is valid
*/
public static boolean isValid(CharSequence name) {
public static boolean isValid(@Nullable CharSequence name) {
return of(name, true) != null;
}
@@ -636,7 +636,7 @@ public final class ConfigurationPropertyName implements Comparable<Configuration
* @throws InvalidConfigurationPropertyNameException if the name is not valid
*/
@SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1232
public static ConfigurationPropertyName of(CharSequence name) {
public static ConfigurationPropertyName of(@Nullable CharSequence name) {
return of(name, false);
}
@@ -647,7 +647,7 @@ public final class ConfigurationPropertyName implements Comparable<Configuration
* @return a {@link ConfigurationPropertyName} instance
* @since 2.3.1
*/
public static @Nullable ConfigurationPropertyName ofIfValid(CharSequence name) {
public static @Nullable ConfigurationPropertyName ofIfValid(@Nullable CharSequence name) {
return of(name, true);
}
@@ -660,7 +660,7 @@ public final class ConfigurationPropertyName implements Comparable<Configuration
* {@code returnNullIfInvalid} is {@code false}
*/
@Contract("_, false -> !null")
static @Nullable ConfigurationPropertyName of(CharSequence name, boolean returnNullIfInvalid) {
static @Nullable ConfigurationPropertyName of(@Nullable CharSequence name, boolean returnNullIfInvalid) {
Elements elements = elementsOf(name, returnNullIfInvalid, ElementsParser.DEFAULT_CAPACITY);
return (elements != null) ? new ConfigurationPropertyName(elements) : null;
}
@@ -61,7 +61,7 @@ public abstract class AbstractFailureAnalyzer<T extends Throwable> implements Fa
}
@SuppressWarnings("unchecked")
protected final <E extends Throwable> @Nullable E findCause(Throwable failure, Class<E> type) {
protected final <E extends Throwable> @Nullable E findCause(@Nullable Throwable failure, Class<E> type) {
while (failure != null) {
if (type.isInstance(failure)) {
return (E) failure;
@@ -48,9 +48,9 @@ import org.springframework.core.env.PropertySource;
class MutuallyExclusiveConfigurationPropertiesFailureAnalyzer
extends AbstractFailureAnalyzer<MutuallyExclusiveConfigurationPropertiesException> {
private final ConfigurableEnvironment environment;
private final @Nullable ConfigurableEnvironment environment;
MutuallyExclusiveConfigurationPropertiesFailureAnalyzer(Environment environment) {
MutuallyExclusiveConfigurationPropertiesFailureAnalyzer(@Nullable Environment environment) {
this.environment = (ConfigurableEnvironment) environment;
}
@@ -34,11 +34,11 @@ import org.springframework.util.ReflectionUtils;
*/
public abstract class AbstractJsonParser implements JsonParser {
protected final Map<String, Object> parseMap(String json, Function<String, Map<String, Object>> parser) {
protected final Map<String, Object> parseMap(@Nullable String json, Function<String, Map<String, Object>> parser) {
return trimParse(json, "{", parser);
}
protected final List<Object> parseList(String json, Function<String, List<Object>> parser) {
protected final List<Object> parseList(@Nullable String json, Function<String, List<Object>> parser) {
return trimParse(json, "[", parser);
}
@@ -44,12 +44,12 @@ public class BasicJsonParser extends AbstractJsonParser {
private static final int MAX_DEPTH = 1000;
@Override
public Map<String, Object> parseMap(String json) {
public Map<String, Object> parseMap(@Nullable String json) {
return tryParse(() -> parseMap(json, (jsonToParse) -> parseMapInternal(0, jsonToParse)), Exception.class);
}
@Override
public List<Object> parseList(String json) {
public List<Object> parseList(@Nullable String json) {
return tryParse(() -> parseList(json, (jsonToParse) -> parseListInternal(0, jsonToParse)), Exception.class);
}
@@ -22,6 +22,7 @@ import java.util.Map;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import org.jspecify.annotations.Nullable;
/**
* Thin wrapper to adapt {@link Gson} to a {@link JsonParser}.
@@ -40,13 +41,13 @@ public class GsonJsonParser extends AbstractJsonParser {
private final Gson gson = new GsonBuilder().create();
@Override
public Map<String, Object> parseMap(String json) {
public Map<String, Object> parseMap(@Nullable String json) {
return tryParse(() -> parseMap(json, (trimmed) -> this.gson.fromJson(trimmed, MAP_TYPE.getType())),
Exception.class);
}
@Override
public List<Object> parseList(String json) {
public List<Object> parseList(@Nullable String json) {
return tryParse(() -> parseList(json, (trimmed) -> this.gson.fromJson(trimmed, LIST_TYPE.getType())),
Exception.class);
}
@@ -53,12 +53,12 @@ public class JacksonJsonParser extends AbstractJsonParser {
}
@Override
public Map<String, Object> parseMap(String json) {
public Map<String, Object> parseMap(@Nullable String json) {
return tryParse(() -> getObjectMapper().readValue(json, MAP_TYPE), Exception.class);
}
@Override
public List<Object> parseList(String json) {
public List<Object> parseList(@Nullable String json) {
return tryParse(() -> getObjectMapper().readValue(json, LIST_TYPE), Exception.class);
}
@@ -19,6 +19,8 @@ package org.springframework.boot.json;
import java.util.List;
import java.util.Map;
import org.jspecify.annotations.Nullable;
/**
* Parser that can read JSON formatted strings into {@link Map}s or {@link List}s.
*
@@ -37,7 +39,7 @@ public interface JsonParser {
* @return the parsed JSON as a map
* @throws JsonParseException if the JSON cannot be parsed
*/
Map<String, Object> parseMap(String json) throws JsonParseException;
Map<String, Object> parseMap(@Nullable String json) throws JsonParseException;
/**
* Parse the specified JSON string into a List.
@@ -45,6 +47,6 @@ public interface JsonParser {
* @return the parsed JSON as a list
* @throws JsonParseException if the JSON cannot be parsed
*/
List<Object> parseList(String json) throws JsonParseException;
List<Object> parseList(@Nullable String json) throws JsonParseException;
}
@@ -28,13 +28,13 @@ import org.jspecify.annotations.Nullable;
*/
class DelegatingLoggingSystemFactory implements LoggingSystemFactory {
private final @Nullable Function<ClassLoader, List<LoggingSystemFactory>> delegates;
private final @Nullable Function<ClassLoader, @Nullable List<LoggingSystemFactory>> delegates;
/**
* Create a new {@link DelegatingLoggingSystemFactory} instance.
* @param delegates a function that provides the delegates
*/
DelegatingLoggingSystemFactory(@Nullable Function<ClassLoader, List<LoggingSystemFactory>> delegates) {
DelegatingLoggingSystemFactory(@Nullable Function<ClassLoader, @Nullable List<LoggingSystemFactory>> delegates) {
this.delegates = delegates;
}
@@ -120,7 +120,7 @@ public final class ColorConverter extends LogEventPatternConverter {
* @param options the options
* @return a new instance, or {@code null} if the options are invalid
*/
public static @Nullable ColorConverter newInstance(Configuration config, @Nullable String[] options) {
public static @Nullable ColorConverter newInstance(@Nullable Configuration config, @Nullable String[] options) {
if (options.length < 1) {
LOGGER.error("Incorrect number of options on style. Expected at least 1, received {}", options.length);
return null;
@@ -67,7 +67,8 @@ public final class EnclosedInSquareBracketsConverter extends LogEventPatternConv
* @param options the options
* @return a new instance, or {@code null} if the options are invalid
*/
public static @Nullable EnclosedInSquareBracketsConverter newInstance(Configuration config, String[] options) {
public static @Nullable EnclosedInSquareBracketsConverter newInstance(@Nullable Configuration config,
String[] options) {
if (options.length < 1) {
LOGGER.error("Incorrect number of options on style. Expected at least 1, received {}", options.length);
return null;
@@ -32,7 +32,7 @@ import org.springframework.util.StringUtils;
public class EnclosedInSquareBracketsConverter extends CompositeConverter<ILoggingEvent> {
@Override
protected String transform(ILoggingEvent event, String in) {
protected String transform(ILoggingEvent event, @Nullable String in) {
in = (!StringUtils.hasLength(in)) ? resolveFromFirstOption(event) : in;
return (!StringUtils.hasLength(in)) ? "" : "[%s] ".formatted(in);
}
@@ -68,7 +68,7 @@ public class LogbackLoggingSystemProperties extends LoggingSystemProperties {
* @since 3.2.0
*/
public LogbackLoggingSystemProperties(Environment environment,
Function<@Nullable String, @Nullable String> defaultValueResolver,
@Nullable Function<@Nullable String, @Nullable String> defaultValueResolver,
@Nullable BiConsumer<String, @Nullable String> setter) {
super(environment, defaultValueResolver, setter);
}
@@ -56,11 +56,11 @@ import org.springframework.util.Assert;
* @author Yanming Zhou
*/
record StructuredLoggingJsonProperties(Set<String> include, Set<String> exclude, Map<String, String> rename,
Map<String, String> add, StackTrace stackTrace, Context context,
Map<String, String> add, @Nullable StackTrace stackTrace, @Nullable Context context,
Set<Class<? extends StructuredLoggingJsonMembersCustomizer<?>>> customizer) {
StructuredLoggingJsonProperties(Set<String> include, Set<String> exclude, Map<String, String> rename,
Map<String, String> add, StackTrace stackTrace, Context context,
Map<String, String> add, @Nullable StackTrace stackTrace, @Nullable Context context,
@Nullable Set<Class<? extends StructuredLoggingJsonMembersCustomizer<?>>> customizer) {
this.include = include;
this.exclude = exclude;
@@ -98,8 +98,9 @@ record StructuredLoggingJsonProperties(Set<String> include, Set<String> exclude,
* @param includeCommonFrames whether common frames should be included
* @param includeHashes whether stack trace hashes should be included
*/
record StackTrace(@Nullable String printer, Root root, Integer maxLength, Integer maxThrowableDepth,
Boolean includeCommonFrames, Boolean includeHashes) {
record StackTrace(@Nullable String printer, @Nullable Root root, @Nullable Integer maxLength,
@Nullable Integer maxThrowableDepth, @Nullable Boolean includeCommonFrames,
@Nullable Boolean includeHashes) {
@Nullable StackTracePrinter createPrinter() {
String name = sanitizePrinter();
@@ -47,7 +47,7 @@ public interface OriginLookup<K> {
* @return an {@link Origin} or {@code null}
*/
@SuppressWarnings("unchecked")
static <K> @Nullable Origin getOrigin(Object source, K key) {
static <K> @Nullable Origin getOrigin(@Nullable Object source, K key) {
if (!(source instanceof OriginLookup)) {
return null;
}
@@ -61,7 +61,7 @@ public record JksSslStoreDetails(@Nullable String type, @Nullable String provide
* @param location the location
* @return a new {@link JksSslStoreDetails} instance.
*/
public static JksSslStoreDetails forLocation(String location) {
public static JksSslStoreDetails forLocation(@Nullable String location) {
return new JksSslStoreDetails(null, null, location, null);
}
@@ -78,7 +78,9 @@ public interface PemSslStore {
* @return a new {@link PemSslStore} instance
*/
default PemSslStore withAlias(@Nullable String alias) {
return of(type(), alias, password(), certificates(), privateKey());
List<X509Certificate> certificates = certificates();
Assert.notNull(certificates, "'certificates' must not be null");
return of(type(), alias, password(), certificates, privateKey());
}
/**
@@ -87,7 +89,9 @@ public interface PemSslStore {
* @return a new {@link PemSslStore} instance
*/
default PemSslStore withPassword(@Nullable String password) {
return of(type(), alias(), password, certificates(), privateKey());
List<X509Certificate> certificates = certificates();
Assert.notNull(certificates, "'certificates' must not be null");
return of(type(), alias(), password, certificates, privateKey());
}
/**
@@ -123,7 +127,7 @@ public interface PemSslStore {
* @param privateKey the private key
* @return a new {@link PemSslStore} instance
*/
static PemSslStore of(String type, List<X509Certificate> certificates, PrivateKey privateKey) {
static PemSslStore of(@Nullable String type, List<X509Certificate> certificates, @Nullable PrivateKey privateKey) {
return of(type, null, null, certificates, privateKey);
}
@@ -134,7 +138,7 @@ public interface PemSslStore {
* @param privateKey the private key
* @return a new {@link PemSslStore} instance
*/
static PemSslStore of(List<X509Certificate> certificates, PrivateKey privateKey) {
static PemSslStore of(List<X509Certificate> certificates, @Nullable PrivateKey privateKey) {
return of(null, null, null, certificates, privateKey);
}
@@ -151,7 +155,7 @@ public interface PemSslStore {
* @return a new {@link PemSslStore} instance
*/
static PemSslStore of(@Nullable String type, @Nullable String alias, @Nullable String password,
@Nullable List<X509Certificate> certificates, @Nullable PrivateKey privateKey) {
List<X509Certificate> certificates, @Nullable PrivateKey privateKey) {
Assert.notEmpty(certificates, "'certificates' must not be empty");
return new PemSslStore() {
@@ -151,7 +151,7 @@ public record PemSslStoreDetails(@Nullable String type, @Nullable String alias,
* reference to the resource to load)
* @return a new {@link PemSslStoreDetails} instance.
*/
public static PemSslStoreDetails forCertificate(String certificate) {
public static PemSslStoreDetails forCertificate(@Nullable String certificate) {
return forCertificates(certificate);
}
@@ -163,7 +163,7 @@ public record PemSslStoreDetails(@Nullable String type, @Nullable String alias,
* @return a new {@link PemSslStoreDetails} instance.
* @since 3.2.0
*/
public static PemSslStoreDetails forCertificates(String certificates) {
public static PemSslStoreDetails forCertificates(@Nullable String certificates) {
return new PemSslStoreDetails(null, certificates, null);
}
@@ -46,7 +46,7 @@ public class ApplicationPid {
this.pid = currentProcessPid();
}
protected ApplicationPid(Long pid) {
protected ApplicationPid(@Nullable Long pid) {
this.pid = pid;
}
@@ -151,7 +151,7 @@ public abstract class SpringBootServletInitializer implements WebApplicationInit
}
}
protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
protected @Nullable WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
SpringApplicationBuilder builder = createSpringApplicationBuilder();
builder.main(getClass());
ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
@@ -201,7 +201,7 @@ public abstract class SpringBootServletInitializer implements WebApplicationInit
* @param application the application to run
* @return the {@link WebApplicationContext}
*/
protected WebApplicationContext run(SpringApplication application) {
protected @Nullable WebApplicationContext run(SpringApplication application) {
return (WebApplicationContext) application.run();
}