Spring Boot 3 Essentials: @Delimiter, Custom Converters, YAML & Filters
This guide walks you through powerful Spring Boot 3 features—including the @Delimiter annotation for automatic collection parsing, custom type converters for @Value, loading YAML files via a FactoryBean, a suite of ordered filters, and using DeferredImportSelector for conditional configuration imports.
@Delimiter Annotation
The @Delimiter annotation can automatically split incoming string data (e.g., comma "," or pipe "|") into a Collection or array. Example:
<code>public class User {
private String name;
private Integer age;
@Delimiter(",")
private List<String> addresses = new ArrayList<>();
// getters, setters
}
@GetMapping("/user")
public Object users(User user) {
System.out.println(user);
return user;
}</code>When the endpoint is called, the address string is split by commas and populated into the list.
Custom @Value Type Conversion
Spring provides many default converters via DefaultConversionService#addDefaultConverters . For types that cannot be converted, you can define a custom converter. Example: converting a configuration string to an enum.
<code># Configuration
pack:
payway: weixin</code> <code>public enum PaywayEnum {
WEIXIN("weixin"), ALIPAY("alipay"), BANK("bank");
private final String code;
PaywayEnum(String code) { this.code = code; }
public String getCode() { return code; }
public static PaywayEnum getEnum(String code) {
return Arrays.stream(PaywayEnum.values())
.filter(e -> e.getCode().equals(code))
.findFirst()
.orElse(null);
}
}</code> <code>@Value("${pack.payway}")
private PaywayEnum payway;</code>If no converter is registered, the application fails with a conversion error. The following custom converter solves the problem:
<code>public class StringToPaywayEnumConverter implements ConverterFactory<String, Enum> {
@Override
public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
return new StringToEnum();
}
private class StringToEnum<T extends Enum> implements Converter<String, T> {
@Override
public T convert(String source) {
if (source.isEmpty()) return null;
return (T) PaywayEnum.getEnum(source);
}
}
public static Class<?> getEnumType(Class<?> targetType) {
Class<?> enumType = targetType;
while (enumType != null && !enumType.isEnum()) {
enumType = enumType.getSuperclass();
}
if (enumType == null) {
throw new IllegalArgumentException("Conversion error");
}
return enumType;
}
}</code>Register this converter in the Spring container to enable the conversion.
Loading YAML Files
Standard @PropertySource only supports .properties . To load a .yml file, define a YamlPropertiesFactoryBean and expose it as a bean.
<code>@Configuration
@PropertySource("config.properties")
public class AppConfig {}
// pack.yml
pack:
title: xxxooo
author: pack</code> <code>@Bean
public YamlPropertiesFactoryBean packYaml(@Value("classpath:pack.yml") Resource resource) {
YamlPropertiesFactoryBean bean = new YamlPropertiesFactoryBean();
bean.setResources(resource);
return bean;
}</code> <code>@Resource
private Properties packYaml;</code>The bean returns a Properties object that can be injected wherever needed.
Convenient Ordered Filters
OrderedCharacterEncodingFilter – Handles request/response character encoding.
OrderedFormContentFilter – Parses form data for PUT, PATCH, DELETE when Content-Type is application/x-www-form-urlencoded .
OrderedHiddenHttpMethodFilter – Simulates HTTP methods (PUT, DELETE, PATCH) via a hidden _method field in forms.
OrderedRequestContextFilter – Binds the current request’s locale to the thread context (via LocaleContextHolder ).
ApplicationContextHeaderFilter – Adds the application context ID to the response header X-Application-Context .
DeferredImportSelector
A DeferredImportSelector runs after all @Configuration beans have been processed, useful for conditional imports based on @Conditional . You can also set ordering by implementing Ordered or using @Order .
<code>public class PackDeferredImportSelector implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// Import Config1 when Config2 is processed
return new String[]{"com.pack.test.import_selector.Config1"};
}
}</code>Example configuration classes:
<code>public class C1 {
@PostConstruct
public void init() { System.err.println("C1 init..."); }
}
public class C2 {
@PostConstruct
public void init() { System.err.println("C2 init..."); }
}
@Configuration
public class Config1 {
@PostConstruct
public void init() { System.err.println("Config1 init..."); }
@Bean C1 c1() { return new C1(); }
}
@Configuration
public class Config2 {
@PostConstruct
public void init() { System.err.println("Config2 init..."); }
@Bean C2 c2() { return new C2(); }
}
</code>When Config2 imports PackDeferredImportSelector , Spring logs show that Config1 processing is deferred until other configurations finish.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.