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.
Answers:
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 occured.'),
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...
Answers:
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.
Answers:
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!
Answers:
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)
Answers:
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.
Answers:
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
Answers:
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) :)
Answers:
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.
Answers:
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.
Answers:
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.
Answers:
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());
Answers:
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.
Answers:
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
No comments:
Post a Comment