140

I am writing a web service in Java, and I am trying to figure out the best way to define error codes and their associated error strings. I need to have a numerical error code and an error string grouped together. Both the error code and error string will be sent to the client accessing the web service. For example, when a SQLException occurs, I might want to do the following:

// Example: errorCode = 1, 
//          errorString = "There was a problem accessing the database."
throw new SomeWebServiceException(errorCode, errorString);

The client program might be shown the message:

"Error #1 has occured: There was a problem accessing the database."

My first thought was to used an Enum of the error codes and override the toString methods to return the error strings. Here is what I came up with:

public enum Errors {
  DATABASE {
    @Override
    public String toString() {
      return "A database error has occured.";
    }
  },

  DUPLICATE_USER {
    @Override
    public String toString() {
      return "This user already exists.";
    }
  },

  // more errors follow
}

My question is: Is there a better way to do this? I would prefer an solution in code, rather than reading from an external file. I am using Javadoc for this project, and being able to document the error codes in-line and have them automatically update in the documentation would be helpful.

William Brendel
  • 31,712
  • 14
  • 72
  • 77
  • Late comment but worth a mention I thing... 1) Do you really need error codes here in the exception? See blabla999 answer below. 2) You should be careful passing too much error information back to the user. Useful error info should be written to server logs but the client should be told the bare minimum (e.g. "there was a problem logging in"). This is a question of security and preventing spoofers getting a foothold. – wmorrison365 Apr 11 '13 at 08:35

14 Answers14

190

Well there's certainly a better implementation of the enum solution (which is generally quite nice):

public enum Error {
  DATABASE(0, "A database error has occurred."),
  DUPLICATE_USER(1, "This user already exists.");

  private final int code;
  private final String description;

  private Error(int code, String description) {
    this.code = code;
    this.description = description;
  }

  public String getDescription() {
     return description;
  }

  public int getCode() {
     return code;
  }

  @Override
  public String toString() {
    return code + ": " + description;
  }
}

You may want to override toString() to just return the description instead - not sure. Anyway, the main point is that you don't need to override separately for each error code. Also note that I've explicitly specified the code instead of using the ordinal value - this makes it easier to change the order and add/remove errors later.

Don't forget that this isn't internationalised at all - but unless your web service client sends you a locale description, you can't easily internationalise it yourself anyway. At least they'll have the error code to use for i18n at the client side...

Youcef LAIDANI
  • 55,661
  • 15
  • 90
  • 140
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 14
    To internationalize, replace the description field with a string code that can be looked up in a resource bundle? – Marcus Downing Jan 15 '09 at 13:26
  • @Marcus: I like that idea. I'm concentrating on getting this thing out the door, but when we look at internationalization, I think I will do what you suggested. Thanks! – William Brendel Jan 15 '09 at 13:32
  • @marcus, if toString() is not overrriden (which it does not need to be), then the string code could just be the enum value toString() which would be DATABASE, or DUPLICATE_USER in this case. – rouble Nov 03 '11 at 12:49
  • @Jon Skeet! I like this solution, how one could produce a solution which is easy to localize (or translate in other languages etc.) Thinking of using it in Android can I use the R.string.IDS_XXXX instead of hard coded strings there? – A.B. Oct 02 '16 at 07:40
  • 1
    @A.B.: Well once you've got the enum, you could easily write a class to extract the relevant localized resource from the enum value, via properties files or whatever. – Jon Skeet Oct 02 '16 at 07:51
  • Thanks Jon!, I have devised something similar to this, SUCCESS(0, App.R().getString(R.string.IDS_0206)); where App is class inherited from android.app.Application and App.R() is >> public static Resources R(){ return getContext().getResources();} – A.B. Oct 02 '16 at 19:02
  • Just loved it ! @JonSkeet... Nice solution :) – Tom Taylor Apr 19 '17 at 17:54
  • Thanks, but a usage example would be nice too – Aaron Nov 16 '17 at 20:41
  • Each and every different db's will have different error codes.Do we need to define all error codes?If so to how many dbs will you configure in code ? – Ajay Takur Nov 10 '18 at 14:41
  • @AjayTakur: It's not clear to me what you're asking, to be honest - this doesn't seem to relate clearly to the question. – Jon Skeet Nov 10 '18 at 19:04
  • Eg:Assume Postgres will throw some 2xx errorcode for sqlexception and oracle will throw some 5xx for sqlexception. In Enum do you define all those for both db's? – Ajay Takur Nov 11 '18 at 06:45
  • @AjayTakur: If you're trying to abstract the database away, you'd probably try to work out the logical impact of each error on your application, and design the enum that way. Some errors from different databases may share the same logical impact, others may be database-specific. – Jon Skeet Nov 11 '18 at 07:26
39

As far as I am concerned, I prefer to externalize the error messages in a properties files. This will be really helpful in case of internationalization of your application (one properties file per language). It is also easier to modify an error message, and it won't need any re-compilation of the Java sources.

On my projects, generally I have an interface that contains errors codes (String or integer, it doesn't care much), which contains the key in the properties files for this error:

public interface ErrorCodes {
    String DATABASE_ERROR = "DATABASE_ERROR";
    String DUPLICATE_USER = "DUPLICATE_USER";
    ...
}

in the properties file:

DATABASE_ERROR=An error occurred in the database.
DUPLICATE_USER=The user already exists.
...

Another problem with your solution is the maintenability: you have only 2 errors, and already 12 lines of code. So imagine your Enumeration file when you will have hundreds of errors to manage!

abarisone
  • 3,707
  • 11
  • 35
  • 54
Romain Linsolas
  • 79,475
  • 49
  • 202
  • 273
  • 3
    I would up this more than 1 if I could. Hardcoding the strings is ugly for maintenance. – Robin Jan 15 '09 at 15:22
  • 7
    Storing String constants in interface is a bad Idea. You can use enums or use String constants in final classes with private constructor, per package or related area. Please John Skeets answer with Enums. Please check. https://stackoverflow.com/questions/320588/interfaces-with-static-fields-in-java-for-sharing-constants/320642#320642 – Anand Varkey Philips Aug 27 '17 at 19:21
22

Overloading toString() seems a bit icky -- that seems a bit of a stretch of toString()'s normal use.

What about:

public enum Errors {
  DATABASE(1, "A database error has occured."),
  DUPLICATE_USER(5007, "This user already exists.");
  //... add more cases here ...

  private final int id;
  private final String message;

  Errors(int id, String message) {
     this.id = id;
     this.message = message;
  }

  public int getId() { return id; }
  public String getMessage() { return message; }
}

seems a lot cleaner to me... and less verbose.

ctpenrose
  • 1,467
  • 2
  • 18
  • 28
Cowan
  • 37,227
  • 11
  • 66
  • 65
  • 5
    Overloading toString() on any objects (let alone enums) is quite normal. – cletus Jan 15 '09 at 13:20
  • +1 Not quite as flexible as Jon Skeet's solution, but it still solves the problem nicely. Thanks! – William Brendel Jan 15 '09 at 13:22
  • 2
    I meant that toString() is most commonly and usefully used to give enough information to identify the object -- it often includes the class name, or some way to meaningfully tell the type of object. A toString() which returns just 'A database error has occurred' would be surprising in many contexts. – Cowan Jan 15 '09 at 13:28
  • 1
    I agree with Cowan, using toString() in this way seems a bit 'hackish'. Just a quick bang for the buck and not a normal usage. For the enum, toString() should return the name of the enum constant. This would look interesting in a debugger when you want the value of a variable. – Robin Jan 15 '09 at 15:21
21

At my last job I went a little deeper in the enum version:

public enum Messages {
    @Error
    @Text("You can''t put a {0} in a {1}")
    XYZ00001_CONTAINMENT_NOT_ALLOWED,
    ...
}

@Error, @Info, @Warning are retained in the class file and are available at runtime. (We had a couple of other annotations to help describe message delivery as well)

@Text is a compile-time annotation.

I wrote an annotation processor for this that did the following:

  • Verify that there are no duplicate message numbers (the part before the first underscore)
  • Syntax-check the message text
  • Generate a messages.properties file that contains the text, keyed by the enum value.

I wrote a few utility routines that helped log errors, wrap them as exceptions (if desired) and so forth.

I'm trying to get them to let me open-source it... -- Scott

Scott Stanchfield
  • 29,742
  • 9
  • 47
  • 65
6

Just to keep flogging this particular dead horse- we've had good use of numeric error codes when errors are shown to end-customers, since they frequently forget or misread the actual error message but may sometimes retain and report a numeric value that can give you a clue to what actually happened.

telcopro
  • 148
  • 1
  • 9
5

I'd recommend that you take a look at java.util.ResourceBundle. You should care about I18N, but it's worth it even if you don't. Externalizing the messages is a very good idea. I've found that it was useful to be able to give a spreadsheet to business folks that allowed them to put in the exact language they wanted to see. We wrote an Ant task to generate the .properties files at compile time. It makes I18N trivial.

If you're also using Spring, so much the better. Their MessageSource class is useful for these sorts of things.

duffymo
  • 305,152
  • 44
  • 369
  • 561
4

There are many ways to solve this. My preferred approach is to have interfaces:

public interface ICode {
     /*your preferred code type here, can be int or string or whatever*/ id();
}

public interface IMessage {
    ICode code();
}

Now you can define any number of enums which provide messages:

public enum DatabaseMessage implements IMessage {
     CONNECTION_FAILURE(DatabaseCode.CONNECTION_FAILURE, ...);
}

Now you have several options to turn those into Strings. You can compile the strings into your code (using annotations or enum constructor parameters) or you can read them from a config/property file or from a database table or a mixture. The latter is my preferred approach because you will always need some messages that you can turn into text very early (ie. while you connect to the database or read the config).

I'm using unit tests and reflection frameworks to find all types that implement my interfaces to make sure each code is used somewhere and that the config files contain all expected messages, etc.

Using frameworks that can parse Java like https://github.com/javaparser/javaparser or the one from Eclipse, you can even check where the enums are used and find unused ones.

Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
2

I (and the rest of our team in my company) prefer to raise exceptions instead of returning error codes. Error codes have to be checked everywhere, passed around, and tend to make the code unreadable when the amount of code becomes bigger.

The error class would then define the message.

PS: and actually also care for internationalization !
PPS: you could also redefine the raise-method and add logging, filtering etc. if required (at leastin environments, where the Exception classes and friends are extendable/changeable)

blabla999
  • 3,130
  • 22
  • 24
  • sorry, Robin, but then (at least from the above example), these ought to be two exceptions - "database error" and "duplicate user" are so completely different that two separate error-subclasses should be created, which are individually catchable (one being a system, the other being an admin error) – blabla999 Jan 15 '09 at 16:02
  • and what are the error codes used for, if not to differentiate between one or the other exception ? So at least above the handler, he is exactly that: dealing with error-codes which are passed around and if-switched upon. – blabla999 Jan 15 '09 at 16:12
  • I think the name of the exception would be far more illustrative and self-describing than an error code. Better to put more thought into discovering good exception names, IMO. – duffymo Jan 15 '09 at 18:34
  • @blabla999 ah, my thoughts exactly. Why catch a coarse-grained exception and then test "if errorcode == x, or y, or z". Such a pain and goes against the grain. Then, also, you can't catch different exceptions at different levels in your stack. You'd have to catch at every level and test the error code at each. It makes the client code so much more verbose... +1 +more if I could. That said, I guess we have to answer the OPs question. – wmorrison365 Apr 11 '13 at 08:32
  • 2
    Keep in mind, this is for a web service. The client can only parse strings. On the server side there would still be exceptions thrown that has a errorCode member, that can be used in the final response to the client. – pkrish Apr 24 '15 at 17:55
1

Using interface as message constant is generally a bad idea. It will leak into client program permanently as part of exported API. Who knows, that later client programmers might parse that error messages(public) as part of their program.

You will be locked forever to support this, as changes in string format will/may break client program.

Awan Biru
  • 373
  • 2
  • 10
1

A little late but, I was just looking for a pretty solution for myself. If you have different kind of message error you can add simple, custom message factory so that you can specify more details and format that you'd like later.

public enum Error {
    DATABASE(0, "A database error has occured. "), 
    DUPLICATE_USER(1, "User already exists. ");
    ....
    private String description = "";
    public Error changeDescription(String description) {
        this.description = description;
        return this;
    }
    ....
}

Error genericError = Error.DATABASE;
Error specific = Error.DUPLICATE_USER.changeDescription("(Call Admin)");

EDIT: ok, using enum here is a little dangerous since you alter particular enum permanently. I guess better would be to change to class and use static fields, but than you cannot use '==' anymore. So I guess it's a good example what not to do, (or do it only during initialization) :)

pprzemek
  • 2,455
  • 3
  • 25
  • 25
  • 1
    Totally agree with your EDIT, it is not a good practice to alter an enum field at runtime. With this design everyone is able to edit the error message. This is pretty dangerous. **Enum fields should always be final.** – b3nyc May 15 '17 at 16:49
0

enum for error code/message definition is still a nice solution though it has a i18n concerns. Actually we may have two situations: the code/message is displayed to the end user or to the system integrator. For the later case, I18N is not necessary. I think the web services is most likely the later case.

Jimmy
  • 1,699
  • 2
  • 16
  • 17
0

Please follow the below example:

public enum ErrorCodes {
NO_File("No file found. "),
private ErrorCodes(String value) { 
    this.errordesc = value; 
    }
private String errordesc = ""; 
public String errordesc() {
    return errordesc;
}
public void setValue(String errordesc) {
    this.errordesc = errordesc;
}

};

In your code call it like:

fileResponse.setErrorCode(ErrorCodes.NO_FILE.errordesc());
Chinmoy
  • 1,391
  • 13
  • 14
0

I use PropertyResourceBundle to define the error codes in an enterprise application to manage locale error code resources. This is the best way to handle error codes instead of writing code (may be hold good for few error codes) when the number of error codes are huge and structured.

Look at java doc for more information on PropertyResourceBundle

Mujibur Rahman
  • 313
  • 1
  • 5
  • 17
0

I looked at this an other posts trying to come up with a nice way for a JSON over HTTP backend API with Spring RestControllers calling services etc. I know this is old post, but here's what we did:

We defined an Enum like this:

public enum Error {   
   ERROR_0001("0001", "A %s is required for this operation."),   
   ERROR_0002("0002", "A %s is required."),   
   ERROR_0003("0003", "Widget number is required."),   
   ERROR_0004("0004", "Widget number can only be a max 16 characters in length."),
//more as needed

private final String code;   private final String description;   public static final String ERROR_CODE_PREFIX = "BCV";

  Error(String code, String description) {
    this.code = code;
    this.description = description;   }

  public String getDescription() {
    return description;   }

  public String getCode() {
    return code;   }

  public String getCodeWithPrefix() {...}

  public String getFormattedDescriptionWithCode(@Nullable Object... messageParameters) {...}

  public String getFormattedDescription(@Nullable Object... messageParameters) {...}

Then in a Spring Validator we have:

  if (target == null) {
        errors.rejectValue(null, ERROR_0001.getCodeWithPrefix(),
                ERROR_0001.getFormattedDescriptionWithCode("Something"));
    }

And in the exception advice:

    @ControllerAdvice(annotations = RestController.class)
    @RequestMapping(produces = "application/vnd.error+json")
    @Component
    public class RestExceptionAdvice {
    
        @ExceptionHandler(Throwable.class)
        public ResponseEntity<JsonError> handleUnexpectedException(final Throwable t) {
            log.error(t.getMessage(), t);
            return new ResponseEntity<>(new JsonError("Unexpected error.", t.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR);
        }
    
        @ExceptionHandler({
                BadArgumentsException.class
        })
        public ResponseEntity<JsonError> handleBadRequest(final Exception e) {
            log.error(e.getMessage(),e);
            return new ResponseEntity<>(new JsonError(e.getMessage(), e.getLocalizedMessage()), HttpStatus.BAD_REQUEST);
        }

        //handle exceptions from Spring Validators wired into controllers
        @ExceptionHandler({
                MethodArgumentNotValidException.class
        })
        public ResponseEntity<JsonError> handleValidationBadRequest(final 
              MethodArgumentNotValidException e) {

                log.error(e.getMessage(),e);
                ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
                return new ResponseEntity<>(new JsonError(objectError.getDefaultMessage(), e.getLocalizedMessage()),HttpStatus.BAD_REQUEST);
        }
    //... more handler methods as needed

    //simple structure for frontend to handle
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    private class JsonError {
        private String message;
        private String details;
    }

Then in the UI (like Angular or whatever) see if the error coming back has the 'message' field and show it on a Toastr/Error Popup.

Randy
  • 729
  • 5
  • 14