SHEN LI

Back

Introduction#

Over the past year I have worked on several Spring Boot backend projects and gradually built up a set of opinions about RESTful API design. This post collects the practices I consider most important.

1. A unified response envelope#

A consistent response shape lowers communication cost during both development and debugging. I usually go with:

{
  "code": 200,
  "message": "success",
  "data": {}
}
json

The Java side:

@Data
public class ApiResponse<T> {
    private Integer code;
    private String message;
    private T data;

    public static <T> ApiResponse<T> success(T data) {
        ApiResponse<T> response = new ApiResponse<>();
        response.setCode(200);
        response.setMessage("success");
        response.setData(data);
        return response;
    }

    public static <T> ApiResponse<T> error(Integer code, String message) {
        ApiResponse<T> response = new ApiResponse<>();
        response.setCode(code);
        response.setMessage(message);
        return response;
    }
}
java

2. Global exception handling#

With @RestControllerAdvice and @ExceptionHandler, exception handling can be centralised cleanly:

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ApiResponse<?> handleValidation(MethodArgumentNotValidException ex) {
        String msg = ex.getBindingResult().getFieldErrors()
            .stream()
            .map(e -> e.getField() + ": " + e.getDefaultMessage())
            .collect(Collectors.joining(", "));
        return ApiResponse.error(400, msg);
    }

    @ExceptionHandler(Exception.class)
    public ApiResponse<?> handleException(Exception ex) {
        return ApiResponse.error(500, "Internal server error");
    }
}
java

3. Request validation#

Spring Boot ships with Bean Validation; combined with @Valid it makes request validation straightforward:

@RestController
@RequestMapping("/api/users")
public class UserController {

    @PostMapping
    public ApiResponse<User> create(@Valid @RequestBody CreateUserRequest request) {
        // business logic
    }
}
java

4. API versioning#

Keeping the version in the URL path is the option I recommend:

  • /api/v1/users
  • /api/v2/users

That makes a breaking upgrade a gradual transition rather than a flag day.

Summary#

These are the practices I rely on when building RESTful APIs with Spring Boot. Good API design should be settled at the start of a project to avoid rework later. Hope it helps.