Skip to content

Commit d4925d6

Browse files
GH-4016: Fix @RetryableTopic includeNames/excludeNames for SpEL
Fixes: #4016 Previously, the `includeNames` and `excludeNames` attributes of `@RetryableTopic` were not processed through the configured `BeanExpressionResolver` and `BeanExpressionContext`. This change ensures proper resolution from property placeholders `(${...})` and SpEL expressions `(#{...})`, consistent with the documentation and behavior of similar annotations like `@KafkaListener`. Signed-off-by: Dimitri Tugo <[email protected]> [[email protected]: Improve comint message] The Javadoc of the `@RetryableTopic` includes the statement: > All String properties can be resolved from property placeholders `${...}` or SpEL expressions `#{...}`. While `includeNames` and `excludeNames` are technically `String[]` rather than `String`, it’s a reasonable and common expectation that this applies to all string-valued attributes — including arrays. This interpretation is also consistent with how similar annotations behave, such as `@KafkaListener`, where array properties like topics support both property placeholders and SpEL expressions on a per-element basis. Enabling resolution here improves consistency with `@KafkaListener`, reduces potential surprises, and allows for cleaner externalization of retryable or non-retryable exception lists. **Auto-cherry-pick to `3.3.x`** Signed-off-by: Artem Bilan <[email protected]>
1 parent 08985a3 commit d4925d6

File tree

1 file changed

+29
-2
lines changed

1 file changed

+29
-2
lines changed

spring-kafka/src/main/java/org/springframework/kafka/annotation/RetryableTopicAnnotationProcessor.java

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.lang.reflect.Method;
2020
import java.util.ArrayList;
2121
import java.util.Arrays;
22+
import java.util.Collection;
2223
import java.util.Collections;
2324
import java.util.List;
2425
import java.util.Map;
@@ -132,10 +133,15 @@ public RetryTopicConfiguration processAnnotation(String[] topics, Class<?> clazz
132133
if (resolvedTimeout != null) {
133134
timeout = resolvedTimeout;
134135
}
135-
List<Class<? extends Throwable>> includes = resolveClasses(annotation.include(), annotation.includeNames(),
136+
137+
String[] resolvedIncludeNames = resolveToStringArray(annotation.includeNames());
138+
List<Class<? extends Throwable>> includes = resolveClasses(annotation.include(), resolvedIncludeNames,
136139
"include");
137-
List<Class<? extends Throwable>> excludes = resolveClasses(annotation.exclude(), annotation.excludeNames(),
140+
141+
String[] resolvedExcludeNames = resolveToStringArray(annotation.excludeNames());
142+
List<Class<? extends Throwable>> excludes = resolveClasses(annotation.exclude(), resolvedExcludeNames,
138143
"exclude");
144+
139145
boolean traverse = false;
140146
if (StringUtils.hasText(annotation.traversingCauses())) {
141147
Boolean traverseResolved = resolveExpressionAsBoolean(annotation.traversingCauses(), "traversingCauses");
@@ -423,4 +429,25 @@ private String resolve(String value) {
423429
return value;
424430
}
425431

432+
private String[] resolveToStringArray(String[] values) {
433+
List<String> result = new ArrayList<>();
434+
for (String value : values) {
435+
Object resolved = resolveExpression(value);
436+
if (resolved instanceof String[] strings) {
437+
Collections.addAll(result, strings);
438+
}
439+
else if (resolved instanceof Collection<?> coll) {
440+
for (Object item : coll) {
441+
result.add(item.toString());
442+
}
443+
}
444+
else if (resolved instanceof String str) {
445+
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(str)));
446+
}
447+
else if (resolved != null) {
448+
result.add(resolved.toString());
449+
}
450+
}
451+
return result.toArray(new String[0]);
452+
}
426453
}

0 commit comments

Comments
 (0)