Backend Development 9 min read

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.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Boot 3 Essentials: @Delimiter, Custom Converters, YAML & Filters

@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.

JavaBackend DevelopmentSpring BootyamlFiltersCustom ConverterDeferredImportSelector
Spring Full-Stack Practical Cases
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.