37

Seeing as Java doesn't have nullable types, nor does it have a TryParse(), how do you handle input validation without throwing an exceptions?

The usual way:

String userdata = /*value from gui*/
int val;
try
{
   val = Integer.parseInt(userdata);
}
catch (NumberFormatException nfe)
{
   // bad data - set to sentinel
   val = Integer.MIN_VALUE;
}

I could use a regex to check if it's parseable, but that seems like a lot of overhead as well.

What's the best practice for handling this situation?

EDIT: Rationale: There's been a lot of talk on SO about exception handling, and the general attitude is that exceptions should be used for unexpected scenarios only. However, I think bad user input is EXPECTED, not rare. Yes, it really is an academic point.

Further Edits:

Some of the answers demonstrate exactly what is wrong with SO. You ignore the question being asked, and answer another question that has nothing to do with it. The question isn't asking about transition between layers. The question isn't asking what to return if the number is un-parseable. For all you know, val = Integer.MIN_VALUE; is exactly the right option for the application that this completely context free code snippet was take from.

Chris Cudmore
  • 29,793
  • 12
  • 57
  • 94
  • 6
    Bad user input is ALWAYS expected. You're completely right. It shouldn't have a try/catch, for that very reason. C# > Java; case in point! – core Oct 07 '08 at 03:58
  • 1
    http://msdn.microsoft.com/en-us/library/bb397679.aspx // ToInt32 can throw FormatException or OverflowException. try { numVal = Convert.ToInt32(input); } catch (FormatException e) { Console.WriteLine("Input string is not a sequence of digits."); } catch (OverflowException e) { Console.WriteLine("The number cannot fit in an Int32."); } finally { } niiiiiceeee! – Andrej Fink Dec 03 '10 at 12:00
  • 2
    @AndrewFink Oh, but even if that were a valid point (OP was talking about TryParse which doesn't) a number that doesn't fit *is* unexpected. – Arda Xi Sep 03 '12 at 22:31
  • 3
    I agree with you 100%, both about what is wrong with SO and wanting a non-exception approach. What we did is create a tryParse() that first walks the string to verify that it is all digits. If so we then do the parse in the try/catch, otherwise we return false. – David Thielen Apr 12 '13 at 19:19
  • 2
    @DavidThielen Without getting into optimization discussions, Is avoiding the exception worth the overhead? – Chris Cudmore Apr 12 '13 at 20:58
  • 3
    @ChrisCudmore I think so. I set my IDE to break on any exception and set things up so there are no exceptions in "normal" usage. And that way if an exception occurs, I know to look at it. – David Thielen Apr 12 '13 at 21:57
  • I think this whole approach of avoiding exceptions (in a situation where exception is the natural solution!) is mistaken. @DavidThielen - we have a hierarchy of exceptions. So make an `ExceptionIDontWantToEverHappen` class or interface, and set your IDE to break on *those*, not on all exceptions. Exceptions are part of the language, they are supposed to be used, not avoided. Sure, any code you can write with exceptions, you can write *without* them too; that's what we did before exceptions were invented. But why? What other features of modern programming languages do you also want to avoid? – Viliam Búr May 28 '13 at 12:36
  • 1
    @ViliamBúr I don't want to avoid exceptions, I want to use them appropriately. And parse is a great example - C# also has TryParse and that works better when you will get non-numerics in normal use. And where the value should always be a numeric, I use Parse and catch the exception (generally several calls above). – David Thielen May 28 '13 at 12:54
  • Using exceptions appropriately means using them when there is an algorithm, and there is a situation where that algorithm couldn't complete its work. For example the algorithm is converting a string to an integer, and suddenly goes: oops, can't continue converting. -- Speaking about *unexpected* scenarios is a guideline for the beginners: if you didn't expect something, an exception will happen. But even if an expert programmer *expects* that a string could contain wrong characters, they can still use exceptions. Exception doesn't mean that something is wrong or unexpected. It's ok to use it. – Viliam Búr May 28 '13 at 13:10
  • (From a UX point of view, bad input should just be prevented.) – bigstones Sep 06 '13 at 17:32

16 Answers16

28

I asked if there were open source utility libraries that had methods to do this parsing for you and the answer is yes!

From Apache Commons Lang you can use NumberUtils.toInt:

// returns defaultValue if the string cannot be parsed.
int i = org.apache.commons.lang.math.NumberUtils.toInt(s, defaultValue);

From Google Guava you can use Ints.tryParse:

// returns null if the string cannot be parsed
// Will throw a NullPointerException if the string is null
Integer i = com.google.common.primitives.Ints.tryParse(s);

There is no need to write your own methods to parse numbers without throwing exceptions.

Community
  • 1
  • 1
Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
  • Note that the second example from Guava DOES throw an exception if s is null, instead you can use `org.apache.commons.lang.math.NumberUtils.createInteger(s)` as suggested in the @Zlosny answer. – MilesHampson Apr 06 '16 at 05:19
  • According to the [javadoc for createInteger](https://commons.apache.org/proper/commons-lang/javadocs/api-2.6/org/apache/commons/lang/math/NumberUtils.html#createInteger%28java.lang.String%29) it will throw a number format exception if it can't parse the string. I've added a note about the null pointer exception to the Guava example in the answer. – Stephen Ostermiller Apr 06 '16 at 09:31
17

For user supplied data, Integer.parseInt is usually the wrong method because it doesn't support internationisation. The java.text package is your (verbose) friend.

try {
    NumberFormat format = NumberFormat.getIntegerInstance(locale);
    format.setParseIntegerOnly(true);
    format.setMaximumIntegerDigits(9);
    ParsePosition pos = new ParsePosition(0);
    int val = format.parse(str, pos).intValue();
    if (pos.getIndex() != str.length()) {
        // ... handle case of extraneous characters after digits ...
    }
    // ... use val ...
} catch (java.text.ParseFormatException exc) {
    // ... handle this case appropriately ...
}
Tom Hawtin - tackline
  • 145,806
  • 30
  • 211
  • 305
  • A formatter is a good idea for parsing raw user input. The problem lies in the implementation. DecimalFormat can incompletely parse input, like "123.99 + one dozen". Did the user mean "123"? "124"? "136"? The only safe thing is to reject this, but DecimalFormat will silently return 123. – erickson Oct 06 '08 at 20:50
  • 1
    I think this was before @ comments, and I never saw your edit until now. +2! – erickson Apr 25 '16 at 17:31
16

That's pretty much it, although returning MIN_VALUE is kind of questionable, unless you're sure it's the right thing to use for what you're essentially using as an error code. At the very least I'd document the error code behavior, though.

Might also be useful (depending on the application) to log the bad input so you can trace.

Steve B.
  • 55,454
  • 12
  • 93
  • 132
  • 1
    Well, the example was contrived. I could also set a flag in the catch block -- userInputIsGood = false; – Chris Cudmore Oct 06 '08 at 14:34
  • Unless your application is all about parsing user-input integers, and those users are really bad at entering integers, I wouldn't expect the exception overhead to be significant. – erickson Oct 06 '08 at 20:54
  • based on what are you saying that @erickson ? unless doing a mission critical mission, it takes a 8 milliseconds even with the error catching to do 100 such try catches for different strings some of which do throw errors which are logged to file. – tgkprog Apr 25 '16 at 17:11
  • @tgkprog I'm not sure I understand your comment. You seem to be saying that handling exceptions is fast. My point was that exception overhead is insignificant in most applications. Aren't we agreeing? – erickson Apr 25 '16 at 17:28
  • i guess we are. i misunderstood "and those users are really bad at entering integers". even if they are really bad - will just be a few milliseconds more. @erickson – tgkprog Apr 25 '16 at 18:00
11

What's the problem with your approach? I don't think doing it that way will hurt your application's performance at all. That's the correct way to do it. Don't optimize prematurely.

asterite
  • 7,761
  • 2
  • 23
  • 18
  • 9
    It's not premature optimization to avoid throwing exceptions left and right. Some sanity is required in programming, regardless of when you want to "optimize". – Zenexer Nov 20 '13 at 10:57
6

I'm sure it is bad form, but I have a set of static methods on a Utilities class that do things like Utilities.tryParseInt(String value) which returns 0 if the String is unparseable and Utilities.tryParseInt(String value, int defaultValue) which allows you to specify a value to use if parseInt() throws an exception.

I believe there are times when returning a known value on bad input is perfectly acceptable. A very contrived example: you ask the user for a date in the format YYYYMMDD and they give you bad input. It may be perfectly acceptable to do something like Utilities.tryParseInt(date, 19000101) or Utilities.tryParseInt(date, 29991231); depending on the program requirements.

Grant Wagner
  • 25,263
  • 7
  • 54
  • 64
  • I have the same utility method but instead return Integer and use null for invalid integers. At some point, some one up the call chain will have to deal with an invalid integer. – Steve Kuo Oct 06 '08 at 20:54
3

I'm going to restate the point that stinkyminky was making towards the bottom of the post:

A generally well accepted approach validating user input (or input from config files, etc...) is to use validation prior to actually processing the data. In most cases, this is a good design move, even though it can result in multiple calls to parsing algorithms.

Once you know that you have properly validated the user input, then it is safe to parse it and ignore, log or convert to RuntimeException the NumberFormatException.

Note that this approach requires you to consider your model in two pieces: the business model (Where we actually care about having values in int or float format) and the user interface model (where we really want to allow the user to put in whatever they want).

In order for the data to migrate from the user interface model to the business model, it must pass through a validation step (this can occur on a field by field basis, but most scenarios call for validation on the entire object that is being configured).

If validation fails, then the user is presented with feedback informing them of what they've done wrong and given a chance to fix it.

Binding libraries like JGoodies Binding and JSR 295 make this sort of thing a lot easier to implement than it might sound - and many web frameworks provide constructs that separate user input from the actual business model, only populating business objects after validation is complete.

In terms of validation of configuration files (the other use case presented in some of the comments), it's one thing to specify a default if a particular value isn't specified at all - but if the data is formatted wrong (someone types an 'oh' instead of a 'zero' - or they copied from MS Word and all the back-ticks got a funky unicode character), then some sort of system feedback is needed (even if it's just failing the app by throwing a runtime exception).

Kevin Day
  • 16,067
  • 8
  • 44
  • 68
  • 3
    Does not answer the question – redben May 15 '13 at 16:40
  • I think it does answer the question; he's saying validate the string first (e.g. with RegEx) and then you don't need to catch the NumberFormatException. Though an example might have been clearer than a huge block of text. – charles-allen Jul 11 '17 at 08:09
2

Here's how I do it:

public Integer parseInt(String data) {
  Integer val = null;
  try {
    val = Integer.parseInt(userdata);
  } catch (NumberFormatException nfe) { }
  return val;
}

Then the null signals invalid data. If you want a default value, you could change it to:

public Integer parseInt(String data,int default) {
  Integer val = default;
  try {
    val = Integer.parseInt(userdata);
  } catch (NumberFormatException nfe) { }
  return val;
}
noah
  • 21,289
  • 17
  • 64
  • 88
1

Try org.apache.commons.lang.math.NumberUtils.createInteger(String s). That helped me a lot. There are similar methods there for doubles, longs etc.

bluish
  • 26,356
  • 27
  • 122
  • 180
Zlosny
  • 81
  • 4
  • The documentation for createInteger says that it throws a NumberFormatException if it can't parse the string: https://commons.apache.org/proper/commons-lang/javadocs/api-2.6/org/apache/commons/lang/math/NumberUtils.html#createInteger%28java.lang.String%29 – Stephen Ostermiller Apr 06 '16 at 09:33
1

I think the best practice is the code you show.

I wouldn't go for the regex alternative because of the overhead.

Shimi Bandiel
  • 5,773
  • 3
  • 40
  • 49
0

You could use a Integer, which can be set to null if you have a bad value. If you are using java 1.6, it will provide auto boxing/unboxing for you.

Milhous
  • 14,473
  • 16
  • 63
  • 82
0

Cleaner semantics (Java 8 OptionalInt)

For Java 8+, I would probably use RegEx to pre-filter (to avoid the exception as you noted) and then wrap the result in a primitive optional (to deal with the "default" problem):

public static OptionalInt toInt(final String input) {
    return input.matches("[+-]?\\d+") 
            ? OptionalInt.of(Integer.parseInt(input)) 
            : OptionalInt.empty();
}

If you have many String inputs, you might consider returning an IntStream instead of OptionalInt so that you can flatMap().

References

charles-allen
  • 3,891
  • 2
  • 23
  • 35
-1

The above code is bad because it is equivalent as the following.

// this is bad
int val = Integer.MIN_VALUE;
try
{
   val = Integer.parseInt(userdata);
}
catch (NumberFormatException ignoreException) { }

The exception is ignored completely. Also, the magic token is bad because an user can pass in -2147483648 (Integer.MIN_VALUE).

The generic parse-able question is not beneficial. Rather, it should be relevant to the context. Your application has a specific requirement. You can define your method as

private boolean isUserValueAcceptable(String userData)
{
   return (    isNumber(userData)    
          &&   isInteger(userData)   
          &&   isBetween(userData, Integer.MIN_VALUE, Integer.MAX_VALUE ) 
          );
}

Where you can documentation the requirement and you can create well defined and testable rules.

mjlee
  • 3,374
  • 4
  • 27
  • 22
  • You say "The above code is bad." Why exactly is it better to assign a default value and ignore the exception? – Michael Myers Oct 06 '08 at 15:07
  • Oh, I see. You were saying that both were bad. – Michael Myers Oct 06 '08 at 15:23
  • 1
    I'd love to see how that isBetween() method is implemented without parsing the string... So you wind up parsing the string twice - in validation scenarios, this is quite normal, though. Don't know why anyone modded you down, I'm modding it back up - you are straight on with this. – Kevin Day Oct 07 '08 at 03:45
-1

If you can avoid exceptions by testing beforehand like you said (isParsable()) it might be better--but not all libraries were designed with that in mind.

I used your trick and it sucks because stack traces on my embedded system are printed regardless of if you catch them or not :(

Bill K
  • 62,186
  • 18
  • 105
  • 157
-2

The exception mechanism is valuable, as it is the only way to get a status indicator in combination with a response value. Furthermore, the status indicator is standardized. If there is an error you get an exception. That way you don't have to think of an error indicator yourself. The controversy is not so much with exceptions, but with Checked Exceptions (e.g. the ones you have to catch or declare).

Personally I feel you picked one of the examples where exceptions are really valuable. It is a common problem the user enters the wrong value, and typically you will need to get back to the user for the correct value. You normally don't revert to the default value if you ask the user; that gives the user the impression his input matters.

If you do not want to deal with the exception, just wrap it in a RuntimeException (or derived class) and it will allow you to ignore the exception in your code (and kill your application when it occurs; that's fine too sometimes).

Some examples on how I would handle NumberFormat exceptions: In web app configuration data:

loadCertainProperty(String propVal) {
  try
  {
    val = Integer.parseInt(userdata);
    return val;
  }
  catch (NumberFormatException nfe)
  { // RuntimeException need not be declared
    throw new RuntimeException("Property certainProperty in your configuration is expected to be " +
                               " an integer, but was '" + propVal + "'. Please correct your " +
                               "configuration and start again");
    // After starting an enterprise application the sysadmin should always check availability
    // and can now correct the property value
  }
}

In a GUI:

public int askValue() {
  // TODO add opt-out button; see Swing docs for standard dialog handling
  boolean valueOk = false;
  while(!valueOk) {
    try {
      String val = dialog("Please enter integer value for FOO");
      val = Integer.parseInt(userdata);
      return val; 
    } catch (NumberFormatException nfe) {
      // Ignoring this; I don't care how many typo's the customer makes
    }
  }
}

In a web form: return the form to the user with a usefull error message and a chance to correct. Most frameworks offer a standardized way of validation.

extraneon
  • 23,575
  • 2
  • 47
  • 51
-2

Integer.MIN_VALUE as NumberFormatException is bad idea.

You can add proposal to Project Coin to add this method to Integer

@Nullable public static Integer parseInteger (String src)... it will return null for bad input

Then put link to your proposal here and we all will vote for it!

PS: Look at this http://msdn.microsoft.com/en-us/library/bb397679.aspx this is how ugly and bloated it could be

Andrej Fink
  • 350
  • 2
  • 5
-5

Put some if statements in front of it. if (null != userdata )

arinte
  • 3,660
  • 10
  • 45
  • 65