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<String, Object> 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<String, Object> delete(long id, String lang, String userId) {
    // passing current user info as parameters – a serious design flaw
}
@PostMapping("/update")
public Map<String, Object> 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<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 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.

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.

javaaopException 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

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.