Beware of DecimalFormat

Parsing user input into numbers (especially numbers representing money) is a very common task. Such parsing is often implemented with DecimalFormat. However, parsing with DecimalFormat should be done with care. You may even want to consider an alternative. Here's why:

Here's an example of parsing with DecimalFormat. An example run appears below. As you can see,


import java.text.*;

public final class BewareDecimalFormat {
  
  /**
  * parseUserInput DecimalFormat.
  */ 
  public static void main (String... arguments) {

    String patternOne = "#,##0.00";
    log("Format : " + patternOne);
    parseUserInput("1000", patternOne);
    parseUserInput("1,000.00", patternOne);
    parseUserInput("1000.33", patternOne);
    parseUserInput(".20", patternOne);
    parseUserInput(".2", patternOne); 
    parseUserInput(".222", patternOne); 
    parseUserInput(".222333444", patternOne); 
    parseUserInput("100.222333444", patternOne); 
    parseUserInput(".22A", patternOne); 
    parseUserInput(".22BlahA", patternOne); 
    parseUserInput("22Blah", patternOne); 
    parseUserInput("Blah22", patternOne); 
    parseUserInput("100000,000.00", patternOne);
    parseUserInput("10,0000,000.00", patternOne);
    parseUserInput("1,0000,0000.00", patternOne);

    String patternTwo= "#,###0.00"; //group every 4 digits, not 3
    log(" ");
    log("Format : " + patternTwo);
    parseUserInput("1000.00", patternTwo);
    parseUserInput("1,000.00", patternTwo);
    parseUserInput("1,0000.00", patternTwo);
    parseUserInput("1,00000.00", patternTwo);
    log("Done.");
  }

  // PRIVATE

  private static void parseUserInput(String value, String decimalFormatPattern) {
    DecimalFormat format = new DecimalFormat(decimalFormatPattern);
    ParsePosition parsePosition = new ParsePosition(0);
    Object object = format.parse(value, parsePosition);

    if(object == null) {
      log("Failed to parse: " + value);
    }
    else if(parsePosition.getIndex() < value.length()) {
      log(
        value + " parsed OK (not whole input) ParsePos:" + 
        parsePosition.getIndex() + ", Parse Result: " + object 
      );
    }
    else {
      log(
        value + " parsed OK. ParsePos: " + parsePosition.getIndex() + 
        ", Parse Result: " + object
      );
    } 
  }

  private static void log(Object message){
    System.out.println(String.valueOf(message));
  }
} 

Example run:
>java -cp . BewareDecimalFormat
Format : #,##0.00
1000 parsed OK. ParsePos: 4, Parse Result: 1000
1,000.00 parsed OK. ParsePos: 8, Parse Result: 1000
1000.33 parsed OK. ParsePos: 7, Parse Result: 1000.33
.20 parsed OK. ParsePos: 3, Parse Result: 0.2
.2 parsed OK. ParsePos: 2, Parse Result: 0.2
.222 parsed OK. ParsePos: 4, Parse Result: 0.222
.222333444 parsed OK. ParsePos: 10, Parse Result: 0.222333444
100.222333444 parsed OK. ParsePos: 13, Parse Result: 100.222333444
.22A parsed OK (not whole input) ParsePos:3, Parse Result: 0.22
.22BlahA parsed OK (not whole input) ParsePos:3, Parse Result: 0.22
22Blah parsed OK (not whole input) ParsePos:2, Parse Result: 22
Failed to parse: Blah22
100000,000.00 parsed OK. ParsePos: 13, Parse Result: 100000000
10,0000,000.00 parsed OK. ParsePos: 14, Parse Result: 100000000
1,0000,0000.00 parsed OK. ParsePos: 14, Parse Result: 100000000

Format : #,###0.00
1000.00 parsed OK. ParsePos: 7, Parse Result: 1000
1,000.00 parsed OK. ParsePos: 8, Parse Result: 1000
1,0000.00 parsed OK. ParsePos: 9, Parse Result: 10000
1,00000.00 parsed OK. ParsePos: 10, Parse Result: 100000
Done.