DEV Community

Petter Holmström
Petter Holmström

Posted on

A Potentially Expensive Mistake

Once upon a time, there was a project. We upgraded its JVM from Java 11 to Java 17 and did some smoke testing. Everything looked fine, except our financial values, which were not formatted correctly anymore.

We had been using the standard NumberFormat class with the correct locale in order to ensure the correct formatting. Java 11 did this right and formatted numbers with spaces as thousand separators, like this: 1 000 000. Java 17, for some strange reason, decided to change this and used commas instead, like this: 1,000,000, even though the locale was exactly the same.

We of course needed to fix this, so we changed the DecimalFormatSymbols like this:

var formatter = NumberFormat.getNumberInstance(locale);
var symbols = ((DecimalFormat) formatter)
    .getDecimalFormatSymbols();
symbols.setGroupingSeparator(' ');
symbols.setMonetaryGroupingSeparator(' ');
((DecimalFormat) formatter).setDecimalFormatSymbols(symbols);
Enter fullscreen mode Exit fullscreen mode

This did the trick and the financial values were again formatted correctly and all was well, right?

Unfortunately not. You can also use NumberFormat to parse strings and in a few places of the code, we did just this. When we were using only NumberFormat.getNumberInstance(locale) without any customization, this worked exactly as it was supposed to. Once we started to do customization of the formatting, things took a dangerous turn that was not picked up by any of our unit tests.

If you take a value formatted by the customized formatter and asks a non-customized formatter to parse it, like this, what do you get?

var formatted = formatter.format(1000000); // "1 000 000"
var parsed = NumberFormat.getNumberInstance(locale)
    .parse(formatted); // ??
Enter fullscreen mode Exit fullscreen mode

It turns out, the parsed variable above will not contain the value of 1000000, but 1!

You see, the NumberFormat.parse method only parses the beginning of the string. Once it encounters a character it does not recognize, it stops and returns the result. For example this code:

formatter.parse("1000 pirates on a ship");
Enter fullscreen mode Exit fullscreen mode

will not throw an exception but return 1000 and discard the rest of the string. I was not aware of this, even though it says so in the JavaDocs. This also happened with the parsing of the financial values, since space as a thousand separator was not a valid character from the point of view of the uncustomized formatter.

Needless to say, converting one million into one is quite a serious bug. In our case, it made it all the way into production before we caught it but luckily, it had not managed to cause any damage yet.

I had not expected a change of VM to have this kind of effect, but it is often the unexpected bugs that end up biting you the hardest.

Do your career a big favor. Join DEV. (The website you're on right now)

It takes one minute, it's free, and is worth it for your career.

Get started

Community matters

Top comments (0)

AWS Security LIVE!

Tune in for AWS Security LIVE!

Join AWS Security LIVE! for expert insights and actionable tips to protect your organization and keep security teams prepared.

Learn More

👋 Kindness is contagious

Dive into an ocean of knowledge with this thought-provoking post, revered deeply within the supportive DEV Community. Developers of all levels are welcome to join and enhance our collective intelligence.

Saying a simple "thank you" can brighten someone's day. Share your gratitude in the comments below!

On DEV, sharing ideas eases our path and fortifies our community connections. Found this helpful? Sending a quick thanks to the author can be profoundly valued.

Okay