Skip to content

Commit 315bbf3

Browse files
committed
Consistently declare nullability @⁠Contract for core utilities
Closes gh-34934
1 parent 9505e76 commit 315bbf3

File tree

12 files changed

+81
-17
lines changed

12 files changed

+81
-17
lines changed

spring-core/src/main/java/org/springframework/util/ClassUtils.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@
5454

5555
import org.jspecify.annotations.Nullable;
5656

57+
import org.springframework.lang.Contract;
58+
5759
/**
5860
* Miscellaneous {@code java.lang.Class} utility methods.
5961
*
@@ -246,6 +248,7 @@ private static void registerCommonClasses(Class<?>... commonClasses) {
246248
* @param classLoaderToUse the actual ClassLoader to use for the thread context
247249
* @return the original thread context ClassLoader, or {@code null} if not overridden
248250
*/
251+
@Contract("null -> null")
249252
public static @Nullable ClassLoader overrideThreadContextClassLoader(@Nullable ClassLoader classLoaderToUse) {
250253
Thread currentThread = Thread.currentThread();
251254
ClassLoader threadContextClassLoader = currentThread.getContextClassLoader();
@@ -386,6 +389,7 @@ public static boolean isPresent(String className, @Nullable ClassLoader classLoa
386389
* @param classLoader the ClassLoader to check against
387390
* (can be {@code null} in which case this method will always return {@code true})
388391
*/
392+
@Contract("_, null -> true")
389393
public static boolean isVisible(Class<?> clazz, @Nullable ClassLoader classLoader) {
390394
if (classLoader == null) {
391395
return true;
@@ -473,6 +477,7 @@ private static boolean isLoadable(Class<?> clazz, ClassLoader classLoader) {
473477
* @return the primitive class, or {@code null} if the name does not denote
474478
* a primitive class or primitive array class
475479
*/
480+
@Contract("null -> null")
476481
public static @Nullable Class<?> resolvePrimitiveClassName(@Nullable String name) {
477482
Class<?> result = null;
478483
// Most class names will be quite long, considering that they
@@ -552,6 +557,7 @@ public static Class<?> resolvePrimitiveIfNecessary(Class<?> clazz) {
552557
* @see Void
553558
* @see Void#TYPE
554559
*/
560+
@Contract("null -> false")
555561
public static boolean isVoidType(@Nullable Class<?> type) {
556562
return (type == void.class || type == Void.class);
557563
}
@@ -861,6 +867,7 @@ public static Class<?> createCompositeInterface(Class<?>[] interfaces, @Nullable
861867
* given classes is {@code null}, the other class will be returned.
862868
* @since 3.2.6
863869
*/
870+
@Contract("null, _ -> param2; _, null -> param1")
864871
public static @Nullable Class<?> determineCommonAncestor(@Nullable Class<?> clazz1, @Nullable Class<?> clazz2) {
865872
if (clazz1 == null) {
866873
return clazz2;
@@ -955,6 +962,7 @@ public static boolean isCglibProxy(Object object) {
955962
* or simply a check for containing {@link #CGLIB_CLASS_SEPARATOR}
956963
*/
957964
@Deprecated(since = "5.2")
965+
@Contract("null -> false")
958966
public static boolean isCglibProxyClass(@Nullable Class<?> clazz) {
959967
return (clazz != null && isCglibProxyClassName(clazz.getName()));
960968
}
@@ -967,6 +975,7 @@ public static boolean isCglibProxyClass(@Nullable Class<?> clazz) {
967975
* or simply a check for containing {@link #CGLIB_CLASS_SEPARATOR}
968976
*/
969977
@Deprecated(since = "5.2")
978+
@Contract("null -> false")
970979
public static boolean isCglibProxyClassName(@Nullable String className) {
971980
return (className != null && className.contains(CGLIB_CLASS_SEPARATOR));
972981
}
@@ -1007,6 +1016,7 @@ public static Class<?> getUserClass(Class<?> clazz) {
10071016
* @param value the value to introspect
10081017
* @return the qualified name of the class
10091018
*/
1019+
@Contract("null -> null")
10101020
public static @Nullable String getDescriptiveType(@Nullable Object value) {
10111021
if (value == null) {
10121022
return null;
@@ -1030,6 +1040,7 @@ public static Class<?> getUserClass(Class<?> clazz) {
10301040
* @param clazz the class to check
10311041
* @param typeName the type name to match
10321042
*/
1043+
@Contract("_, null -> false")
10331044
public static boolean matchesTypeName(Class<?> clazz, @Nullable String typeName) {
10341045
return (typeName != null &&
10351046
(typeName.equals(clazz.getTypeName()) || typeName.equals(clazz.getSimpleName())));

spring-core/src/main/java/org/springframework/util/CollectionUtils.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ public static <K, V> void mergePropertiesIntoMap(@Nullable Properties props, Map
200200
* @param element the element to look for
201201
* @return {@code true} if found, {@code false} otherwise
202202
*/
203+
@Contract("null, _ -> false")
203204
public static boolean contains(@Nullable Iterator<?> iterator, Object element) {
204205
if (iterator != null) {
205206
while (iterator.hasNext()) {
@@ -218,6 +219,7 @@ public static boolean contains(@Nullable Iterator<?> iterator, Object element) {
218219
* @param element the element to look for
219220
* @return {@code true} if found, {@code false} otherwise
220221
*/
222+
@Contract("null, _ -> false")
221223
public static boolean contains(@Nullable Enumeration<?> enumeration, Object element) {
222224
if (enumeration != null) {
223225
while (enumeration.hasMoreElements()) {
@@ -238,6 +240,7 @@ public static boolean contains(@Nullable Enumeration<?> enumeration, Object elem
238240
* @param element the element to look for
239241
* @return {@code true} if found, {@code false} otherwise
240242
*/
243+
@Contract("null, _ -> false")
241244
public static boolean containsInstance(@Nullable Collection<?> collection, Object element) {
242245
if (collection != null) {
243246
for (Object candidate : collection) {
@@ -289,6 +292,7 @@ public static boolean containsAny(Collection<?> source, Collection<?> candidates
289292
* or {@code null} if none or more than one such value found
290293
*/
291294
@SuppressWarnings("unchecked")
295+
@Contract("null, _ -> null")
292296
public static <T> @Nullable T findValueOfType(@Nullable Collection<?> collection, @Nullable Class<T> type) {
293297
if (isEmpty(collection)) {
294298
return null;
@@ -386,6 +390,7 @@ else if (candidate != val.getClass()) {
386390
* @see LinkedHashMap#keySet()
387391
* @see java.util.LinkedHashSet
388392
*/
393+
@Contract("null -> null")
389394
public static <T> @Nullable T firstElement(@Nullable Set<T> set) {
390395
if (isEmpty(set)) {
391396
return null;
@@ -408,6 +413,7 @@ else if (candidate != val.getClass()) {
408413
* @return the first element, or {@code null} if none
409414
* @since 5.2.3
410415
*/
416+
@Contract("null -> null")
411417
public static <T> @Nullable T firstElement(@Nullable List<T> list) {
412418
if (isEmpty(list)) {
413419
return null;
@@ -425,6 +431,7 @@ else if (candidate != val.getClass()) {
425431
* @see LinkedHashMap#keySet()
426432
* @see java.util.LinkedHashSet
427433
*/
434+
@Contract("null -> null")
428435
public static <T> @Nullable T lastElement(@Nullable Set<T> set) {
429436
if (isEmpty(set)) {
430437
return null;
@@ -448,6 +455,7 @@ else if (candidate != val.getClass()) {
448455
* @return the last element, or {@code null} if none
449456
* @since 5.0.3
450457
*/
458+
@Contract("null -> null")
451459
public static <T> @Nullable T lastElement(@Nullable List<T> list) {
452460
if (isEmpty(list)) {
453461
return null;

spring-core/src/main/java/org/springframework/util/FileSystemUtils.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -28,6 +28,8 @@
2828

2929
import org.jspecify.annotations.Nullable;
3030

31+
import org.springframework.lang.Contract;
32+
3133
import static java.nio.file.FileVisitOption.FOLLOW_LINKS;
3234

3335
/**
@@ -54,6 +56,7 @@ public abstract class FileSystemUtils {
5456
* @return {@code true} if the {@code File} was successfully deleted,
5557
* otherwise {@code false}
5658
*/
59+
@Contract("null -> false")
5760
public static boolean deleteRecursively(@Nullable File root) {
5861
if (root == null) {
5962
return false;
@@ -76,6 +79,7 @@ public static boolean deleteRecursively(@Nullable File root) {
7679
* @throws IOException in the case of I/O errors
7780
* @since 5.0
7881
*/
82+
@Contract("null -> false")
7983
public static boolean deleteRecursively(@Nullable Path root) throws IOException {
8084
if (root == null) {
8185
return false;

spring-core/src/main/java/org/springframework/util/ObjectUtils.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ public static boolean isEmpty(@Nullable Object obj) {
172172
* if the {@code Optional} is empty, or simply the given object as-is
173173
* @since 5.0
174174
*/
175+
@Contract("null -> null")
175176
public static @Nullable Object unwrapOptional(@Nullable Object obj) {
176177
if (obj instanceof Optional<?> optional) {
177178
Object result = optional.orElse(null);
@@ -188,6 +189,7 @@ public static boolean isEmpty(@Nullable Object obj) {
188189
* @param element the element to check for
189190
* @return whether the element has been found in the given array
190191
*/
192+
@Contract("null, _ -> false")
191193
public static boolean containsElement(@Nullable Object @Nullable [] array, @Nullable Object element) {
192194
if (array == null) {
193195
return false;

spring-core/src/main/java/org/springframework/util/PatternMatchUtils.java

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

1919
import org.jspecify.annotations.Nullable;
2020

21+
import org.springframework.lang.Contract;
22+
2123
/**
2224
* Utility methods for simple pattern matching, in particular for Spring's typical
2325
* {@code xxx*}, {@code *xxx}, {@code *xxx*}, and {@code xxx*yyy} pattern styles.
@@ -36,6 +38,7 @@ public abstract class PatternMatchUtils {
3638
* @param str the String to match
3739
* @return whether the String matches the given pattern
3840
*/
41+
@Contract("null, _ -> false; _, null -> false")
3942
public static boolean simpleMatch(@Nullable String pattern, @Nullable String str) {
4043
return simpleMatch(pattern, str, false);
4144
}
@@ -44,6 +47,7 @@ public static boolean simpleMatch(@Nullable String pattern, @Nullable String str
4447
* Variant of {@link #simpleMatch(String, String)} that ignores upper/lower case.
4548
* @since 6.1.20
4649
*/
50+
@Contract("null, _ -> false; _, null -> false")
4751
public static boolean simpleMatchIgnoreCase(@Nullable String pattern, @Nullable String str) {
4852
return simpleMatch(pattern, str, true);
4953
}
@@ -113,6 +117,7 @@ private static int indexOf(String str, String otherStr, int startIndex, boolean
113117
* @param str the String to match
114118
* @return whether the String matches any of the given patterns
115119
*/
120+
@Contract("null, _ -> false; _, null -> false")
116121
public static boolean simpleMatch(String @Nullable [] patterns, @Nullable String str) {
117122
if (patterns != null) {
118123
for (String pattern : patterns) {
@@ -125,9 +130,10 @@ public static boolean simpleMatch(String @Nullable [] patterns, @Nullable String
125130
}
126131

127132
/**
128-
* Variant of {@link #simpleMatch(String[], String)} that ignores upper/lower case.
133+
* Variant of {@link #simpleMatch(String[], String)} that ignores upper/lower case.
129134
* @since 6.1.20
130135
*/
136+
@Contract("null, _ -> false; _, null -> false")
131137
public static boolean simpleMatchIgnoreCase(String @Nullable [] patterns, @Nullable String str) {
132138
if (patterns != null) {
133139
for (String pattern : patterns) {

spring-core/src/main/java/org/springframework/util/ReflectionUtils.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929

3030
import org.jspecify.annotations.Nullable;
3131

32+
import org.springframework.lang.Contract;
33+
3234
/**
3335
* Simple utility class for working with the reflection API and handling
3436
* reflection exceptions.
@@ -137,6 +139,7 @@ public static void handleInvocationTargetException(InvocationTargetException ex)
137139
* @param ex the exception to rethrow
138140
* @throws RuntimeException the rethrown exception
139141
*/
142+
@Contract("_ -> fail")
140143
public static void rethrowRuntimeException(@Nullable Throwable ex) {
141144
if (ex instanceof RuntimeException runtimeException) {
142145
throw runtimeException;
@@ -158,6 +161,7 @@ public static void rethrowRuntimeException(@Nullable Throwable ex) {
158161
* @param throwable the exception to rethrow
159162
* @throws Exception the rethrown exception (in case of a checked exception)
160163
*/
164+
@Contract("_ -> fail")
161165
public static void rethrowException(@Nullable Throwable throwable) throws Exception {
162166
if (throwable instanceof Exception exception) {
163167
throw exception;
@@ -501,6 +505,7 @@ private static Method[] getDeclaredMethods(Class<?> clazz, boolean defensive) {
501505
* Determine whether the given method is an "equals" method.
502506
* @see java.lang.Object#equals(Object)
503507
*/
508+
@Contract("null -> false")
504509
public static boolean isEqualsMethod(@Nullable Method method) {
505510
return (method != null && method.getParameterCount() == 1 && method.getName().equals("equals") &&
506511
method.getParameterTypes()[0] == Object.class);
@@ -510,6 +515,7 @@ public static boolean isEqualsMethod(@Nullable Method method) {
510515
* Determine whether the given method is a "hashCode" method.
511516
* @see java.lang.Object#hashCode()
512517
*/
518+
@Contract("null -> false")
513519
public static boolean isHashCodeMethod(@Nullable Method method) {
514520
return (method != null && method.getParameterCount() == 0 && method.getName().equals("hashCode"));
515521
}
@@ -518,13 +524,15 @@ public static boolean isHashCodeMethod(@Nullable Method method) {
518524
* Determine whether the given method is a "toString" method.
519525
* @see java.lang.Object#toString()
520526
*/
527+
@Contract("null -> false")
521528
public static boolean isToStringMethod(@Nullable Method method) {
522529
return (method != null && method.getParameterCount() == 0 && method.getName().equals("toString"));
523530
}
524531

525532
/**
526533
* Determine whether the given method is originally declared by {@link java.lang.Object}.
527534
*/
535+
@Contract("null -> false")
528536
public static boolean isObjectMethod(@Nullable Method method) {
529537
return (method != null && (method.getDeclaringClass() == Object.class ||
530538
isEqualsMethod(method) || isHashCodeMethod(method) || isToStringMethod(method)));
@@ -585,6 +593,7 @@ public static void makeAccessible(Method method) {
585593
* @param type the type of the field (may be {@code null} if name is specified)
586594
* @return the corresponding Field object, or {@code null} if not found
587595
*/
596+
@Contract("_, null, null -> fail")
588597
public static @Nullable Field findField(Class<?> clazz, @Nullable String name, @Nullable Class<?> type) {
589598
Assert.notNull(clazz, "Class must not be null");
590599
Assert.isTrue(name != null || type != null, "Either name or type of the field must be specified");

spring-core/src/main/java/org/springframework/util/ResourceUtils.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -28,6 +28,8 @@
2828

2929
import org.jspecify.annotations.Nullable;
3030

31+
import org.springframework.lang.Contract;
32+
3133
/**
3234
* Utility methods for resolving resource locations to files in the
3335
* file system. Mainly for internal use within the framework.
@@ -104,6 +106,7 @@ public abstract class ResourceUtils {
104106
* @see java.net.URL
105107
* @see #toURL(String)
106108
*/
109+
@Contract("null -> false")
107110
public static boolean isUrl(@Nullable String resourceLocation) {
108111
if (resourceLocation == null) {
109112
return false;

spring-core/src/main/java/org/springframework/util/SerializationUtils.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525

2626
import org.jspecify.annotations.Nullable;
2727

28+
import org.springframework.lang.Contract;
29+
2830
/**
2931
* Static utilities for serialization and deserialization using
3032
* <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/serialization/"
@@ -47,6 +49,7 @@ public abstract class SerializationUtils {
4749
* @param object the object to serialize
4850
* @return an array of bytes representing the object in a portable fashion
4951
*/
52+
@Contract("null -> null")
5053
public static byte @Nullable [] serialize(@Nullable Object object) {
5154
if (object == null) {
5255
return null;
@@ -73,6 +76,7 @@ public abstract class SerializationUtils {
7376
* any other format) which is regularly checked and updated for not allowing RCE.
7477
*/
7578
@Deprecated(since = "6.0")
79+
@Contract("null -> null")
7680
public static @Nullable Object deserialize(byte @Nullable [] bytes) {
7781
if (bytes == null) {
7882
return null;

0 commit comments

Comments
 (0)