IllegalArgumentException exception analysis

IllegalArgumentException, after analyzing the stack, it was found that the most fundamental exception is the following:

java.lang.IllegalArgumentException:
No enum constant com.a.b.f.m.a.c.AType.P_M

It’s probably the above content. It seems very simple. The error message prompted is that the P_M enumeration item is not found in the AType enumeration class.

So after investigation, we found that before this exception started to appear online, a downstream system that the application relied on was released, and during the release process, an API package changed. The main change was in the Response return value class of an RPC interface. The P_M enumeration item is added to one of the enumeration parameters AType.

However, when the downstream system was released, the system we were responsible for was not notified of the upgrade, so an error was reported.

Let’s analyze why this happens.

Reproduce the problem

First, downstream system A provides a second-party library with a parameter type in the return value of an interface that is an enumeration type.

The first-party library refers to the dependencies in this project. The second-party library refers to the dependencies provided by other projects within the company. The third-party library refers to the dependencies from third parties such as other organizations and companies.

public interface AFacadeService {

    public AResponse doSth(ARequest aRequest);
}

public Class AResponse{

    private Boolean success;

    private AType aType;
}

public enum AType{

    P_T,

    A_B
}

System B then relies on this second-party library and calls the doSth method of AFacadeService through RPC remote calling.

public class BService {

    @Autowired
    AFacadeService aFacadeService;

    public void doSth(){
        ARequest aRequest = new ARequest();

        AResponse aResponse = aFacadeService.doSth(aRequest);

        AType aType = aResponse.getAType();
    }
}

At this time, if systems A and B rely on the same second-party library, the enumeration AType used by both will be the same class, and the enumeration items inside will be the same. This situation will not happen what is the problem.

However, if one day, the second-party library is upgraded and a new enumeration item P_M is added to the AType enumeration class, then only system A is upgraded, but system B is not upgraded.

Then the AType that system A depends on is like this:

public enum AType{

    P_T,

    A_B,

    P_M
}

The AType that the B system depends on is like this:

public enum AType{

    P_T,

    A_B
}

In this case**, when system B calls system A through RPC, if the type bit of aType in the AResponse returned by system A is added with P_M, system B will not be able to parse it. Generally at this time, a deserialization exception will occur in the RPC framework. cause the program to be interrupted. **

Principle Analysis

We have clearly analyzed the phenomenon of this problem, so let’s take a look at the principle and why such anomalies occur.

In fact, this principle is not difficult. Most of these RPC frameworks will use JSON format for data transmission, that is, the client will serialize the return value into a JSON string, and the server will then serialize the JSON The string is deserialized into a Java object.

During the deserialization process of JSON, for an enumeration type, it will try to call the valueOf method of the corresponding enumeration class to obtain the corresponding enumeration.

When we look at the implementation of the valueOf method of the enumeration class, we can find that if the corresponding enumeration item cannot be found from the enumeration class, an IllegalArgumentException will be thrown:

public static <T extends Enum<T>> T valueOf(Class<T> enumType,
                                            String name) {
    T result = enumType.enumConstantDirectory().get(name);
    if (result != null)
        return result;
    if (name == null)
        throw new NullPointerException("Name is null");
    throw new IllegalArgumentException(
        "No enum constant " + enumType.getCanonicalName() + "." + name);
}

Regarding this issue, there is actually a similar agreement in the “Alibaba Java Development Manual”:

![-w1538][1]?

This stipulates that “Enumerations can be used for parameters of second-party libraries, but enumerations are not allowed for return values“. The thinking behind this is what is mentioned above in this article.

Extended thinking

Why can there be an enumeration in the parameter?

I don’t know if you have thought about this issue. In fact, this has something to do with Erfangku’s responsibilities.

Generally, when system A wants to provide a remote interface for others to call, it will define a second-party library to tell the caller how to construct parameters and which interface to call.

The caller of this second-party library will make calls based on the content defined in it. The parameter construction process is completed by system B. If system B uses an old second-party library, the enumerations used will naturally be existing ones, and the new ones will not be used, so like this There won’t be any problems.

For example, in the previous example, when system B calls system A, when AType is used to construct parameters, there are only two options: P_T and A_B. Although system A already supports P_M, system B does not use it.

If system B wants to use P_M, then the second-party library needs to be upgraded.

However, the return value is different. The return value is not controlled by the client. What the server returns is determined by the second-party library it relies on.

However, in fact, compared to the regulations in the manual,I prefer not to use enumerations in the RPC interface for input and output parameters.

Generally, we have several considerations when using enumerations:

  • 1. The enumeration strictly controls the incoming content of the downstream system to avoid illegal characters.

  • 2. It is convenient for downstream systems to know which values can be passed, and it is less prone to errors.

It is undeniable that using enumerations does have some benefits, but I do not recommend using them mainly for the following reasons:

  • 1. If the second-party library is upgraded and some enumeration items in an enumeration are deleted, problems will arise when using the enumeration in the input parameters, and the caller will not be able to recognize the enumeration item.

  • 2. Sometimes, there are multiple upstream and downstream systems. For example, system C indirectly calls system A through system B. The parameters of system A are passed from system C, and system B only performs a parameter conversion and assembly. In this case, once the second-party library of system A is upgraded, both B and C will be upgraded at the same time. If either system is not upgraded, it will be incompatible.

In fact, I suggest that you use strings instead of enumerations in interfaces. Compared with the strong type of enumeration, strings are a weak type.

If strings are used instead of enumerations in the RPC interface, then the two problems we mentioned above can be avoided. The upstream system only needs to pass strings, and the legality of specific values only needs to be checked in system A. Just do the verification.

For the convenience of the caller, you can use the @see annotation of javadoc to indicate which enumeration the value of this string field is obtained from.

public Class AResponse{

    private Boolean success;

    /**
    * @see AType
    */
    private String aType;
}

For a relatively large Internet company like Ali, there may be hundreds of callers for an interface that randomly provides, and interface upgrades are also normal. We simply cannot do it twice every time. It is completely unrealistic to require all callers to upgrade together after the library is upgraded, and for some callers, there is no need to upgrade because they cannot use new features.

There is another situation that looks special but is actually quite common, that is, sometimes an interface declaration is in package A, while some enumeration constants are defined in package B. The more common one is information related to Ali’s transactions. , orders are divided into many levels, and every time a package is introduced, dozens of packages need to be introduced.

For the caller, I definitely don’t want my system to introduce too many dependencies. On the one hand, too many dependencies will slow down the compilation process of the application and easily cause dependency conflicts.

Therefore, when calling the downstream interface, if the type of the field in the parameter is an enumeration, then I have no choice but to rely on his second-party library. But if it’s not an enumeration, just a string, then I can choose not to rely on it.

Therefore, when we define interfaces, we will try to avoid using strong types such as enumerations. The specification stipulates that it is not allowed to be used in return values, but I have higher requirements, that is, I rarely use it even in the input parameters of the interface.

In the end, I just don’t recommend using enumerations in the input and output parameters of externally provided interfaces. I don’t mean not to use enumerations at all. As I mentioned in many previous articles, enumerations have many benefits, and I often use them in code . Therefore, we must not give up eating because of choking.

Of course, the opinions in this article only represent my own. Whether it is applicable to other people, other scenarios or the practice of other companies requires readers to distinguish by themselves. I suggest that you think more about it when using it.

syntaxbug.com © 2021 All Rights Reserved.