Skip to content

Commit 17601b4

Browse files
committed
Add nullability annotations to module/spring-boot-jackson
See gh-46587
1 parent 3db5b57 commit 17601b4

File tree

10 files changed

+59
-37
lines changed

10 files changed

+59
-37
lines changed

documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/json/jackson/customserializersanddeserializers/object/MyJsonComponent.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ class MyJsonComponent {
4242
@Throws(IOException::class)
4343
override fun deserializeObject(jsonParser: JsonParser, context: DeserializationContext,
4444
codec: ObjectCodec, tree: JsonNode): MyObject {
45-
val name = nullSafeValue(tree["name"], String::class.java)
46-
val age = nullSafeValue(tree["age"], Int::class.java)
45+
val name = nullSafeValue(tree["name"], String::class.java) ?: throw IllegalStateException("name is null")
46+
val age = nullSafeValue(tree["age"], Int::class.java) ?: throw IllegalStateException("age is null")
4747
return MyObject(name, age)
4848
}
4949
}

module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/JsonComponentModule.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import com.fasterxml.jackson.databind.KeyDeserializer;
2929
import com.fasterxml.jackson.databind.Module;
3030
import com.fasterxml.jackson.databind.module.SimpleModule;
31+
import org.jspecify.annotations.Nullable;
3132

3233
import org.springframework.aot.generate.GenerationContext;
3334
import org.springframework.aot.hint.MemberCategory;
@@ -62,6 +63,7 @@
6263
*/
6364
public class JsonComponentModule extends SimpleModule implements BeanFactoryAware, InitializingBean {
6465

66+
@SuppressWarnings("NullAway.Init")
6567
private BeanFactory beanFactory;
6668

6769
@Override
@@ -129,6 +131,7 @@ private static boolean isSuitableInnerClass(Class<?> innerClass) {
129131
private <T> void addJsonSerializerBean(JsonSerializer<T> serializer, JsonComponent.Scope scope, Class<?>[] types) {
130132
Class<T> baseType = (Class<T>) ResolvableType.forClass(JsonSerializer.class, serializer.getClass())
131133
.resolveGeneric();
134+
Assert.state(baseType != null, "'baseType' must not be null");
132135
addBeanToModule(serializer, baseType, types,
133136
(scope == Scope.VALUES) ? this::addSerializer : this::addKeySerializer);
134137

@@ -138,6 +141,7 @@ private <T> void addJsonSerializerBean(JsonSerializer<T> serializer, JsonCompone
138141
private <T> void addJsonDeserializerBean(JsonDeserializer<T> deserializer, Class<?>[] types) {
139142
Class<T> baseType = (Class<T>) ResolvableType.forClass(JsonDeserializer.class, deserializer.getClass())
140143
.resolveGeneric();
144+
Assert.state(baseType != null, "'baseType' must not be null");
141145
addBeanToModule(deserializer, baseType, types, this::addDeserializer);
142146
}
143147

@@ -162,12 +166,13 @@ private <E, T> void addBeanToModule(E element, Class<T> baseType, Class<?>[] typ
162166
static class JsonComponentBeanFactoryInitializationAotProcessor implements BeanFactoryInitializationAotProcessor {
163167

164168
@Override
165-
public BeanFactoryInitializationAotContribution processAheadOfTime(
169+
public @Nullable BeanFactoryInitializationAotContribution processAheadOfTime(
166170
ConfigurableListableBeanFactory beanFactory) {
167171
String[] jsonComponents = beanFactory.getBeanNamesForAnnotation(JsonComponent.class);
168172
Map<Class<?>, List<Class<?>>> innerComponents = new HashMap<>();
169173
for (String jsonComponent : jsonComponents) {
170174
Class<?> type = beanFactory.getType(jsonComponent, true);
175+
Assert.state(type != null, "'type' must not be null");
171176
for (Class<?> declaredClass : type.getDeclaredClasses()) {
172177
if (isSuitableInnerClass(declaredClass)) {
173178
innerComponents.computeIfAbsent(type, (t) -> new ArrayList<>()).add(declaredClass);

module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/JsonMixinModule.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import com.fasterxml.jackson.databind.Module;
2020
import com.fasterxml.jackson.databind.module.SimpleModule;
21+
import org.jspecify.annotations.Nullable;
2122

2223
/**
2324
* Spring Bean and Jackson {@link Module} to find and
@@ -36,7 +37,7 @@ public class JsonMixinModule extends SimpleModule {
3637
* @param entries the entries to register to this instance
3738
* @param classLoader the classloader to use
3839
*/
39-
public void registerEntries(JsonMixinModuleEntries entries, ClassLoader classLoader) {
40+
public void registerEntries(JsonMixinModuleEntries entries, @Nullable ClassLoader classLoader) {
4041
entries.doWithEntry(classLoader, this::setMixInAnnotation);
4142
}
4243

module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/JsonMixinModuleEntries.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import java.util.function.BiConsumer;
2323
import java.util.function.Consumer;
2424

25+
import org.jspecify.annotations.Nullable;
26+
2527
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
2628
import org.springframework.beans.factory.config.BeanDefinition;
2729
import org.springframework.context.ApplicationContext;
@@ -77,8 +79,9 @@ public static JsonMixinModuleEntries scan(ApplicationContext context, Collection
7779
for (String basePackage : basePackages) {
7880
if (StringUtils.hasText(basePackage)) {
7981
for (BeanDefinition candidate : scanner.findCandidateComponents(basePackage)) {
80-
Class<?> mixinClass = ClassUtils.resolveClassName(candidate.getBeanClassName(),
81-
context.getClassLoader());
82+
String beanClassName = candidate.getBeanClassName();
83+
Assert.state(beanClassName != null, "'beanClassName' must not be null");
84+
Class<?> mixinClass = ClassUtils.resolveClassName(beanClassName, context.getClassLoader());
8285
registerMixinClass(builder, mixinClass);
8386
}
8487
}
@@ -104,12 +107,12 @@ private static void registerMixinClass(Builder builder, Class<?> mixinClass) {
104107
* @param classLoader the classloader to use to resolve class name if necessary
105108
* @param action the action to invoke on each type to mixin class entry
106109
*/
107-
public void doWithEntry(ClassLoader classLoader, BiConsumer<Class<?>, Class<?>> action) {
110+
public void doWithEntry(@Nullable ClassLoader classLoader, BiConsumer<Class<?>, Class<?>> action) {
108111
this.entries.forEach((type, mixin) -> action.accept(resolveClassNameIfNecessary(type, classLoader),
109112
resolveClassNameIfNecessary(mixin, classLoader)));
110113
}
111114

112-
private Class<?> resolveClassNameIfNecessary(Object nameOrType, ClassLoader classLoader) {
115+
private Class<?> resolveClassNameIfNecessary(Object nameOrType, @Nullable ClassLoader classLoader) {
113116
return (nameOrType instanceof Class<?> type) ? type
114117
: ClassUtils.resolveClassName((String) nameOrType, classLoader);
115118
}

module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/JsonMixinModuleEntriesBeanRegistrationAotProcessor.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
import javax.lang.model.element.Modifier;
2323

24+
import org.jspecify.annotations.Nullable;
25+
2426
import org.springframework.aot.generate.AccessControl;
2527
import org.springframework.aot.generate.GeneratedMethod;
2628
import org.springframework.aot.generate.GenerationContext;
@@ -44,7 +46,7 @@
4446
class JsonMixinModuleEntriesBeanRegistrationAotProcessor implements BeanRegistrationAotProcessor {
4547

4648
@Override
47-
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
49+
public @Nullable BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
4850
if (registeredBean.getBeanClass().equals(JsonMixinModuleEntries.class)) {
4951
return BeanRegistrationAotContribution
5052
.withCustomCodeFragments((codeFragments) -> new AotContribution(codeFragments, registeredBean));
@@ -58,7 +60,7 @@ static class AotContribution extends BeanRegistrationCodeFragmentsDecorator {
5860

5961
private final RegisteredBean registeredBean;
6062

61-
private final ClassLoader classLoader;
63+
private final @Nullable ClassLoader classLoader;
6264

6365
AotContribution(BeanRegistrationCodeFragments delegate, RegisteredBean registeredBean) {
6466
super(delegate);

module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/JsonObjectDeserializer.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import com.fasterxml.jackson.databind.JsonMappingException;
3030
import com.fasterxml.jackson.databind.JsonNode;
3131
import com.fasterxml.jackson.databind.node.NullNode;
32+
import org.jspecify.annotations.Nullable;
3233

3334
import org.springframework.util.Assert;
3435

@@ -86,7 +87,8 @@ protected abstract T deserializeObject(JsonParser jsonParser, DeserializationCon
8687
* @return the node value or {@code null}
8788
* @since 3.4.0
8889
*/
89-
protected final <D, R> R nullSafeValue(JsonNode jsonNode, Class<D> type, Function<D, R> mapper) {
90+
protected final <D, R> @Nullable R nullSafeValue(@Nullable JsonNode jsonNode, Class<D> type,
91+
Function<D, R> mapper) {
9092
D value = nullSafeValue(jsonNode, type);
9193
return (value != null) ? mapper.apply(value) : null;
9294
}
@@ -102,7 +104,7 @@ protected final <D, R> R nullSafeValue(JsonNode jsonNode, Class<D> type, Functio
102104
* @return the node value or {@code null}
103105
*/
104106
@SuppressWarnings({ "unchecked" })
105-
protected final <D> D nullSafeValue(JsonNode jsonNode, Class<D> type) {
107+
protected final <D> @Nullable D nullSafeValue(@Nullable JsonNode jsonNode, Class<D> type) {
106108
Assert.notNull(type, "'type' must not be null");
107109
if (jsonNode == null) {
108110
return null;

module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import com.fasterxml.jackson.databind.SerializationFeature;
3939
import com.fasterxml.jackson.databind.cfg.ConstructorDetector;
4040
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
41+
import org.jspecify.annotations.Nullable;
4142

4243
import org.springframework.aot.hint.ReflectionHints;
4344
import org.springframework.aot.hint.RuntimeHints;
@@ -293,7 +294,6 @@ private void configurePropertyNamingStrategyField(Jackson2ObjectMapperBuilder bu
293294
// Find the field (this way we automatically support new constants
294295
// that may be added by Jackson in the future)
295296
Field field = findPropertyNamingStrategyField(fieldName);
296-
Assert.state(field != null, () -> "Constant named '" + fieldName + "' not found");
297297
try {
298298
builder.propertyNamingStrategy((PropertyNamingStrategy) field.get(null));
299299
}
@@ -303,8 +303,10 @@ private void configurePropertyNamingStrategyField(Jackson2ObjectMapperBuilder bu
303303
}
304304

305305
private Field findPropertyNamingStrategyField(String fieldName) {
306-
return ReflectionUtils.findField(com.fasterxml.jackson.databind.PropertyNamingStrategies.class,
307-
fieldName, PropertyNamingStrategy.class);
306+
Field field = ReflectionUtils.findField(PropertyNamingStrategies.class, fieldName,
307+
PropertyNamingStrategy.class);
308+
Assert.state(field != null, () -> "Constant named '" + fieldName + "' not found");
309+
return field;
308310
}
309311

310312
private void configureModules(Jackson2ObjectMapperBuilder builder) {
@@ -349,7 +351,7 @@ private void configureConstructorDetector(Jackson2ObjectMapperBuilder builder) {
349351
static class JacksonAutoConfigurationRuntimeHints implements RuntimeHintsRegistrar {
350352

351353
@Override
352-
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
354+
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
353355
if (ClassUtils.isPresent("com.fasterxml.jackson.databind.PropertyNamingStrategy", classLoader)) {
354356
registerPropertyNamingStrategyHints(hints.reflection());
355357
}

module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/autoconfigure/JacksonProperties.java

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import com.fasterxml.jackson.databind.SerializationFeature;
3232
import com.fasterxml.jackson.databind.cfg.EnumFeature;
3333
import com.fasterxml.jackson.databind.cfg.JsonNodeFeature;
34+
import org.jspecify.annotations.Nullable;
3435

3536
import org.springframework.boot.context.properties.ConfigurationProperties;
3637

@@ -50,13 +51,13 @@ public class JacksonProperties {
5051
* Date format string or a fully-qualified date format class name. For instance,
5152
* 'yyyy-MM-dd HH:mm:ss'.
5253
*/
53-
private String dateFormat;
54+
private @Nullable String dateFormat;
5455

5556
/**
5657
* One of the constants on Jackson's PropertyNamingStrategies. Can also be a
5758
* fully-qualified class name of a PropertyNamingStrategy implementation.
5859
*/
59-
private String propertyNamingStrategy;
60+
private @Nullable String propertyNamingStrategy;
6061

6162
/**
6263
* Jackson visibility thresholds that can be used to limit which methods (and fields)
@@ -93,45 +94,45 @@ public class JacksonProperties {
9394
* Controls the inclusion of properties during serialization. Configured with one of
9495
* the values in Jackson's JsonInclude.Include enumeration.
9596
*/
96-
private JsonInclude.Include defaultPropertyInclusion;
97+
private JsonInclude.@Nullable Include defaultPropertyInclusion;
9798

9899
/**
99100
* Global default setting (if any) for leniency.
100101
*/
101-
private Boolean defaultLeniency;
102+
private @Nullable Boolean defaultLeniency;
102103

103104
/**
104105
* Strategy to use to auto-detect constructor, and in particular behavior with
105106
* single-argument constructors.
106107
*/
107-
private ConstructorDetectorStrategy constructorDetector;
108+
private @Nullable ConstructorDetectorStrategy constructorDetector;
108109

109110
/**
110111
* Time zone used when formatting dates. For instance, "America/Los_Angeles" or
111112
* "GMT+10".
112113
*/
113-
private TimeZone timeZone = null;
114+
private @Nullable TimeZone timeZone;
114115

115116
/**
116117
* Locale used for formatting.
117118
*/
118-
private Locale locale;
119+
private @Nullable Locale locale;
119120

120121
private final Datatype datatype = new Datatype();
121122

122-
public String getDateFormat() {
123+
public @Nullable String getDateFormat() {
123124
return this.dateFormat;
124125
}
125126

126-
public void setDateFormat(String dateFormat) {
127+
public void setDateFormat(@Nullable String dateFormat) {
127128
this.dateFormat = dateFormat;
128129
}
129130

130-
public String getPropertyNamingStrategy() {
131+
public @Nullable String getPropertyNamingStrategy() {
131132
return this.propertyNamingStrategy;
132133
}
133134

134-
public void setPropertyNamingStrategy(String propertyNamingStrategy) {
135+
public void setPropertyNamingStrategy(@Nullable String propertyNamingStrategy) {
135136
this.propertyNamingStrategy = propertyNamingStrategy;
136137
}
137138

@@ -159,43 +160,43 @@ public Map<JsonGenerator.Feature, Boolean> getGenerator() {
159160
return this.generator;
160161
}
161162

162-
public JsonInclude.Include getDefaultPropertyInclusion() {
163+
public JsonInclude.@Nullable Include getDefaultPropertyInclusion() {
163164
return this.defaultPropertyInclusion;
164165
}
165166

166-
public void setDefaultPropertyInclusion(JsonInclude.Include defaultPropertyInclusion) {
167+
public void setDefaultPropertyInclusion(JsonInclude.@Nullable Include defaultPropertyInclusion) {
167168
this.defaultPropertyInclusion = defaultPropertyInclusion;
168169
}
169170

170-
public Boolean getDefaultLeniency() {
171+
public @Nullable Boolean getDefaultLeniency() {
171172
return this.defaultLeniency;
172173
}
173174

174-
public void setDefaultLeniency(Boolean defaultLeniency) {
175+
public void setDefaultLeniency(@Nullable Boolean defaultLeniency) {
175176
this.defaultLeniency = defaultLeniency;
176177
}
177178

178-
public ConstructorDetectorStrategy getConstructorDetector() {
179+
public @Nullable ConstructorDetectorStrategy getConstructorDetector() {
179180
return this.constructorDetector;
180181
}
181182

182-
public void setConstructorDetector(ConstructorDetectorStrategy constructorDetector) {
183+
public void setConstructorDetector(@Nullable ConstructorDetectorStrategy constructorDetector) {
183184
this.constructorDetector = constructorDetector;
184185
}
185186

186-
public TimeZone getTimeZone() {
187+
public @Nullable TimeZone getTimeZone() {
187188
return this.timeZone;
188189
}
189190

190-
public void setTimeZone(TimeZone timeZone) {
191+
public void setTimeZone(@Nullable TimeZone timeZone) {
191192
this.timeZone = timeZone;
192193
}
193194

194-
public Locale getLocale() {
195+
public @Nullable Locale getLocale() {
195196
return this.locale;
196197
}
197198

198-
public void setLocale(Locale locale) {
199+
public void setLocale(@Nullable Locale locale) {
199200
this.locale = locale;
200201
}
201202

module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/autoconfigure/package-info.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,7 @@
1717
/**
1818
* Auto-configuration for Jackson.
1919
*/
20+
@NullMarked
2021
package org.springframework.boot.jackson.autoconfigure;
22+
23+
import org.jspecify.annotations.NullMarked;

module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/package-info.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,7 @@
1717
/**
1818
* Custom enhancements and support for the Jackson project.
1919
*/
20+
@NullMarked
2021
package org.springframework.boot.jackson;
22+
23+
import org.jspecify.annotations.NullMarked;

0 commit comments

Comments
 (0)