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:
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;
}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.
# Configuration
pack:
payway: weixin 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);
}
} @Value("${pack.payway}")
private PaywayEnum payway;If no converter is registered, the application fails with a conversion error. The following custom converter solves the problem:
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;
}
}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.
@Configuration
@PropertySource("config.properties")
public class AppConfig {}
// pack.yml
pack:
title: xxxooo
author: pack @Bean
public YamlPropertiesFactoryBean packYaml(@Value("classpath:pack.yml") Resource resource) {
YamlPropertiesFactoryBean bean = new YamlPropertiesFactoryBean();
bean.setResources(resource);
return bean;
} @Resource
private Properties packYaml;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.
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"};
}
}Example configuration classes:
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(); }
}When Config2 imports PackDeferredImportSelector, Spring logs show that Config1 processing is deferred until other configurations finish.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
