Streamlining Spring Controllers with Unified Responses and Validation

This article explains how to simplify Spring MVC controller code by introducing a unified response format, leveraging ResponseBodyAdvice for automatic wrapping, applying JSR‑303 validation for request parameters, creating custom validators, and handling exceptions globally to keep business logic clean and maintainable.

Architecture Digest
Architecture Digest
Architecture Digest
Streamlining Spring Controllers with Unified Responses and Validation

What Makes a Good Controller Layer

In a typical three‑tier or COLA architecture, the Controller is essential for exposing data interfaces, handling request parsing, delegating to services, catching exceptions, and returning responses.

Current Problems

Parameter validation is tightly coupled with business logic, violating the single‑responsibility principle.

Repeatedly throwing the same exception across services leads to duplicated code.

Inconsistent success and error response formats make client integration difficult.

Refactoring the Controller Layer

Unified Response Structure

Define a common result interface and an enum of standard codes, then implement a generic Result<T> class with static factory methods for success and failure.

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

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

public class Result<T> {
    private Integer code;
    private String message;
    private T data;
    public static <T> Result<T> success(T data) {
        return new Result<>(ResultEnum.SUCCESS.getCode(), ResultEnum.SUCCESS.getMessage(), data);
    }
    public static Result<?> failed() {
        return new Result<>(ResultEnum.COMMON_FAILED.getCode(), ResultEnum.COMMON_FAILED.getMessage(), null);
    }
    // other factory methods omitted
}

Automatic Wrapping with ResponseBodyAdvice

Implement ResponseBodyAdvice to intercept controller return values and wrap them in Result.success(...) unless they are already wrapped.

@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;
        }
        return Result.success(body);
    }
}

Parameter Validation

Use JSR‑303 (Hibernate Validator) annotations such as @NotBlank, @Min, @Max, @Email on DTO fields and enable automatic validation with @Validated on controller methods.

@Data
public class TestDTO {
    @NotBlank
    private String userName;
    @NotBlank @Length(min = 6, max = 20)
    private String password;
    @NotNull @Email
    private String email;
}

@PostMapping("/test-validation")
public void testValidation(@RequestBody @Validated TestDTO dto) {
    testService.save(dto);
}

Custom Validation Rules

Create a custom annotation and validator, for example a @Mobile annotation that checks Chinese mobile phone formats.

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = MobileValidator.class)
public @interface Mobile {
    boolean required() default true;
    String message() default "不是一个手机号码格式";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

public class MobileValidator implements ConstraintValidator<Mobile, CharSequence> {
    private boolean required;
    private final Pattern pattern = Pattern.compile("^1[34578][0-9]{9}$");
    @Override
    public void initialize(Mobile annotation) { this.required = annotation.required(); }
    @Override
    public boolean isValid(CharSequence value, ConstraintValidatorContext ctx) {
        if (!required && !StringUtils.hasText(value)) return true;
        return pattern.matcher(value).matches();
    }
}

Custom Exceptions and Global Exception Handling

Define business‑specific runtime exceptions and handle them uniformly with @RestControllerAdvice, mapping each exception to a Result.failed(...) response.

public class BusinessException extends RuntimeException {
    public BusinessException(String msg) { super(msg); }
}

@RestControllerAdvice(basePackages = "com.example.demo")
public class ExceptionAdvice {
    @ExceptionHandler(BusinessException.class)
    public Result<?> handleBusinessException(BusinessException ex) {
        return Result.failed(ex.getMessage());
    }
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<?> handleValidationException(MethodArgumentNotValidException ex) {
        StringBuilder sb = new StringBuilder("校验失败:");
        ex.getBindingResult().getFieldErrors().forEach(e ->
            sb.append(e.getField()).append(":").append(e.getDefaultMessage()).append(", "));
        return Result.failed(sb.toString());
    }
    @ExceptionHandler(Exception.class)
    public Result<?> handle(Exception ex) {
        return Result.failed(ex.getMessage());
    }
}

Conclusion

After applying unified response wrapping, JSR‑303 validation, custom validators, and global exception handling, controller code becomes concise, each endpoint clearly declares its input contracts, and all success and error responses share the same structure, allowing developers to focus on business logic.

Javaexception-handlingSpringValidationController
Architecture Digest
Written by

Architecture Digest

Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.

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.