diff --git a/spring-core/src/main/java/org/springframework/util/ClassUtils.java b/spring-core/src/main/java/org/springframework/util/ClassUtils.java index 7601998947d..b5a933c1d59 100644 --- a/spring-core/src/main/java/org/springframework/util/ClassUtils.java +++ b/spring-core/src/main/java/org/springframework/util/ClassUtils.java @@ -54,6 +54,8 @@ import java.util.regex.Pattern; import org.jspecify.annotations.Nullable; +import org.springframework.lang.Contract; + /** * Miscellaneous {@code java.lang.Class} utility methods. * @@ -246,6 +248,7 @@ public abstract class ClassUtils { * @param classLoaderToUse the actual ClassLoader to use for the thread context * @return the original thread context ClassLoader, or {@code null} if not overridden */ + @Contract("null -> null") public static @Nullable ClassLoader overrideThreadContextClassLoader(@Nullable ClassLoader classLoaderToUse) { Thread currentThread = Thread.currentThread(); ClassLoader threadContextClassLoader = currentThread.getContextClassLoader(); @@ -386,6 +389,7 @@ public abstract class ClassUtils { * @param classLoader the ClassLoader to check against * (can be {@code null} in which case this method will always return {@code true}) */ + @Contract("_, null -> true") public static boolean isVisible(Class clazz, @Nullable ClassLoader classLoader) { if (classLoader == null) { return true; @@ -473,6 +477,7 @@ public abstract class ClassUtils { * @return the primitive class, or {@code null} if the name does not denote * a primitive class or primitive array class */ + @Contract("null -> null") public static @Nullable Class resolvePrimitiveClassName(@Nullable String name) { Class result = null; // Most class names will be quite long, considering that they @@ -552,6 +557,7 @@ public abstract class ClassUtils { * @see Void * @see Void#TYPE */ + @Contract("null -> false") public static boolean isVoidType(@Nullable Class type) { return (type == void.class || type == Void.class); } @@ -861,6 +867,7 @@ public abstract class ClassUtils { * given classes is {@code null}, the other class will be returned. * @since 3.2.6 */ + @Contract("null, _ -> param2; _, null -> param1") public static @Nullable Class determineCommonAncestor(@Nullable Class clazz1, @Nullable Class clazz2) { if (clazz1 == null) { return clazz2; @@ -955,6 +962,7 @@ public abstract class ClassUtils { * or simply a check for containing {@link #CGLIB_CLASS_SEPARATOR} */ @Deprecated(since = "5.2") + @Contract("null -> false") public static boolean isCglibProxyClass(@Nullable Class clazz) { return (clazz != null && isCglibProxyClassName(clazz.getName())); } @@ -967,6 +975,7 @@ public abstract class ClassUtils { * or simply a check for containing {@link #CGLIB_CLASS_SEPARATOR} */ @Deprecated(since = "5.2") + @Contract("null -> false") public static boolean isCglibProxyClassName(@Nullable String className) { return (className != null && className.contains(CGLIB_CLASS_SEPARATOR)); } @@ -1007,6 +1016,7 @@ public abstract class ClassUtils { * @param value the value to introspect * @return the qualified name of the class */ + @Contract("null -> null") public static @Nullable String getDescriptiveType(@Nullable Object value) { if (value == null) { return null; @@ -1030,6 +1040,7 @@ public abstract class ClassUtils { * @param clazz the class to check * @param typeName the type name to match */ + @Contract("_, null -> false") public static boolean matchesTypeName(Class clazz, @Nullable String typeName) { return (typeName != null && (typeName.equals(clazz.getTypeName()) || typeName.equals(clazz.getSimpleName()))); diff --git a/spring-core/src/main/java/org/springframework/util/CollectionUtils.java b/spring-core/src/main/java/org/springframework/util/CollectionUtils.java index a485046a96a..d1c0c46a709 100644 --- a/spring-core/src/main/java/org/springframework/util/CollectionUtils.java +++ b/spring-core/src/main/java/org/springframework/util/CollectionUtils.java @@ -200,6 +200,7 @@ public abstract class CollectionUtils { * @param element the element to look for * @return {@code true} if found, {@code false} otherwise */ + @Contract("null, _ -> false") public static boolean contains(@Nullable Iterator iterator, Object element) { if (iterator != null) { while (iterator.hasNext()) { @@ -218,6 +219,7 @@ public abstract class CollectionUtils { * @param element the element to look for * @return {@code true} if found, {@code false} otherwise */ + @Contract("null, _ -> false") public static boolean contains(@Nullable Enumeration enumeration, Object element) { if (enumeration != null) { while (enumeration.hasMoreElements()) { @@ -238,6 +240,7 @@ public abstract class CollectionUtils { * @param element the element to look for * @return {@code true} if found, {@code false} otherwise */ + @Contract("null, _ -> false") public static boolean containsInstance(@Nullable Collection collection, Object element) { if (collection != null) { for (Object candidate : collection) { @@ -289,6 +292,7 @@ public abstract class CollectionUtils { * or {@code null} if none or more than one such value found */ @SuppressWarnings("unchecked") + @Contract("null, _ -> null") public static @Nullable T findValueOfType(@Nullable Collection collection, @Nullable Class type) { if (isEmpty(collection)) { return null; @@ -386,6 +390,7 @@ public abstract class CollectionUtils { * @see LinkedHashMap#keySet() * @see java.util.LinkedHashSet */ + @Contract("null -> null") public static @Nullable T firstElement(@Nullable Set set) { if (isEmpty(set)) { return null; @@ -408,6 +413,7 @@ public abstract class CollectionUtils { * @return the first element, or {@code null} if none * @since 5.2.3 */ + @Contract("null -> null") public static @Nullable T firstElement(@Nullable List list) { if (isEmpty(list)) { return null; @@ -425,6 +431,7 @@ public abstract class CollectionUtils { * @see LinkedHashMap#keySet() * @see java.util.LinkedHashSet */ + @Contract("null -> null") public static @Nullable T lastElement(@Nullable Set set) { if (isEmpty(set)) { return null; @@ -448,6 +455,7 @@ public abstract class CollectionUtils { * @return the last element, or {@code null} if none * @since 5.0.3 */ + @Contract("null -> null") public static @Nullable T lastElement(@Nullable List list) { if (isEmpty(list)) { return null; diff --git a/spring-core/src/main/java/org/springframework/util/FileSystemUtils.java b/spring-core/src/main/java/org/springframework/util/FileSystemUtils.java index c857588d18f..2bbe365c8d3 100644 --- a/spring-core/src/main/java/org/springframework/util/FileSystemUtils.java +++ b/spring-core/src/main/java/org/springframework/util/FileSystemUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,8 @@ import java.util.EnumSet; import org.jspecify.annotations.Nullable; +import org.springframework.lang.Contract; + import static java.nio.file.FileVisitOption.FOLLOW_LINKS; /** @@ -54,6 +56,7 @@ public abstract class FileSystemUtils { * @return {@code true} if the {@code File} was successfully deleted, * otherwise {@code false} */ + @Contract("null -> false") public static boolean deleteRecursively(@Nullable File root) { if (root == null) { return false; @@ -76,6 +79,7 @@ public abstract class FileSystemUtils { * @throws IOException in the case of I/O errors * @since 5.0 */ + @Contract("null -> false") public static boolean deleteRecursively(@Nullable Path root) throws IOException { if (root == null) { return false; diff --git a/spring-core/src/main/java/org/springframework/util/ObjectUtils.java b/spring-core/src/main/java/org/springframework/util/ObjectUtils.java index 8bcd8578595..1496a2e8386 100644 --- a/spring-core/src/main/java/org/springframework/util/ObjectUtils.java +++ b/spring-core/src/main/java/org/springframework/util/ObjectUtils.java @@ -172,6 +172,7 @@ public abstract class ObjectUtils { * if the {@code Optional} is empty, or simply the given object as-is * @since 5.0 */ + @Contract("null -> null") public static @Nullable Object unwrapOptional(@Nullable Object obj) { if (obj instanceof Optional optional) { Object result = optional.orElse(null); @@ -188,6 +189,7 @@ public abstract class ObjectUtils { * @param element the element to check for * @return whether the element has been found in the given array */ + @Contract("null, _ -> false") public static boolean containsElement(@Nullable Object @Nullable [] array, @Nullable Object element) { if (array == null) { return false; diff --git a/spring-core/src/main/java/org/springframework/util/PatternMatchUtils.java b/spring-core/src/main/java/org/springframework/util/PatternMatchUtils.java index 35d54aaba1d..b4c0d56902f 100644 --- a/spring-core/src/main/java/org/springframework/util/PatternMatchUtils.java +++ b/spring-core/src/main/java/org/springframework/util/PatternMatchUtils.java @@ -18,6 +18,8 @@ package org.springframework.util; import org.jspecify.annotations.Nullable; +import org.springframework.lang.Contract; + /** * Utility methods for simple pattern matching, in particular for Spring's typical * {@code xxx*}, {@code *xxx}, {@code *xxx*}, and {@code xxx*yyy} pattern styles. @@ -36,6 +38,7 @@ public abstract class PatternMatchUtils { * @param str the String to match * @return whether the String matches the given pattern */ + @Contract("null, _ -> false; _, null -> false") public static boolean simpleMatch(@Nullable String pattern, @Nullable String str) { return simpleMatch(pattern, str, false); } @@ -44,6 +47,7 @@ public abstract class PatternMatchUtils { * Variant of {@link #simpleMatch(String, String)} that ignores upper/lower case. * @since 6.1.20 */ + @Contract("null, _ -> false; _, null -> false") public static boolean simpleMatchIgnoreCase(@Nullable String pattern, @Nullable String str) { return simpleMatch(pattern, str, true); } @@ -113,6 +117,7 @@ public abstract class PatternMatchUtils { * @param str the String to match * @return whether the String matches any of the given patterns */ + @Contract("null, _ -> false; _, null -> false") public static boolean simpleMatch(String @Nullable [] patterns, @Nullable String str) { if (patterns != null) { for (String pattern : patterns) { @@ -125,9 +130,10 @@ public abstract class PatternMatchUtils { } /** - * Variant of {@link #simpleMatch(String[], String)} that ignores upper/lower case. + * Variant of {@link #simpleMatch(String[], String)} that ignores upper/lower case. * @since 6.1.20 */ + @Contract("null, _ -> false; _, null -> false") public static boolean simpleMatchIgnoreCase(String @Nullable [] patterns, @Nullable String str) { if (patterns != null) { for (String pattern : patterns) { diff --git a/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java b/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java index e5dced175ed..80ad6c5ba3e 100644 --- a/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java +++ b/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java @@ -29,6 +29,8 @@ import java.util.Map; import org.jspecify.annotations.Nullable; +import org.springframework.lang.Contract; + /** * Simple utility class for working with the reflection API and handling * reflection exceptions. @@ -137,6 +139,7 @@ public abstract class ReflectionUtils { * @param ex the exception to rethrow * @throws RuntimeException the rethrown exception */ + @Contract("_ -> fail") public static void rethrowRuntimeException(@Nullable Throwable ex) { if (ex instanceof RuntimeException runtimeException) { throw runtimeException; @@ -158,6 +161,7 @@ public abstract class ReflectionUtils { * @param throwable the exception to rethrow * @throws Exception the rethrown exception (in case of a checked exception) */ + @Contract("_ -> fail") public static void rethrowException(@Nullable Throwable throwable) throws Exception { if (throwable instanceof Exception exception) { throw exception; @@ -501,6 +505,7 @@ public abstract class ReflectionUtils { * Determine whether the given method is an "equals" method. * @see java.lang.Object#equals(Object) */ + @Contract("null -> false") public static boolean isEqualsMethod(@Nullable Method method) { return (method != null && method.getParameterCount() == 1 && method.getName().equals("equals") && method.getParameterTypes()[0] == Object.class); @@ -510,6 +515,7 @@ public abstract class ReflectionUtils { * Determine whether the given method is a "hashCode" method. * @see java.lang.Object#hashCode() */ + @Contract("null -> false") public static boolean isHashCodeMethod(@Nullable Method method) { return (method != null && method.getParameterCount() == 0 && method.getName().equals("hashCode")); } @@ -518,6 +524,7 @@ public abstract class ReflectionUtils { * Determine whether the given method is a "toString" method. * @see java.lang.Object#toString() */ + @Contract("null -> false") public static boolean isToStringMethod(@Nullable Method method) { return (method != null && method.getParameterCount() == 0 && method.getName().equals("toString")); } @@ -525,6 +532,7 @@ public abstract class ReflectionUtils { /** * Determine whether the given method is originally declared by {@link java.lang.Object}. */ + @Contract("null -> false") public static boolean isObjectMethod(@Nullable Method method) { return (method != null && (method.getDeclaringClass() == Object.class || isEqualsMethod(method) || isHashCodeMethod(method) || isToStringMethod(method))); @@ -585,6 +593,7 @@ public abstract class ReflectionUtils { * @param type the type of the field (may be {@code null} if name is specified) * @return the corresponding Field object, or {@code null} if not found */ + @Contract("_, null, null -> fail") public static @Nullable Field findField(Class clazz, @Nullable String name, @Nullable Class type) { Assert.notNull(clazz, "Class must not be null"); Assert.isTrue(name != null || type != null, "Either name or type of the field must be specified"); diff --git a/spring-core/src/main/java/org/springframework/util/ResourceUtils.java b/spring-core/src/main/java/org/springframework/util/ResourceUtils.java index 7cf9c53cc75..ea544470caf 100644 --- a/spring-core/src/main/java/org/springframework/util/ResourceUtils.java +++ b/spring-core/src/main/java/org/springframework/util/ResourceUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,8 @@ import java.util.Locale; import org.jspecify.annotations.Nullable; +import org.springframework.lang.Contract; + /** * Utility methods for resolving resource locations to files in the * file system. Mainly for internal use within the framework. @@ -104,6 +106,7 @@ public abstract class ResourceUtils { * @see java.net.URL * @see #toURL(String) */ + @Contract("null -> false") public static boolean isUrl(@Nullable String resourceLocation) { if (resourceLocation == null) { return false; diff --git a/spring-core/src/main/java/org/springframework/util/SerializationUtils.java b/spring-core/src/main/java/org/springframework/util/SerializationUtils.java index 166472af39d..79c58b1e7b2 100644 --- a/spring-core/src/main/java/org/springframework/util/SerializationUtils.java +++ b/spring-core/src/main/java/org/springframework/util/SerializationUtils.java @@ -25,6 +25,8 @@ import java.io.Serializable; import org.jspecify.annotations.Nullable; +import org.springframework.lang.Contract; + /** * Static utilities for serialization and deserialization using * null") public static byte @Nullable [] serialize(@Nullable Object object) { if (object == null) { return null; @@ -73,6 +76,7 @@ public abstract class SerializationUtils { * any other format) which is regularly checked and updated for not allowing RCE. */ @Deprecated(since = "6.0") + @Contract("null -> null") public static @Nullable Object deserialize(byte @Nullable [] bytes) { if (bytes == null) { return null; diff --git a/spring-core/src/main/java/org/springframework/util/StringUtils.java b/spring-core/src/main/java/org/springframework/util/StringUtils.java index ffdc2d5f56e..a8d63b1d281 100644 --- a/spring-core/src/main/java/org/springframework/util/StringUtils.java +++ b/spring-core/src/main/java/org/springframework/util/StringUtils.java @@ -109,6 +109,7 @@ public abstract class StringUtils { * (or {@link ObjectUtils#isEmpty(Object)}) */ @Deprecated(since = "5.3") + @Contract("null -> true") public static boolean isEmpty(@Nullable Object str) { return (str == null || "".equals(str)); } @@ -210,6 +211,7 @@ public abstract class StringUtils { * contains at least 1 whitespace character * @see Character#isWhitespace */ + @Contract("null -> false") public static boolean containsWhitespace(@Nullable CharSequence str) { if (!hasLength(str)) { return false; @@ -231,6 +233,7 @@ public abstract class StringUtils { * contains at least 1 whitespace character * @see #containsWhitespace(CharSequence) */ + @Contract("null -> false") public static boolean containsWhitespace(@Nullable String str) { return containsWhitespace((CharSequence) str); } @@ -366,6 +369,7 @@ public abstract class StringUtils { * @param singleCharacter the character to compare to * @since 5.2.9 */ + @Contract("null, _ -> false") public static boolean matchesCharacter(@Nullable String str, char singleCharacter) { return (str != null && str.length() == 1 && str.charAt(0) == singleCharacter); } @@ -377,6 +381,7 @@ public abstract class StringUtils { * @param prefix the prefix to look for * @see java.lang.String#startsWith */ + @Contract("null, _ -> false; _, null -> false") public static boolean startsWithIgnoreCase(@Nullable String str, @Nullable String prefix) { return (str != null && prefix != null && str.length() >= prefix.length() && str.regionMatches(true, 0, prefix, 0, prefix.length())); @@ -389,6 +394,7 @@ public abstract class StringUtils { * @param suffix the suffix to look for * @see java.lang.String#endsWith */ + @Contract("null, _ -> false; _, null -> false") public static boolean endsWithIgnoreCase(@Nullable String str, @Nullable String suffix) { return (str != null && suffix != null && str.length() >= suffix.length() && str.regionMatches(true, str.length() - suffix.length(), suffix, 0, suffix.length())); @@ -528,6 +534,7 @@ public abstract class StringUtils { * @return the quoted {@code String} (for example, "'myString'"), * or the input object as-is if not a {@code String} */ + @Contract("null -> null; !null -> !null") public static @Nullable Object quoteIfString(@Nullable Object obj) { return (obj instanceof String str ? quote(str) : obj); } @@ -653,6 +660,7 @@ public abstract class StringUtils { * {@code null} if the provided path is {@code null} or does not contain a dot * ({@code "."}) */ + @Contract("null -> null") public static @Nullable String getFilenameExtension(@Nullable String path) { if (path == null) { return null; @@ -1034,6 +1042,7 @@ public abstract class StringUtils { * @param array2 the second array (can be {@code null}) * @return the new array ({@code null} if both given arrays were {@code null}) */ + @Contract("null, _ -> param2; _, null -> param1") public static String @Nullable [] concatenateStringArrays(String @Nullable [] array1, String @Nullable [] array2) { if (ObjectUtils.isEmpty(array1)) { return array2; @@ -1105,6 +1114,7 @@ public abstract class StringUtils { * index 1 being after the delimiter (neither element includes the delimiter); * or {@code null} if the delimiter wasn't found in the given input {@code String} */ + @Contract("null, _ -> null; _, null -> null") public static String @Nullable [] split(@Nullable String toSplit, @Nullable String delimiter) { if (!hasLength(toSplit) || !hasLength(delimiter)) { return null; @@ -1147,8 +1157,9 @@ public abstract class StringUtils { * @return a {@code Properties} instance representing the array contents, * or {@code null} if the array to process was {@code null} or empty */ + @Contract("null, _, _ -> null") public static @Nullable Properties splitArrayElementsIntoProperties( - String[] array, String delimiter, @Nullable String charsToDelete) { + String @Nullable [] array, String delimiter, @Nullable String charsToDelete) { if (ObjectUtils.isEmpty(array)) { return null; diff --git a/spring-core/src/main/java/org/springframework/util/TypeUtils.java b/spring-core/src/main/java/org/springframework/util/TypeUtils.java index e45d1e011df..c14007b5388 100644 --- a/spring-core/src/main/java/org/springframework/util/TypeUtils.java +++ b/spring-core/src/main/java/org/springframework/util/TypeUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,8 @@ import java.lang.reflect.WildcardType; import org.jspecify.annotations.Nullable; +import org.springframework.lang.Contract; + /** * Utility to work with generic type parameters. * @@ -210,6 +212,7 @@ public abstract class TypeUtils { return (upperBounds.length == 0 ? IMPLICIT_UPPER_BOUNDS : upperBounds); } + @Contract("_, null -> true; null, _ -> false") public static boolean isAssignableBound(@Nullable Type lhsType, @Nullable Type rhsType) { if (rhsType == null) { return true; diff --git a/spring-core/src/main/java/org/springframework/util/function/SupplierUtils.java b/spring-core/src/main/java/org/springframework/util/function/SupplierUtils.java index bc8dc059054..d593f8b4087 100644 --- a/spring-core/src/main/java/org/springframework/util/function/SupplierUtils.java +++ b/spring-core/src/main/java/org/springframework/util/function/SupplierUtils.java @@ -49,6 +49,7 @@ public abstract class SupplierUtils { * @return a supplier's result or the given Object as-is * @since 6.1.4 */ + @Contract("null -> null") public static @Nullable Object resolve(@Nullable Object candidate) { return (candidate instanceof Supplier supplier ? supplier.get() : candidate); } diff --git a/spring-core/src/test/java/org/springframework/util/CollectionUtilsTests.java b/spring-core/src/test/java/org/springframework/util/CollectionUtilsTests.java index bfbdeadaa1e..f03ac62946a 100644 --- a/spring-core/src/test/java/org/springframework/util/CollectionUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/util/CollectionUtilsTests.java @@ -180,27 +180,29 @@ class CollectionUtilsTests { @Test void findValueOfType() { - List integerList = new ArrayList<>(); - integerList.add(1); - assertThat(CollectionUtils.findValueOfType(integerList, Integer.class)).isEqualTo(1); + assertThat(CollectionUtils.findValueOfType(List.of(1), Integer.class)).isEqualTo(1); + + assertThat(CollectionUtils.findValueOfType(Set.of(2), Integer.class)).isEqualTo(2); + } + + @Test + void findValueOfTypeWithNullType() { + assertThat(CollectionUtils.findValueOfType(List.of(1), (Class) null)).isEqualTo(1); + } - Set integerSet = new HashSet<>(); - integerSet.add(2); - assertThat(CollectionUtils.findValueOfType(integerSet, Integer.class)).isEqualTo(2); + @Test + void findValueOfTypeWithNullCollection() { + assertThat(CollectionUtils.findValueOfType(null, Integer.class)).isNull(); } @Test void findValueOfTypeWithEmptyCollection() { - List emptyList = new ArrayList<>(); - assertThat(CollectionUtils.findValueOfType(emptyList, Integer.class)).isNull(); + assertThat(CollectionUtils.findValueOfType(List.of(), Integer.class)).isNull(); } @Test void findValueOfTypeWithMoreThanOneValue() { - List integerList = new ArrayList<>(); - integerList.add(1); - integerList.add(2); - assertThat(CollectionUtils.findValueOfType(integerList, Integer.class)).isNull(); + assertThat(CollectionUtils.findValueOfType(List.of(1, 2), Integer.class)).isNull(); } @Test