in today’s microservices, the interaction between services is becoming more and more complex, unified exception handling specifications as the basis of the framework, once online is difficult to change, if the design is not good, it will lead to later maintenance costs are getting larger and larger. for the design of error codes, different development teams have different style habits. this article shares the author’s experience and corresponding thinking summarized from practice, hoping to inspire readers.
the source code covered in this article: https://github.com/sofn/app-engine/tree/master/common-error
what is an error code
Quoted from Alibaba’s Java Developer Manual – Exception Logs – Error Codes
the principle of error code formulation: fast traceability, easy to remember, communication standardization.
description: the error code is too perfect and complex to think of, just like the strange words in the kangxi dictionary, the words seem to be accurate, but the dictionary is not easy to carry around and is simple to understand.
example: whose fault is it that the error code answers the question? what’s wrong?
1) the error code must be able to quickly know the source of the error and quickly determine whose problem it is.
2) error codes are easy to remember and compare (easy to equals in the code).
3) the error code can be separated from the document and the system platform to achieve the purpose of offline lightweight free communication.
So can it be represented by Java exceptions? The answer is clearly no
- the source of the error must be known quickly: the exception class cannot be located quickly because of its multiplexing, and the number of exception classes and lines of code is not a stable value
- must be easy to remember and compare: exception classes are not comparable and are not conducive to front-end and back-end interactions
- Ability to communicate outside of code: Exception classes can only exist in Java code
error code design
the design of the error code is relatively simple, generally only need to define a number and describe the information. however, there are still many scenarios to consider if you want to design a complete error code system.
1. layering of error codes
most project error code designs are divided into 3 levels to meet the business scenarios, namely projects, modules, and error codes. for example, the error code is 6 digits, the first two digits are the project code, the middle two digits are the module code, and the last two digits are the exception number. the following is a description of the corresponding error code 10203:

2. representation of errors: enumerate the or class
enumerations are recommended because they are immutable and all values are described in a single file.
3. multi-module error code definition and interface definition
the most original error definition method is that all the error codes in the project are defined in a class, but this will lead to more and more error codes as the business develops, which will eventually lead to difficult maintenance, and the recommended practice is to define multiple error code enumeration classes according to the granularity of the project + module. there are two issues to consider:
(1) maintenance of project coding and module coding: it is recommended to build another enumeration class for unified maintenance
(2) unified reference of exception classes: define interfaces, enumerate classes to implement interfaces
example:
//异常接口定义public interface ErrorCode {}//模块定义public enum UserProjectCodes { LOGIN(1, 1, "登录模块"), USER(1, 2, "用户模块")}//登录模块异常码定义public enum LoginErrorCodes implements ErrorCode { USER_NOT_EXIST(0, "用户名不存在"), //错误码: 10100 PASSWORD_ERROR(1, "密码错误"); //错误码: 10101
private final int nodeNum; private final String msg;
UserLoginErrorCodes(int nodeNum, String msg) { this.nodeNum = nodeNum; this.msg = msg; ErrorManager.register(UserProjectCodes.LOGIN, this); }}
4. anti-weight design
Error codes are essentially a number, and each one needs to be defined by an RD code, which can be easily repeated in items with many error codes. It is best practice to call the Helper class in the constructor of the enumeration, which maintains all exception codes uniformly, and fails to initialize the enumeration if there are duplicates.
5. error extension information
Only the error code is not enough, but also needs to feedback to the caller detailed error information has been easily corrected. Fixed error message string in some scenarios to write is not enough, here it is recommended to use slf4j to log when using dynamic parameters, this way compared to the String.format format advantage is that do not need to care about the type of parameters and memory %s, %d, etc. the difference, and often use when printing logs, reducing the learning cost of team members.
example:
//错误码定义PARAM_ERROR(17, "参数非法,期望得到:{},实际得到:{}")//错误码使用ErrorCodes.PARAM_ERROR.format(arg1, arg2);
implementation:
org.slf4j.helpers.MessageFormatter.arrayFormat(this.message, args).getMessage()
error codes and exceptions
In daily business development, the most used for exceptions is to throw Java exceptions, which are divided into exceptions and unchecked exceptions:
- checked exceptions: this kind of exception that is forced to be checked at compile time is called a “checked exception.” that is, the exception declared in the declaration of the method.
- unchecked exceptions: various exceptions that are not declared in the declaration of a method, but occur while the method is running, are called “exceptions that are not checked”. this exception is an error and is automatically caught.
1. abnormal binding error code
defines two parent classes, one for the first check exception and one for the unchecked exception. you can support incoming error codes, but you need to support the original exception parameter, which will give a default error code, such as: 500 server internal exceptions
//父类定义public abstract class BaseException extends Exception {
protected BaseException(String message) {...}
protected BaseException(String message, Throwable cause) {...}
protected BaseException(Throwable cause) {...}
protected BaseException(ErrorInfo errorInfo) {...}
protected BaseException(ErrorCode errorCode) {...}
protected BaseException(ErrorCode errorCode, Object... args) {...}}
2. partial abnormalities
using exceptions can be applied to most scenarios, but it is not very suitable for multi-entry scenarios, such as the need to save 10 records in batches, some successes, some failures, this scenario is not suitable for directly throwing exceptions.
In Node.js and Go, exception handling is handled in a multi-return value manner, with the first value being an exception and null indicating no exceptions if null. In Java, it is recommended to use Ether in the vavr library to implement, usually using lvalues to indicate exceptions, while rvalues indicate the returned result after normal calls, i.e. Either<ErrorCode, T>
Note that It is not recommended to implement Pair and Tuple, because Either can only set an lvalue or rvalue, while Pair and Tuple do not have this restriction.
error code and uniform return value
IN THE INTERACTION BETWEEN THE FRONT AND BACK ENDS, THE BACK END GENERALLY USES JSON TO RETURN THE RESULTS, INTEGRATE THE ERROR CODES MENTIONED EARLIER, AND DEFINE THE FOLLOWING FORMATS:
{ "code": number, "msg": string, "data": object}
In SpringMVC, the implementation is custom ResponsiveBodyAdvice and exception interception, and the specific implementation is directly viewed: source code
After implementing the above steps, it can be used happily in the SpringMVC framework, which will automatically handle exceptions and encapsulate into a uniform return format
@GetMapping("/order") public Order getOrder(Long orderId) { return service.findById(orderId); }
summary
this article summarizes the various factors that need to be considered in designing error codes, and gives reference examples, which can basically meet the general medium and large projects. the most important thing is to have a specification, so that team members can comply with the norms in order to make the project a healthy iteration.