Why Consistent API Responses Matter: Best Practices for Java Controllers
This article explains why a unified API response format like ResultBean is essential for Java Spring controllers, outlines common interface design mistakes, presents controller conventions, and demonstrates an AOP solution with logging and exception handling to improve code readability and maintainability.
1. Interface Definition
In daily work we need to define interfaces; inconsistent return formats, missing error handling, irrelevant parameters, complex JSON strings, and missing data are common pitfalls.
Common mistakes
Inconsistent return format : sometimes array, sometimes object, sometimes plain string. A unified ResultBean should be used.
Ignoring failure cases : methods return void or only success data, causing rework.
Irrelevant input parameters : language or userId should be obtained from session, not passed explicitly.
Complex input parameters : raw JSON strings should be replaced by typed beans.
Not returning required data : creation APIs should return the new object's identifier instead of a boolean.
Below is a simple ResultBean class used as a unified response wrapper.
@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_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; }
}2. Controller Specification
All controller methods should return the unified ResultBean (or PageResultBean) and never expose raw maps, JSON, or request/response objects to the service layer.
Do not log inside controllers; logging is handled by AOP.
Parameters like HttpServletRequest or HttpServletResponse should be avoided.
AOP Implementation
The AOP aspect logs execution time, captures exceptions, and wraps them into a ResultBean.
public class ControllerAOP {
private static final Logger logger = LoggerFactory.getLogger(ControllerAOP.class);
public Object handlerControllerMethod(ProceedingJoinPoint pjp) {
long start = System.currentTimeMillis();
ResultBean<?> result;
try {
result = (ResultBean<?>) pjp.proceed();
logger.info(pjp.getSignature() + " use time:" + (System.currentTimeMillis() - start));
} 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);
result.setMsg(e.toString());
result.setCode(ResultBean.FAIL);
}
return result;
}
}AOP Configuration (XML)
<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>Using a unified ResultBean enables consistent responses, simplifies AOP handling, and reduces unnecessary rework.
Java Interview Crash Guide
Dedicated to sharing Java interview Q&A; follow and reply "java" to receive a free premium Java interview guide.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
