Backend Development 12 min read

Standardizing Java API Interfaces and Controller Practices with ResultBean and AOP

The article explains common pitfalls in Java API design—such as inconsistent return formats, missing error handling, unnecessary parameters, and complex inputs—then proposes a unified ResultBean response model, controller conventions, and AOP-based logging and exception handling to improve code readability and maintainability.

Top Architect
Top Architect
Top Architect
Standardizing Java API Interfaces and Controller Practices with ResultBean and AOP

In many Java projects, interface definitions suffer from problems like inconsistent return formats, lack of failure handling, inclusion of unrelated parameters, and overly complex input types, which lead to unreadable code and frequent refactoring.

Common issues and error examples:

@PostMapping("/delete")
public Map
delete(long id, String lang) {
    // ...
}

@PostMapping("/delete")
public Object delete(long id, String lang) {
    try {
        boolean result = configService.delete(id, local);
        return result; // success returns boolean, failure returns string – bad practice
    } catch (Exception e) {
        log.error(e);
        return e.toString();
    }
}
@PostMapping("/update")
public void update(long id, xxx) {
    // no return, no failure handling – leads to extra work later
}
@PostMapping("/delete")
public Map
delete(long id, String lang, String userId) {
    // passing current user info as parameters – a serious design flaw
}
@PostMapping("/update")
public Map
update(long id, String jsonStr) {
    // using raw JSON string as a parameter – hurts readability
}
@PostMapping("/add")
public boolean add(xxx) {
    return configService.add(); // should return the created object's ID instead of a boolean
}

To solve these problems, the author recommends defining a unified response wrapper ResultBean<T> and using it consistently across all controller methods.

@Data
public class ResultBean
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 conventions:

All controller methods must return ResultBean or PageResultBean (for pagination).

Never pass raw Map , JSON , HttpServletRequest or HttpServletResponse objects to service layers.

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

Using Spring AOP, the author provides an interceptor that logs execution time, captures known exceptions (e.g., CheckException , UnloginException ), and wraps unknown exceptions into a ResultBean with a generic error 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);
            result.setMsg(e.toString());
            result.setCode(ResultBean.FAIL);
        }
        return result;
    }
}

The corresponding XML configuration for the AOP aspect is:

<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 enforcing a uniform ResultBean format and applying AOP for logging and exception handling, developers can achieve better code readability, easier testing, and consistent API responses.

Finally, the author emphasizes that good habits and clear conventions are more valuable than chasing the latest technology trends.

backendJavaaopexception handlingAPI designControllerResultBean
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

0 followers
Reader feedback

How this landed with the community

login 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.