Master Custom Data Type Conversion in Spring Boot with Formatter SPI
This guide explains how to use Spring Boot's Formatter SPI to create custom type converters, implement both simple and annotation‑based formatters, register them in a WebMvcConfigurer, and test the conversions through REST endpoints, complete with code examples.
Environment
Spring Boot 2.6.12
Conversion Service and Formatter SPI
Spring provides two SPI mechanisms for type conversion: the Converter SPI for generic conversions (e.g., java.util.Date ↔ Long ) and the Formatter SPI for locale‑aware formatting in web applications. ConversionService offers a unified API for both.
Creating a Custom Formatter
Implement the Formatter<T> interface (which extends Printer<T> and Parser<T> ) for the target type, for example Users :
<code>public class Users {
private String name;
private Integer age;
}</code> <code>public class UsersFormatter implements Formatter<Users> {
@Override
public String print(Users object, Locale locale) {
if (object == null) {
return "";
}
return "【name = " + object.getName() + ", age = " + object.getAge() + "】";
}
@Override
public Users parse(String text, Locale locale) throws ParseException {
if (text == null || text.trim().isEmpty()) {
return null;
}
Users user = new Users();
String[] values = text.split(",");
user.setName(values[0]);
user.setAge(Integer.parseInt(values[1]));
return user;
}
}</code>Register the formatter in a @Configuration class that implements WebMvcConfigurer :
<code>@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatter(new UsersFormatter());
}
}</code>Test endpoint:
<code>@GetMapping("/save")
public Object save(Users users) {
return users;
}</code>Result (example output):
Annotation‑Based Formatter
Define a custom annotation and an AnnotationFormatterFactory to bind the annotation to a formatter.
<code>@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
public @interface AgeFormat {}
</code> <code>public final class AgeFormatAnnotationFormatterFactory implements AnnotationFormatterFactory<AgeFormat> {
@Override
public Set<Class<?>> getFieldTypes() {
Set<Class<?>> types = new HashSet<>();
types.add(Integer.class);
return types;
}
@Override
public Printer<Integer> getPrinter(AgeFormat annotation, Class<?> fieldType) {
return new AgeFormatter();
}
@Override
public Parser<Integer> getParser(AgeFormat annotation, Class<?> fieldType) {
return new AgeFormatter();
}
private static class AgeFormatter implements Formatter<Integer> {
@Override
public String print(Integer object, Locale locale) {
return object == null ? "" : object.toString();
}
@Override
public Integer parse(String text, Locale locale) throws ParseException {
if (text == null || text.trim().isEmpty()) {
return -1;
}
return Integer.parseInt(text.substring(1)); // skip leading 's'
}
}
}
</code>Register the annotation formatter:
<code>@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatterForFieldAnnotation(new AgeFormatAnnotationFormatterFactory());
}
}
</code>Apply the annotation to the age field:
<code>public class Users {
private String name;
@AgeFormat
private Integer age;
}
</code>Test endpoint:
<code>@GetMapping("/save2")
public Object save2(Users users) {
return users;
}
</code>Result (example output):
Formatter on Method Parameter
Another custom annotation can be used on a controller method parameter:
<code>public final class UsersFormatAnnotationFormatterFactory implements AnnotationFormatterFactory<UsersFormat> {
@Override
public Set<Class<?>> getFieldTypes() {
Set<Class<?>> types = new HashSet<>();
types.add(Users.class);
return types;
}
@Override
public Printer<?> getPrinter(UsersFormat annotation, Class<?> fieldType) {
return new UsersFormatter();
}
@Override
public Parser<?> getParser(UsersFormat annotation, Class<?> fieldType) {
return new UsersFormatter();
}
}
</code> <code>@GetMapping("/save3")
public Object save3(@UsersFormat Users users) {
return users;
}
</code>Result (example output):
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.