Java CompactNumberFormat Bug or Feature?

Java 12 introduced a new CompactNumberFormat class, which anyone studying for the new Java 17 1Z0-829 OCP certification should know. It’s really cool utility feature, helping to shorten lengthy number values into shorter forms for common usage. It supports a Style setting, SHORT (1M) vs LONG (1 million), as well as rounding, and many other features. Generally speaking, it rounds the value to the first human-readable 3-digit tuple, formats it, and then adds a label depending on style/locale.

Let’s take a look at an example:

var shortCNF = NumberFormat.getCompactNumberInstance(Locale.US,
   Style.SHORT);
var longCNF = NumberFormat.getCompactNumberInstance(Locale.US,
   Style.LONG);
System.out.print(shortCNF.format(15_300));          // 15K
System.out.print(longCNF.format(15_300));           // 15 thousand
System.out.print(shortCNF.format(124_000_200));     // 124M
System.out.print(longCNF.format(124_000_200));      // 124 million
System.out.print(shortCNF.format(4_834_000_000.0)); // 5B
System.out.print(longCNF.format(4_834_000_000.0));  // 5 billion

Useful stuff, right? Notice the last two examples rounded the value up to 5 bllion? Rounding (which can be disabled) is enabled by default. Well, while writing some really tricky practice exam questions for our upcoming Java OCP 17 Practice Test Book, I discovered something rather odd:

var shortCNF = NumberFormat.getCompactNumberInstance(Locale.US,
   Style.SHORT);
var longCNF = NumberFormat.getCompactNumberInstance(Locale.US,
   Style.LONG);
System.out.print(shortCNF.format(999_999));   // 1000K (this is weird)
System.out.print(longCNF.format(999_999));    // 1000 thousand (this is weird)
System.out.print(shortCNF.format(1_000_000)); // 1M
System.out.print(longCNF.format(1_000_000));  // 1 million
System.out.print(shortCNF.format(1_999_999)); // 2M
System.out.print(longCNF.format(1_999_999));  // 2 million

Notice the issue? If the CompactNumberFormat rounds up and enters a new range (thousand to million, million to billion, etc), it doesn’t adjust the labels or values. The first two sets of values should print 1M and 1 million, but the rounded value prints 1000K and 1000 thousand instead. While I used Locale.US for reproducibility, this isn’t required. It appears when you use other locales, and other ranges. For instance, 999_999_999 formats as 1000M, instead of 1B. I validated on Oracle’s latest release of Java 17.0.2.

So.. is this a bug or a feature? It partially depends how you read the unicode spec the Java feature was based on. The spec covers formatting rules and order of operation, but it doesn’t provide as much insight on how rounding is supposed to be handled in this particular situation.

I believe this is a bug because:

  • No one would ever expect (or want) to see one million written as 1000 thousand or 1000K. If you saw that on a website or mobile app, you’d likely report it as a bug. (If you’re a developer using this feature, you would probably then be told to stop using this library altogether!)
  • If it is working as designed, then the spec has a problem. The only work-around for someone who wants to use CompactNumberFormat without encountering this issue is to either disable rounding, or round the value ahead of time. In either situation, the utility of using the CompactNumberFormat feature drops precipitously.

To me, it’s a bug…. or a feature that renders CompactNumberFormat not suitable for practical use. With that in mind, I opened a bug ticket JDK-8281317 with Oracle to address the issue. I will update this page when I get a response!

Side note: On February 6, I created a Twitter poll and interestingly enough the correct answer of 1000K was the least selected option! Certainly, not an intuitive implementation!

Announcing the New Java 17 1Z0-829 Certification Exam and Study Guides!

Scott and Jeanne are thrilled to announce that Oracle has finally announced the new Java 17 1Z0-829 exam! Having worked with Oracle to create the objectives, we’ve been anxiously waiting to let all of our readers know that we already have two books for the new exam well underway:

In fact, you can preorder the Java 17 Complete Study Guide now!

The exam is similar to the Java 11 1Z0-819 that preceded it. The following are some of the key changes Oracle has made to the new exam:

  • Sealed Classes and Records have been added
  • Switch Expressions have been added
  • Pattern Matching has been added
  • Text Blocks have been added
  • Math API is on the exam
  • Date/Time API is back on the exam (previously on the Java 8 exam)
  • Annotations and Security have been removed

We’ll post more details soon about the exam including when you can sign up to take it, as Oracle releases the information!

Happy Book Birthday! OCP Java 11 Practice Tests Now Shipping!

Jeanne and I are ecstatic to announce our new book, OCP Java 11 Practice Tests, is now shipping! It’s the first book in print custom written for the new 1Z0-819 Java 11 Certification Exam as well as the 1Z0-817 Java 11 Upgrade Exam.

Want to become Java 11 Certified but not sure where to start? Purchase our new Java 11 Practice Tests book along with our Java 11 Complete Study Guide, now available as The Java 11 Complete Certification Kit. While Oracle restructured the 1Z0-815 and 1Z0-816 Exams into the 1Z0-819 Exam, the material is almost identical (see this post for more details), making the Java 11 Complete Study Guide along with our new Java 11 Practice Tests Book the best source of material for learning everything you need to become a Java 11 Certified Professional!

Like our previous books, we will post any updates or notes on this blog’s Java 11 Practice Tests Book page.

A special thanks to all of our friends and family that helped write this book in the midst of the global pandemic. It’s been a challenging year and we couldn’t have done it without your support!