Mastering Spring Boot 3 Parameter Conversion: 5 Practical Solutions

This article walks through five comprehensive Spring Boot 3 techniques for converting request parameters—including a custom Converter, PropertyEditor, custom @ConvertUser annotation, argument resolver, and JSON deserializer—showing step‑by‑step implementations, registration details, and runtime screenshots for each approach.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Mastering Spring Boot 3 Parameter Conversion: 5 Practical Solutions

1. Introduction

In Spring Boot development, converting incoming request parameters (strings, JSON, path variables, form data) to strongly‑typed Java objects is a frequent requirement. Mismatches in format or type can cause errors, so Spring Boot offers multiple mechanisms to handle these conversions.

This article presents five universal solutions that cover most everyday scenarios, ranging from simple annotations to advanced argument resolvers.

2. Practical Cases

2.1 Custom Converter<S,T>

Implement the Converter<String, User> interface and register it as a Spring bean. The converter parses a comma‑separated string into a User object.

@Component
public class StringToUserConvert implements Converter<String, User> {
    @Override
    public User convert(String source) {
        if (!StringUtils.hasLength(source)) {
            return null;
        }
        String[] data = source.split(",");
        if (data.length != 3) {
            return null;
        }
        return new User(Long.valueOf(data[0]), data[1], Integer.valueOf(data[2]));
    }
}

When registered globally, this converter works for any controller method that receives a User parameter.

Run result:

If you need the converter only for a specific controller, register it inside @InitBinder:

@RestController
@RequestMapping("/api")
public class ApiController {
    @InitBinder
    public void init(WebDataBinder binder) {
        ConversionService cs = binder.getConversionService();
        if (cs instanceof ConfigurableConversionService gcs) {
            gcs.addConverter(new StringToUserConvert());
        }
    }
    // ... other endpoints
}

2.2 Custom PropertyEditor

Extend PropertyEditorSupport to convert a string into a User object, then register it with WebDataBinder:

@InitBinder
public void init(WebDataBinder binder) {
    binder.registerCustomEditor(User.class, new PropertyEditorSupport() {
        @Override
        public void setAsText(String source) throws IllegalArgumentException {
            if (!StringUtils.hasLength(source)) {
                setValue(null);
                return;
            }
            String[] data = source.split(",");
            if (data.length != 3) {
                setValue(null);
                return;
            }
            setValue(new User(Long.valueOf(data[0]), data[1], Integer.valueOf(data[2])));
        }
    });
}

Run result:

Test endpoint:

@GetMapping("/dto")
public DTO convert2(DTO dto) {
    return dto;
}

public class DTO {
    private User user;
}

Run result:

2.3 Custom Annotation @ConvertUser

Spring provides built‑in annotations like @NumberFormat and @DateTimeFormat that rely on AnnotationFormatterFactory. You can define a similar custom annotation to trigger conversion.

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
public @interface ConvertUser {}

Formatter factory implementation:

public final class ConvertUserAnnotationFormatterFactory implements AnnotationFormatterFactory<ConvertUser> {
    private static final Set<Class<?>> FIELD_TYPES = Set.of(User.class);
    @Override
    public Set<Class<?>> getFieldTypes() { return FIELD_TYPES; }
    @Override
    public Printer<User> getPrinter(ConvertUser annotation, Class<?> fieldType) {
        return user -> "【id = %s, name = %s, age = %s】".formatted(user.getId(), user.getName(), user.getAge());
    }
    @Override
    public Parser<User> getParser(ConvertUser annotation, Class<?> fieldType) {
        return source -> {
            if (!StringUtils.hasLength(source)) return null;
            String[] data = source.split(",");
            if (data.length != 3) return null;
            return new User(Long.valueOf(data[0]), data[1], Integer.valueOf(data[2]));
        };
    }
}

Register the formatter:

@Component
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addFormatterForFieldAnnotation(new ConvertUserAnnotationFormatterFactory());
    }
}

Test endpoint using the annotation:

@GetMapping("/anno")
public User convert2(@ConvertUser User user) {
    return user;
}

Run result:

2.4 Custom Argument Resolver

For the most flexible handling, implement HandlerMethodArgumentResolver that works with the @ConvertUser annotation.

public class ConvertUserArgumentResolver implements HandlerMethodArgumentResolver {
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(ConvertUser.class);
    }
    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        String name = parameter.getParameterName();
        String value = webRequest.getParameter(name);
        if (!StringUtils.hasLength(value)) return null;
        String[] data = value.split(",");
        if (data.length != 3) return null;
        return new User(Long.valueOf(data[0]), data[1], Integer.valueOf(data[2]));
    }
}

Register the resolver:

@Component
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new ConvertUserArgumentResolver());
    }
}

Run result (screenshot):

2.5 JSON Property Conversion

When the request body is JSON, define a custom deserializer and attach it to the @ConvertUser annotation.

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@JacksonAnnotationsInside
@JsonDeserialize(using = UserConverterDeserializer.class)
public @interface ConvertUser {}
public class UserConverterDeserializer extends JsonDeserializer<User> {
    @Override
    public User deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        String source = p.getText();
        if (!StringUtils.hasLength(source)) return null;
        String[] data = source.split(",");
        if (data.length != 3) return null;
        return new User(Long.valueOf(data[0]), data[1], Integer.valueOf(data[2]));
    }
}

DTO using the annotation:

public class DTO {
    @ConvertUser
    private User user;
}

@PostMapping("/body")
public DTO convert3(@RequestBody DTO dto) {
    return dto;
}

Run result:

3. Conclusion

The article demonstrates five interchangeable strategies for handling parameter conversion in Spring Boot 3.5.0, allowing developers to choose the most suitable approach—global bean converters, PropertyEditor, custom annotations with formatter factories, argument resolvers, or JSON deserializers—based on project complexity and performance considerations.

Spring BootAnnotationCustom ConverterParameter ConversionArgument ResolverJSON Deserializer
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

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.