Standardized API Response, Global Exception Handling, and Log Collection in Spring Boot

This article explains how to define a unified JSON response format for Spring Boot APIs, implement a Result class with chainable methods, set up global exception handling using @ControllerAdvice, and configure Logback for comprehensive log collection across different environments.

Java Captain
Java Captain
Java Captain
Standardized API Response, Global Exception Handling, and Log Collection in Spring Boot

The article introduces a unified JSON response structure for Spring Boot applications, aiming to simplify front‑end/back‑end interaction and UI presentation. The standard format includes fields for success flag, status code, message, data payload, and optional identifiers.

Result Enum Definition

@Getter
public enum ResultCodeEnum {
    SUCCESS(true,20000,"成功"),
    UNKNOWN_ERROR(false,20001,"未知错误"),
    PARAM_ERROR(false,20002,"参数错误"),
    // ... other enums
    ;
    // response success flag
    private Boolean success;
    // response status code
    private Integer code;
    // response message
    private String message;
    ResultCodeEnum(boolean success, Integer code, String message) {
        this.success = success;
        this.code = code;
        this.message = message;
    }
}

Unified Result Class (R)

@Data
public class R {
    private Boolean success;
    private Integer code;
    private String message;
    private Map<String, Object> data = new HashMap<>();
    // private constructor to enforce usage of static methods
    private R(){}
    public static R ok() {
        R r = new R();
        r.setSuccess(ResultCodeEnum.SUCCESS.getSuccess());
        r.setCode(ResultCodeEnum.SUCCESS.getCode());
        r.setMessage(ResultCodeEnum.SUCCESS.getMessage());
        return r;
    }
    public static R error() {
        R r = new R();
        r.setSuccess(ResultCodeEnum.UNKNOWN_ERROR.getSuccess());
        r.setCode(ResultCodeEnum.UNKNOWN_ERROR.getCode());
        r.setMessage(ResultCodeEnum.UNKNOWN_ERROR.getMessage());
        return r;
    }
    public static R setResult(ResultCodeEnum result) {
        R r = new R();
        r.setSuccess(result.getSuccess());
        r.setCode(result.getCode());
        r.setMessage(result.getMessage());
        return r;
    }
    // chainable methods for custom data, message, code, etc.
    public R data(Map<String,Object> map) {
        this.setData(map);
        return this;
    }
    public R data(String key,Object value) {
        this.data.put(key, value);
        return this;
    }
    public R message(String message) {
        this.setMessage(message);
        return this;
    }
    public R code(Integer code) {
        this.setCode(code);
        return this;
    }
    public R success(Boolean success) {
        this.setSuccess(success);
        return this;
    }
}

Controller Usage Example

@RestController
@RequestMapping("/api/v1/users")
public class TeacherAdminController {
    @Autowired
    private UserService userService;
    @GetMapping
    public R list() {
        List<Teacher> list = teacherService.list(null);
        return R.ok().data("itms", list).message("用户列表");
    }
}

The resulting JSON response looks like:

{
  "success": true,
  "code": 20000,
  "message": "查询用户列表",
  "data": {
    "itms": [
      {"id":"1","username":"admin","role":"ADMIN","deleted":false,"gmtCreate":"2019-12-26T15:32:29","gmtModified":"2019-12-26T15:41:40"},
      {"id":"2","username":"zhangsan","role":"USER","deleted":false,"gmtCreate":"2019-12-26T15:32:29","gmtModified":"2019-12-26T15:41:40"}
    ]
  }
}

Global Exception Handling

To capture runtime exceptions that bypass normal return flow, a unified global exception handler is defined using @ControllerAdvice.

Custom Exception Class

@Data
public class CMSException extends RuntimeException {
    private Integer code;
    public CMSException(Integer code, String message) {
        super(message);
        this.code = code;
    }
    public CMSException(ResultCodeEnum resultCodeEnum) {
        super(resultCodeEnum.getMessage());
        this.code = resultCodeEnum.getCode();
    }
    @Override
    public String toString() {
        return "CMSException{" + "code=" + code + ", message=" + this.getMessage() + '}';
    }
}

Global Exception Handler

@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    /**--- generic exception handler ---*/
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public R error(Exception e) {
        // e.printStackTrace();
        log.error(ExceptionUtil.getMessage(e));
        return R.error();
    }
    /**--- specific NullPointerException handler ---*/
    @ExceptionHandler(NullPointerException.class)
    @ResponseBody
    public R error(NullPointerException e) {
        e.printStackTrace();
        return R.setResult(ResultCodeEnum.NULL_POINT);
    }
    /**--- custom CMSException handler ---*/
    @ExceptionHandler(CMSException.class)
    @ResponseBody
    public R error(CMSException e) {
        e.printStackTrace();
        return R.error().message(e.getMessage()).code(e.getCode());
    }
}

When a NullPointerException occurs, the response will be:

{
  "success": false,
  "code": 20007,
  "message": "空指针异常",
  "data": {}
}

Log Collection with Logback

Logback is recommended for logging in Spring Boot. The article provides a complete logback-spring.xml configuration that defines console and file appenders for different log levels (DEBUG, INFO, WARN, ERROR), rolling policies, and environment‑specific profiles (dev vs. prod).

<configuration scan="true" scanPeriod="10 seconds">
    <property name="log.path" value="D:/Documents/logs/edu"/>
    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
    <conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
    <conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
    <property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>debug</level>
        </filter>
        <encoder>
            <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>
    <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/edu_debug.log</file>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/web-debug-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>debug</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!-- similar appenders for INFO_FILE, WARN_FILE, ERROR_FILE omitted for brevity -->
    <springProfile name="dev">
        <logger name="com.cms" level="info"/>
        <root level="info">
            <appender-ref ref="CONSOLE"/>
            <appender-ref ref="DEBUG_FILE"/>
            <appender-ref ref="INFO_FILE"/>
            <appender-ref ref="WARN_FILE"/>
            <appender-ref ref="ERROR_FILE"/>
        </root>
    </springProfile>
    <springProfile name="pro">
        <logger name="com.cms" level="warn"/>
        <root level="info">
            <appender-ref ref="ERROR_FILE"/>
            <appender-ref ref="WARN_FILE"/>
        </root>
    </springProfile>
</configuration>

Exception Logging Utility

@Slf4j
public class ExceptionUtil {
    /**
     * Convert stack trace to string
     */
    public static String getMessage(Exception e) {
        String swStr = null;
        try (StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)) {
            e.printStackTrace(pw);
            pw.flush();
            sw.flush();
            swStr = sw.toString();
        } catch (IOException ex) {
            ex.printStackTrace();
            log.error(ex.getMessage());
        }
        return swStr;
    }
}

By integrating ExceptionUtil.getMessage(e) into the global handler, exception details are recorded in the appropriate log files, facilitating faster debugging in production.

References: original article on Juejin, Logback configuration blog, and the open‑source project cloud‑flow .

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.

JavaSpring BootlogbackAPI response
Java Captain
Written by

Java Captain

Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java 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.