diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java b/spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java index edff9b01637..a8e6e51cb1e 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.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. @@ -334,8 +334,7 @@ public class GenericConversionService implements ConfigurableConversionService { } // Full check for complex generic type match required? ResolvableType rt = targetType.getResolvableType(); - if (!(rt.getType() instanceof Class) && !rt.isAssignableFrom(this.targetType) && - !this.targetType.hasUnresolvableGenerics()) { + if (!(rt.getType() instanceof Class) && !rt.isAssignableFromResolvedPart(this.targetType)) { return false; } return !(this.converter instanceof ConditionalConverter conditionalConverter) || diff --git a/spring-core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java b/spring-core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java index c39a5a0d90d..98a3b396e20 100644 --- a/spring-core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java +++ b/spring-core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.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. @@ -565,6 +565,22 @@ class GenericConversionServiceTests { assertThat(conversionService.convert("test", TypeDescriptor.valueOf(String.class), new TypeDescriptor(getClass().getField("integerCollection")))).isEqualTo(Collections.singleton("testX")); } + @Test + void stringListToListOfSubclassOfUnboundGenericClass() { + conversionService.addConverter(new StringListToAListConverter()); + conversionService.addConverter(new StringListToBListConverter()); + + List aList = (List) conversionService.convert(List.of("foo"), + TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class)), + TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(ARaw.class))); + assertThat(aList).allMatch(e -> e instanceof ARaw); + + List bList = (List) conversionService.convert(List.of("foo"), + TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class)), + TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(BRaw.class))); + assertThat(bList).allMatch(e -> e instanceof BRaw); + } + @ExampleAnnotation(active = true) public String annotatedString; @@ -740,6 +756,7 @@ class GenericConversionServiceTests { } } + private interface MyEnumBaseInterface { String getBaseCode(); } @@ -921,4 +938,33 @@ class GenericConversionServiceTests { return Color.decode(source.substring(0, 6)); } } + + + private static class GenericBaseClass { + } + + private static class ARaw extends GenericBaseClass { + } + + private static class BRaw extends GenericBaseClass { + } + + + private static class StringListToAListConverter implements Converter, List> { + + @Override + public List convert(List source) { + return List.of(new ARaw()); + } + } + + + private static class StringListToBListConverter implements Converter, List> { + + @Override + public List convert(List source) { + return List.of(new BRaw()); + } + } + }