Skip to content

Commit 012fb29

Browse files
committed
Fix nullness mismatch for Converter/ConverterFactory
In gh-35947, the `Converter` contract was refined to allow for nullable return values. This created a mismatch with the `ConverterFactory` contract. This commit fixes this mismatch by allowing nullable return values in `Converter` instances created by `ConverterFactory`. Fixes gh-36063
1 parent 92a43c0 commit 012fb29

File tree

7 files changed

+67
-6
lines changed

7 files changed

+67
-6
lines changed

spring-core/src/main/java/org/springframework/core/convert/converter/ConverterFactory.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.core.convert.converter;
1818

19+
import org.jspecify.annotations.Nullable;
20+
1921
/**
2022
* A factory for "ranged" converters that can convert objects from S to subtypes of R.
2123
*
@@ -36,6 +38,6 @@ public interface ConverterFactory<S, R> {
3638
* @param targetType the target type to convert to
3739
* @return a converter from S to T
3840
*/
39-
<T extends R> Converter<S, T> getConverter(Class<T> targetType);
41+
<T extends R> Converter<S, @Nullable T> getConverter(Class<T> targetType);
4042

4143
}

spring-core/src/main/java/org/springframework/core/convert/support/CharacterToNumberFactory.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.core.convert.support;
1818

19+
import org.jspecify.annotations.Nullable;
20+
1921
import org.springframework.core.convert.converter.Converter;
2022
import org.springframework.core.convert.converter.ConverterFactory;
2123
import org.springframework.util.NumberUtils;
@@ -41,7 +43,7 @@
4143
final class CharacterToNumberFactory implements ConverterFactory<Character, Number> {
4244

4345
@Override
44-
public <T extends Number> Converter<Character, T> getConverter(Class<T> targetType) {
46+
public <T extends Number> Converter<Character, @Nullable T> getConverter(Class<T> targetType) {
4547
return new CharacterToNumber<>(targetType);
4648
}
4749

spring-core/src/main/java/org/springframework/core/convert/support/IntegerToEnumConverterFactory.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.core.convert.support;
1818

19+
import org.jspecify.annotations.Nullable;
20+
1921
import org.springframework.core.convert.converter.Converter;
2022
import org.springframework.core.convert.converter.ConverterFactory;
2123

@@ -30,7 +32,7 @@
3032
final class IntegerToEnumConverterFactory implements ConverterFactory<Integer, Enum> {
3133

3234
@Override
33-
public <T extends Enum> Converter<Integer, T> getConverter(Class<T> targetType) {
35+
public <T extends Enum> Converter<Integer, @Nullable T> getConverter(Class<T> targetType) {
3436
return new IntegerToEnum(ConversionUtils.getEnumType(targetType));
3537
}
3638

spring-core/src/main/java/org/springframework/core/convert/support/NumberToNumberConverterFactory.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.core.convert.support;
1818

19+
import org.jspecify.annotations.Nullable;
20+
1921
import org.springframework.core.convert.TypeDescriptor;
2022
import org.springframework.core.convert.converter.ConditionalConverter;
2123
import org.springframework.core.convert.converter.Converter;
@@ -43,7 +45,7 @@
4345
final class NumberToNumberConverterFactory implements ConverterFactory<Number, Number>, ConditionalConverter {
4446

4547
@Override
46-
public <T extends Number> Converter<Number, T> getConverter(Class<T> targetType) {
48+
public <T extends Number> Converter<Number, @Nullable T> getConverter(Class<T> targetType) {
4749
return new NumberToNumber<>(targetType);
4850
}
4951

spring-core/src/main/java/org/springframework/core/convert/support/StringToEnumConverterFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
final class StringToEnumConverterFactory implements ConverterFactory<String, Enum> {
3333

3434
@Override
35-
public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
35+
public <T extends Enum> Converter<String, @Nullable T> getConverter(Class<T> targetType) {
3636
return new StringToEnum(ConversionUtils.getEnumType(targetType));
3737
}
3838

spring-core/src/main/java/org/springframework/core/convert/support/StringToNumberConverterFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
final class StringToNumberConverterFactory implements ConverterFactory<String, Number> {
4444

4545
@Override
46-
public <T extends Number> Converter<String, T> getConverter(Class<T> targetType) {
46+
public <T extends Number> Converter<String, @Nullable T> getConverter(Class<T> targetType) {
4747
return new StringToNumber<>(targetType);
4848
}
4949

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright 2025-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.core.convert.converter
18+
19+
import org.assertj.core.api.Assertions.assertThat
20+
import org.junit.jupiter.api.Test
21+
import kotlin.reflect.full.primaryConstructor
22+
23+
/**
24+
* @author Brian Clozel
25+
*/
26+
class ConverterFactoryNullnessTests {
27+
28+
@Test
29+
fun converterFactoryWithNullableTypes() {
30+
val factory = StringToIdConverterFactory
31+
32+
val userIdConverter = factory.getConverter(UserId::class.java)
33+
assertThat(userIdConverter.convert("42")).isEqualTo(UserId("42"))
34+
}
35+
36+
object StringToIdConverterFactory : ConverterFactory<String, Id> {
37+
override fun <T : Id> getConverter(targetType: Class<T>): Converter<String, T?> {
38+
val constructor = checkNotNull(targetType.kotlin.primaryConstructor)
39+
return Converter { source ->
40+
constructor.call(source)
41+
}
42+
}
43+
}
44+
45+
abstract class Id {
46+
abstract val value: String
47+
}
48+
49+
data class UserId(override val value: String) : Id()
50+
51+
data class ProductId(override val value: String) : Id()
52+
53+
}

0 commit comments

Comments
 (0)