12

I'm making a library that contains a few methods for parsing string dates and times. I am having difficulty deciding what exception those methods should throw when the string argument isn't parseable. I'm considering several options:

1. java.lang.IllegalArgumentException - an invalid string is clearly an illegal argument, but, to me, IllegalArgumentException typically means programming error, and it's rare to want to do an explicit try catch for one. I think string parsing is often for external input and is more of a special case that deserves special treatment. If, say, you had a big block of code that parsed user input and did something else with it, you might want to wrap that code in a try catch block so you could handle the case of the user input containing an invalid string. But catching IllegalArgumentException wouldn't be great for pinpointing invalid user input, because most likely there'd be multiple places in your code that it could be thrown from (not just the user-input parsing).

2. java.lang.NumberFormatException - it's thrown by Integer.parseInt(String) and other similar parsing methods in java.lang. So most Java developers are familiar with it being the exception to catch when you're trying to parse a string that may or may not be valid (e.g. user input). But it's got "Number" in its name, so I'm not sure it really fits with things like dates and times which are numeric in a sense, but conceptually different in my mind. If only it was called "FormatException"...

3. java.text.ParseException - not really an option because it's checked. I want unchecked for this.

4. A custom exception - this gets around the downsides of IllegalArgumentException and NumberFormatException, and it could extend IllegalArgumentException too. But I don't think it's a good idea to add exceptions to a library unless they're really needed. Quoting Josh Bloch, "If in doubt, leave it out". (Also, in my case, for a package that parses dates and times it's quite hard to name such an exception: "DateFormatException", "TimeFormatException", "CalendricalFormatException" (like JSR 310) - none seem ideal to me when applied to methods that parse dates, times, date-times etc. And I think it would be silly to create multiple exceptions in a single package if they were all just used to identify unparseable strings.)

So which option do you think makes most sense?

NB There are good reasons for me to not wanting to use java.util.Date or Joda Time, or JSR 310, so no need to suggest those. Plus I think it would be good if this question was kept fairly general, since this must be an issue that other people designing APIs have struggled with. Dates and times could just as well be IP addresses, or URLs, or any other kind of information that has string formats that need parsing.

Precedents Set by Other Libraries

Just a few examples I found:

java.sql.Timestamp.valueOf(String) throws IllegalArgumentException

java.util.Date.parse(String) throws IllegalArgumentException (deprecated method, and the exception isn't declared, but you can see it in the source)

java.util.UUID.fromString(String) throws IllegalArgumentException

org.apache.axis.types.Time(String) throws NumberFormatException (also Day class in Apache Axis)

org.apache.tools.ant.util.DeweyDecimal(String) throws NumberFormatException

com.google.gdata.data.DateTime.parseDateTime(String) throws NumberFormatException

java.lang.Package.isCompatibleWith(String versionString) throws NumberFormatException (in Java 7, this takes a string version number with dots - kind of like a date or a time in a sense)

I'm sure there are lots of packages that use a custom exception, so I doubt there's much point in giving examples of those.

MB.
  • 7,365
  • 6
  • 42
  • 42

4 Answers4

3

I would have suggested IllegalFormatException as base class (which inherits from IllegalArgumentException, but unfortunately the sole Constructor is Package-protected, so I'd probably use

  • IllegalArgumentException for a small API (a few methods)
  • your own Class Hierarchy that inherits from IllegalArgumentException otherwise.

In any case, I would use IllegalArgumentException as base class.

In one of my previous projects, the guideline was to only throw RuntimeExceptions that inherit from

  • IllegalStateException
  • IllegalArgumentException
  • UnsupportedOperationException

Although this isn't an official dogma, I'd call it a good practice. All three of these are simple and self-descriptive.

Sean Patrick Floyd
  • 292,901
  • 67
  • 465
  • 588
  • I did contemplate using IllegalFormatException directly, but it only exists in Java 1.5+, and this library I'm writing is 1.4 compatible. Starting out with IllegalArgumentException could work well. Then, as the library grows, I'd still have the option of creating and using a subclass without breaking compatibility. – MB. Apr 15 '11 at 12:12
  • @MB as I wrote: you can't use IllegalFormatException, it has no accessible constructor – Sean Patrick Floyd Apr 15 '11 at 12:36
  • Ah yes, sorry. For some reason when I first read your comment I interpreted that bit as saying that IllegalFormatException couldn't be extended. But of course with only a package-private constructor I couldn't realistically use it either. I was failing to see the wood for the trees! – MB. Apr 15 '11 at 13:10
1

I would opt for ParseException, because that's what DateFormat.parse throws. Thus it has the advantage you mention in 2., programmers being familiar with it. But since you want unchecked, I guess this is not an option. If forced to go for unchecked, I'll opt for 4.

subsub
  • 1,857
  • 10
  • 21
  • Familiar is definitely good. But I think a checked exception would be pretty cumbersome for that sort of parsing method. Like parsing integers, sometimes you want to parse user input that is likely to be invalid (and so you want to catch and handle the exception explicitly), but often you're parsing something that you expect to be valid, and then having to deal with a checked exception would be a pain. – MB. Apr 15 '11 at 12:16
  • @MB: True. One thing you could do is provide different functions for those two cases, e.g., `parse` for the first case and the `parseOrNull` or `uncheckedParse` for the latter. Unfortunately (I'm referring to your parseOrNull comment below) right now I cannot think of any good example where the Core Java API does this... Also it does not really conform to any KISS, or InDoubtLeaveItOut principles. – subsub Apr 15 '11 at 12:44
  • If it was for internal use only, I'd probably do that. `parseOrNull` and `parseOrThrow` (throwing an unchecked exception if parsing failed) is actually a convention that I use quite often. But I don't think that would be so good for a public API, for the reasons you mentioned. I suspect it would confuse people, since quite likely it wouldn't be a convention they'd seen before and really they'll just want to get the job done ASAP rather than figure out my terminology. – MB. Apr 15 '11 at 13:04
0

For this question, it is a matter of taste. In my opinion, I would not throw exception but handle that in return. The reason is that user cannot do anything in exception handler if the input was wrong. So these type of errors can be easily handled by checking the return code. Exceptions are heavy and dont bring extra value.

user709753
  • 11
  • 1
  • 2
    I agree about exceptions being heavy, but I strongly disagree about including errors in the return value. That requires bloated response objects or returning Object, both of which can make a simple API unusable. – Sean Patrick Floyd Apr 15 '11 at 12:05
  • +1 @sean-patrick-floyd: could return null, then the bloat is in the code, making it unusable again. – subsub Apr 15 '11 at 12:10
  • It is a matter of taste indeed. I had considered using methods like parseOrNull(String), but I don't think that really fits with the Java way of doing things as set by the core JDK libraries. Plus you're forcing people to check for nulls or get an unhelpful NullPointerException in the case of them parsing strings that they 100% expect to be valid (versus user input where they're expecting pretty much anything). – MB. Apr 15 '11 at 12:19
0

I'm currently leaning in favour of using NumberFormatException. I was thinking plain old IllegalArgumentException but it became clear to me that that wasn't ideal when I wrote a little code like:

try {
    final Day day = Day.fromString(userInputString);
    someMethodThatCouldAlsoThrowIllegalArgumentException(day);
} catch (IllegalArgumentException e) {
    ui.showWarningMessage("Invalid date: " + userInputString);
}

Assuming you parse a valid Day, you'll want to do something with it. And it's very likely that the methods you'd pass it to would throw an IllegalArgumentException if they received an invalid parameter (i.e. a programming error rather than a user error). In code like the above example you'd want to be careful not to confuse a programming error for invalid user input, so, to be on the safe side, you'd need something messy like a null variable defined before the try/catch block, and a check for null after it.

Swap IllegalArgumentException for NumberFormatException or a custom exception and the problem goes away.

Having seen that JDK 1.7's java.lang.Package.isCompatibleWith(String versionNo) uses NumberFormatException for a method that parses a version number (a semi-numeric string like "1.6.1.32"), I'm thinking that this might also be the sensible choice for methods that parse things like dates and times.

NumberFormatException is not exactly ideal for parsing strings that are only semi-numeric, but perhaps it's the best option when you don't want to clutter an API with a custom exception that isn't really needed. If they're doing it in java.lang, then perhaps that makes it the Java way of doing things by definition...

I'm hoping it catches on :)

MB.
  • 7,365
  • 6
  • 42
  • 42