Make a Spring MVC Endpoint Accept JSON, Form and Multipart in One Call
This article explains how to redesign a Spring MVC controller so that a single endpoint can seamlessly handle JSON payloads, URL‑encoded form data, and multipart file uploads by creating a custom annotation, resolver, and Spring configuration, avoiding the pitfalls of manual HttpServletRequest parsing.
When refactoring a legacy PHP API, a Java Spring MVC endpoint needed to accept three content‑types simultaneously: application/json, application/x-www-form-urlencoded, and multipart/form-data. Using HttpServletRequest to parse the body manually results in verbose code, a fixed Map return type, and loss of @Valid validation.
Naïve Implementation
The manual parsing approach checks the request’s Content‑Type and processes each format separately:
private Map<String, Object> getParams(HttpServletRequest request) {
String contentType = request.getContentType();
if (contentType.contains("application/json")) {
// json parsing ...
return null;
} else if (contentType.contains("application/x-www-form-urlencoded")) {
// form parsing ...
return null;
} else if (contentType.contains("multipart")) {
// file stream parsing ...
return null;
} else {
throw new BizException("Unsupported content-type");
}
}Elegant Solution – Custom Annotation and Argument Resolver
A reusable solution uses a custom parameter annotation @GamePHP together with a HandlerMethodArgumentResolver that delegates to existing JSON and form resolvers.
1. Define the Annotation
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GamePHP {}2. Implement the Resolver
public class GamePHPMethodProcessor implements HandlerMethodArgumentResolver {
private GameFormMethodArgumentResolver formResolver;
private GameJsonMethodArgumentResolver jsonResolver;
public GamePHPMethodProcessor() {
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
PHPMessageConverter phpConverter = new PHPMessageConverter();
messageConverters.add(phpConverter);
jsonResolver = new GameJsonMethodArgumentResolver(messageConverters);
formResolver = new GameFormMethodArgumentResolver();
}
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterAnnotation(GamePHP.class) != null;
}
@Override
public Object resolveArgument(MethodParameter methodParameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
ServletRequest servletRequest = webRequest.getNativeRequest(ServletRequest.class);
String contentType = servletRequest.getContentType();
if (contentType == null) {
throw new IllegalArgumentException("Unsupported contentType");
}
if (contentType.contains("application/json")) {
return jsonResolver.resolveArgument(methodParameter, mavContainer, webRequest, binderFactory);
}
if (contentType.contains("application/x-www-form-urlencoded") || contentType.contains("multipart")) {
return formResolver.resolveArgument(methodParameter, mavContainer, webRequest, binderFactory);
}
throw new IllegalArgumentException("Unsupported contentType");
}
}3. Register the Resolver
@Bean
public MyMvcConfigurer mvcConfigurer() {
return new MyMvcConfigurer();
}
public static class MyMvcConfigurer implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new GamePHPMethodProcessor());
}
}4. Enable Multipart Support
Add the required Maven dependencies:
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>Define a MultipartResolver bean to handle file uploads lazily:
@Bean(name = "multipartResolver")
public MultipartResolver multipartResolver() {
CommonsMultipartResolver resolver = new CommonsMultipartResolver();
resolver.setDefaultEncoding("UTF-8");
resolver.setResolveLazily(true);
resolver.setMaxInMemorySize(40960);
resolver.setMaxUploadSize(50 * 1024 * 1024); // 50 MB
return resolver;
}5. Using the Annotation
Replace the standard @RequestParam (or @RequestBody) on a controller method parameter with @GamePHP. Spring will invoke the custom resolver, automatically selecting the appropriate parsing strategy based on the incoming Content‑Type. Validation annotations such as @Valid continue to work because the resolved argument is processed by Spring’s normal data binding pipeline.
Example controller method:
@PostMapping("/api/submit")
public ResponseEntity<Result> submit(@GamePHP @Valid MyRequestDto request) {
// business logic using populated MyRequestDto
return ResponseEntity.ok(new Result("success"));
}This approach keeps controller signatures clean, supports JSON, URL‑encoded form, and multipart requests in a single endpoint, and retains Spring’s validation and binding capabilities without manual request parsing.
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.
Java Architect Essentials
Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.
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.
