Why a Unified Controller Response Format Is Essential: Best Practices and AOP Implementation

The article explains how inconsistent API return types, missing error handling, irrelevant or complex parameters, and lack of proper result objects lead to maintenance headaches, and demonstrates a disciplined approach using a generic ResultBean, standardized controller conventions, and Spring AOP for logging and exception handling.

Java Web Project
Java Web Project
Java Web Project
Why a Unified Controller Response Format Is Essential: Best Practices and AOP Implementation

In daily development, defining interfaces for system integration and front‑back communication reveals a programmer's skill level. The author lists five common pitfalls and shows how a unified response format and disciplined controller design can avoid costly refactoring.

1. Inconsistent Return Formats

Some APIs return arrays, objects, or plain strings inconsistently. The author cites a real system where the same endpoint sometimes returns a Map<String, Object>, a JSON string, or an object, which is undesirable. The recommended solution is to always return a wrapper such as ResultBean (or PageResultBean for pagination).

// Bad example – returning a raw map
@PostMapping("/delete")
public Map<String, Object> delete(long id, String lang) {
    // ...
}

// Bad example – returning boolean on success, string on failure
@PostMapping("/delete")
public Object delete(long id, String lang) {
    try {
        boolean result = configService.delete(id, lang);
        return result;
    } catch (Exception e) {
        log.error(e);
        return e.toString();
    }
}

2. Ignoring Failure Scenarios

Designs that only consider successful paths force later changes when errors appear, causing both front‑end and back‑end code to be rewritten. The author shows a method that returns void without any error information, illustrating the problem.

// Bad example – no return, no error handling
@PostMapping("/update")
public void update(long id, xxx) {
    // ...
}

3. Including Irrelevant Parameters

Parameters such as language codes or user IDs should be obtained from the session or security context, not passed explicitly. The author warns that passing the current user ID is a serious design flaw.

// Bad example – passing userId and lang directly
@PostMapping("/delete")
public Map<String, Object> delete(long id, String lang, String userId) {
    // ...
}

4. Using Complex or Raw JSON Parameters

Accepting raw JSON strings or maps as method arguments reduces readability. Instead, define a proper bean that matches the expected structure.

// Bad example – raw JSON string parameter
@PostMapping("/update")
public Map<String, Object> update(long id, String jsonStr) {
    // ...
}

5. Not Returning Expected Data

For create operations, the API should return the newly created object's identifier. Returning only a boolean forces callers to make extra queries.

// Bad example – returning boolean for an add operation
@PostMapping("/add")
public boolean add(xxx) {
    return configService.add();
}

To address these issues, the author introduces a generic ResultBean<T> class (implemented with Lombok's @Data) that encapsulates a status code, message, and payload. The class defines constants for success, failure, no‑login, and no‑permission scenarios.

@Data
public class ResultBean<T> implements Serializable {
    private static final long serialVersionUID = 1L;
    public static final int SUCCESS = 0;
    public static final int FAIL = 1;
    public static final int NO_LOGIN = -1;
    public static final int NO_PERMISSION = 2;

    private String msg = "success";
    private int code = SUCCESS;
    private T data;

    public ResultBean() {}
    public ResultBean(T data) { this.data = data; }
    public ResultBean(Throwable e) {
        this.msg = e.toString();
        this.code = FAIL;
    }
}

Controller Guidelines

All controller methods must return ResultBean or PageResultBean.

These wrapper objects are for the controller layer only; they should not be passed to service layers.

Never forward raw Map, JSON, or HttpServletRequest/Response objects to services; instead, map them to domain beans.

Avoid logging inside controllers; let AOP handle logging and exception handling.

AOP Implementation

The author provides an AOP class that measures execution time, logs method signatures, and converts any thrown exception into a ResultBean. Known exceptions ( CheckException, UnloginException) receive specific codes, while unknown exceptions are logged and wrapped with a generic failure code.

public class ControllerAOP {
    private static final Logger logger = LoggerFactory.getLogger(ControllerAOP.class);

    public Object handlerControllerMethod(ProceedingJoinPoint pjp) {
        long startTime = System.currentTimeMillis();
        ResultBean<?> result;
        try {
            result = (ResultBean<?>) pjp.proceed();
            logger.info(pjp.getSignature() + " use time:" + (System.currentTimeMillis() - startTime));
        } catch (Throwable e) {
            result = handlerException(pjp, e);
        }
        return result;
    }

    private ResultBean<?> handlerException(ProceedingJoinPoint pjp, Throwable e) {
        ResultBean<?> result = new ResultBean<>();
        if (e instanceof CheckException) {
            result.setMsg(e.getLocalizedMessage());
            result.setCode(ResultBean.FAIL);
        } else if (e instanceof UnloginException) {
            result.setMsg("Unlogin");
            result.setCode(ResultBean.NO_LOGIN);
        } else {
            logger.error(pjp.getSignature() + " error", e);
            // TODO: notify unknown exceptions (e.g., email)
            result.setMsg(e.toString());
            result.setCode(ResultBean.FAIL);
        }
        return result;
    }
}

The corresponding Spring XML configuration registers the AOP bean and defines a pointcut that matches any public method returning ResultBean.

<aop:aspectj-autoproxy/>
<beans:bean id="controllerAop" class="xxx.common.aop.ControllerAOP"/>
<aop:config>
    <aop:aspect id="myAop" ref="controllerAop">
        <aop:pointcut id="target" expression="execution(public xxx.common.beans.ResultBean *(..))"/>
        <aop:around method="handlerControllerMethod" pointcut-ref="target"/>
    </aop:aspect>
</aop:config>

By adopting a unified ResultBean response, developers gain consistent API contracts, enable AOP‑based logging and exception handling, and avoid repetitive refactoring. The author emphasizes that the real value lies in the habit and mindset rather than the technology itself.

Source: https://zhuanlan.zhihu.com/p/28708259

backendJavaAOPSpringbest practicesapi-designController
Java Web Project
Written by

Java Web Project

Focused on Java backend technologies, trending internet tech, and the latest industry developments. The platform serves over 200,000 Java developers, inviting you to learn and exchange ideas together. Check the menu for Java learning resources.

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.