1
\$\begingroup\$

I was answering a question, Java Exception Error Enumerations Anti-pattern, on Software Engineering Stack Exchange and found myself writing up a fair bit of code. It could use a code review so I'm bringing it here.

The question starts out with this enum:

package so.example;

public enum ErrorType {
  INVALID_DATA_FORMAT(1000),
  MISSING_REQUIRED_FIELD(1010),
  MODIFY_DISABLED(2020),
  DUPLICATE_MERCHANT(2050),
  UNKNOWN_RESELLER(2051),
  XML_PARSING_FAILURE(2999),
  SYSTEM_ERROR(3000),
  AND_HUNDREDS_MORE_LIKE_THIS(4444);

  private final int errorCode;

  ErrorType(final int errorCode) {
    this.errorCode = errorCode;
  }

  public int getErrorCode() {
    return errorCode;
  }
}

and after giving SoExampleException constructors that take the enum, uses it like so:

  private void validate(final Request requestBody) throws SoExampleException {
    if (StringUtils.isBlank(requestBody.getSomeNumber())) {
      throw new SoExampleException(ErrorType.MISSING_REQUIRED_FIELD, "someNumber cannot be empty or null");
    }
  }

The question asks for a way to solve the same problem without being forced to put the error codes in the same file. What follows is my attempt.

If the ability to give the exceptions unique names allows us to dispense with the error codes then:

public class InvalidDataFormatException extends GenericExampleException {}
public class MissingRequiredFieldException extends GenericExampleException {}
public class ModifyDisabledException extends GenericExampleException {}
public class DuplicateMerchantException extends GenericExampleException {}
public class UnknownResellerException extends GenericExampleException {}
public class XmlParsingFailureException extends GenericExampleException {}
public class SystemErrorException extends GenericExampleException {}
public class AndHundredsMoreLikeThisException extends GenericExampleException {}

Each in its own file.

If the error codes are still required then:

public abstract class AbstractChildException extends GenericExampleException implements ErrorCode {}

public class InvalidDataFormatException extends AbstractChildException { @Override public int getErrorCode() { return 1000; } }
public class MissingRequiredFieldException extends AbstractChildException { @Override public int getErrorCode() { return 1010; } }
public class ModifyDisabledException extends AbstractChildException { @Override public int getErrorCode() { return 2020; } }
public class DuplicateMerchantException extends AbstractChildException { @Override public int getErrorCode() { return 2050; } }
public class UnknownResellerException extends AbstractChildException { @Override public int getErrorCode() { return 2051; } }
public class XmlParsingFailureException extends AbstractChildException { @Override public int getErrorCode() { return 2999; } }
public class SystemErrorException extends AbstractChildException { @Override public int getErrorCode() { return 3000; } }
public class AndHundredsMoreLikeThisException extends AbstractChildException { @Override public int getErrorCode() { return 4444; } }

This would allow code that didn't statically know the exact exception to access the error code:

catch (AbstractChildException ace) {
    System.out.println("Error code: " + ace.getErrorCode());    
}

What this code would not do is make it easy to construct an exception from a dynamic error code without some reflection magic. Not sure there is any need for that. It does allow error codes to be managed in separate files.

\$\endgroup\$

3 Answers 3

2
\$\begingroup\$

The problem with this approach is that it is pretty difficult to identify all of the error codes that have been used. It's also asking for a copy/paste error where an exception class gets copied and the error code doesn't get updated.

As you note in your answer to the original question, the best solution is probably to just not use error codes. But if you must, I'd suggest you really still want a central enum which defines the possible error codes. You can still define various classes for each error which return the enum from their getStatusCode() function.

Or, you could write an annotation processor which does the magic dirty work of building out a bunch of different classes from an error enum. That might actually provide a helpful approach if you really want to have hundreds of error classes.

\$\endgroup\$
1
2
\$\begingroup\$

If the error codes are still required

I would venture to say that they are not, and that numeric error codes are an anti-pattern in Java. The type system should carry such information.

At worst (in a generic logger, for example), we can use reflection to record the exception name.

Possibly there's a case for getErrorCode() for exceptions that wrap a third-party error condition (e.g. from a database server, or wrapping a C language errno), but in those cases we already have a closed set of error numbers that we don't need to extend.

\$\endgroup\$
0
\$\begingroup\$

Numeric error codes within a Java application are in my opinion an anti-pattern. As others have said, it is hard to enumerate all possible error conditions in a complex application. I think people gravitate towards error codes because they have seen many people using them but haven't thought about why and just copy it without thinking. A kind of a cargo cult itself.

The reason error codes are used is to enumerate possible error scenarios and make them "machine readable" in public, language agnostic, APIs: your HTTP connections, database APIs, REST interfaces, etc. So your code should concentrate on making it easy to transfer from the complex internal error handling (exceptions) into simple API-specific error codes and do that only on the outer API layer. One way is to create a mapper class that lists all known exceptions and their respective error codes (if you create a new exception and foget to map it you get a generic "unknown error" code).

By specifying the error codes in the exceptions, you create a breeding ground for an unmanageable and unnecessary granularity of error codes. Often, for the API client, the only thing they need to know whether the error is recoverable or not. The fact that you added the AndHundredsMoreLikeThisException class should be enough evidence :).

The code that throws the exceptions should not need to be exposed to the error codes at all. The error code may even depend on the API through which the request came in so it's better not to lock it deep down in the business logic. Of course, as always, your exception handling mechanism has to be well structured and thought out. Otherwise the mapping from exceptions to error codes will be a nightmare.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.