Refactor Spring Controllers for Unified Responses and Robust Validation

This article explains how to redesign Spring MVC controllers by introducing a unified Result wrapper, using ResponseBodyAdvice for automatic response packaging, applying JSR‑303 validation on DTOs and method parameters, and handling custom exceptions globally to produce consistent API outputs.

Java Backend Technology
Java Backend Technology
Java Backend Technology
Refactor Spring Controllers for Unified Responses and Robust Validation

From the Current Situation

Controller is an indispensable companion in both three‑layer and COLA architectures, responsible for receiving requests, invoking services, handling exceptions and returning responses.

Problems with Traditional Controller Code

Parameter validation couples business logic, violating Single Responsibility Principle.

Repeated exception throwing across services leads to duplicated code.

Inconsistent success and error response formats make client integration unfriendly.

Refactoring the Controller Layer

Unified Return Structure

A generic IResult interface and Result wrapper provide a status code, message and data payload, making success detection explicit.

public interface IResult {
    Integer getCode();
    String getMessage();
}

public enum ResultEnum implements IResult {
    SUCCESS(2001, "接口调用成功"),
    VALIDATE_FAILED(2002, "参数校验失败"),
    COMMON_FAILED(2003, "接口调用失败"),
    FORBIDDEN(2004, "没有权限访问资源");
    // fields and constructor omitted
}

public class Result<T> {
    private Integer code;
    private String message;
    private T data;
    // static factory methods omitted
}

Unified Response Advice

Implement ResponseBodyAdvice to wrap every controller return value into Result. For String returns, convert the wrapper to JSON manually.

@RestControllerAdvice(basePackages = "com.example.demo")
public class ResponseAdvice implements ResponseBodyAdvice<Object> {
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType,
                                  MediaType selectedContentType,
                                  Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                  ServerHttpRequest request, ServerHttpResponse response) {
        if (body instanceof Result) {
            return body;
        }
        if (body instanceof String) {
            try {
                return objectMapper.writeValueAsString(Result.success(body));
            } catch (JsonProcessingException e) {
                throw new RuntimeException(e);
            }
        }
        return Result.success(body);
    }
}

Adjust the order of MappingJackson2HttpMessageConverter in the MVC configuration to avoid String conversion errors.

@Configuration
public class WebConfiguration implements WebMvcConfigurer {
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(0, new MappingJackson2HttpMessageConverter());
    }
}

Parameter Validation

Use JSR‑303 annotations ( @Valid, @Validated, @Min, @Email, etc.) on DTOs, @PathVariable and @RequestParam parameters. Spring’s MethodValidationPostProcessor and MethodValidationInterceptor perform AOP‑based validation and throw MethodArgumentNotValidException or ConstraintViolationException on failure.

Custom Exceptions and Global Exception Handling

Define domain‑specific exceptions such as BusinessException and ForbiddenException, then handle them in a @RestControllerAdvice that returns a Result with appropriate error codes.

@RestControllerAdvice(basePackages = "com.example.demo")
public class ExceptionAdvice {
    @ExceptionHandler(BusinessException.class)
    public Result<?> handleBusinessException(BusinessException ex) {
        return Result.failed(ex.getMessage());
    }
    @ExceptionHandler(ForbiddenException.class)
    public Result<?> handleForbiddenException(ForbiddenException ex) {
        return Result.failed(ResultEnum.FORBIDDEN);
    }
    // other handlers omitted for brevity
}

After these changes, controller code becomes concise, validation rules are explicit, and all responses share a uniform structure, allowing developers to focus on business logic.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaspringControllerresponsebodyadvice
Java Backend Technology
Written by

Java Backend Technology

Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!

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.