2

I'm formatting a BigDecimal using the DecimalFormat class but I'm having a lot of crash reports. I can't reproduce this issue myself, however I know that DecimalFormat is not thread-safe and I probably made a mistake somewhere.

I just don't know what could be the cause of this issue because I'm sure that the BigDecimal isn't null and I create a new instance of DecimalFormat every time. Is there something I overlooked?

The only thing I can come up with is to make the method synchronized. But I'm not sure if it will help and I'd rather not use it as it will slow things down.

This is one of the crashes:

java.lang.RuntimeException: 
  at java.lang.reflect.Constructor.newInstance0 (Constructor.java)
  at java.lang.reflect.Constructor.newInstance (Constructor.java:343)
  at androidx.loader.content.ModernAsyncTask$3.done (ModernAsyncTask.java:164)
  at java.util.concurrent.FutureTask.finishCompletion (FutureTask.java:383)
  at java.util.concurrent.FutureTask.setException (FutureTask.java:252)
  at java.util.concurrent.FutureTask.run (FutureTask.java:271)
  at java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1167)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:641)
  at java.lang.Thread.run (Thread.java:764)
Caused by: java.lang.NullPointerException: 
  at java.math.BigInt.putLittleEndianInts (BigInt.java:175)
  at java.math.BigInteger.getBigInt (BigInteger.java:322)
  at java.math.BigInteger.toString (BigInteger.java:856)
  at java.math.BigDecimal.toString (BigDecimal.java:2204)
  at android.icu.text.DigitList_Android.set (DigitList_Android.java:724)
  at android.icu.text.DecimalFormat_ICU58_Android.format (DecimalFormat_ICU58_Android.java:1202)
  at android.icu.text.DecimalFormat_ICU58_Android.format (DecimalFormat_ICU58_Android.java:1187)
  at java.text.DecimalFormat.format (DecimalFormat.java:709)
  at java.text.DecimalFormat.format (DecimalFormat.java:634)
  at java.text.Format.format (Format.java:157)
  at com.myapp.formatter.Format.formatAmount (Format.java:230)
  at com.myapp.formatter.MyContentProvider.query (MyContentProvider.java:123)
  at android.content.ContentProvider.query (ContentProvider.java:1057)
  at android.content.ContentProvider.query (ContentProvider.java:1149)
  at android.content.ContentProvider$Transport.query (ContentProvider.java:241)
  at android.content.ContentResolver.query (ContentResolver.java:802)
  at android.content.ContentResolver.query (ContentResolver.java:752)
  at androidx.core.content.ContentResolverCompat.query (ContentResolverCompat.java:81)
  at androidx.loader.content.CursorLoader.loadInBackground (CursorLoader.java:63)
  at androidx.loader.content.CursorLoader.loadInBackground (CursorLoader.java:41)
  at androidx.loader.content.AsyncTaskLoader.onLoadInBackground (AsyncTaskLoader.java:307)
  at androidx.loader.content.AsyncTaskLoader$LoadTask.doInBackground$532ebdd5 (AsyncTaskLoader.java:60)
  at androidx.loader.content.AsyncTaskLoader$LoadTask.doInBackground$42af7916 (AsyncTaskLoader.java:48)
  at androidx.loader.content.ModernAsyncTask$2.call (ModernAsyncTask.java:141)
  at java.util.concurrent.FutureTask.run (FutureTask.java:266)
  at java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1167)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:641)
  at java.lang.Thread.run (Thread.java:764)

And this is the code:

public static String formatAmount(BigDecimal bigDecimal, boolean includeCents, boolean includeCurrency)
{
    String formattedAmount = "";
    if (bigDecimal != null)
    {
        DecimalFormatSymbols decimalFormatSymbols = new DecimalFormatSymbols(getLocale());
        DecimalFormat decimalFormat;
        if (includeCurrency)
        {
            decimalFormat = new DecimalFormat("¤\u00A0#,##0.00;¤\u00A0-#,##0.00", decimalFormatSymbols);
        }
        else
        {
            decimalFormat = new DecimalFormat("#,##0.00;-#,##0.00", decimalFormatSymbols);
        }

        int fractionDigits = includeCents ? 2 : 0;
        bigDecimal.setScale(fractionDigits, BigDecimal.ROUND_DOWN);

        decimalFormat.setMinimumFractionDigits(fractionDigits);
        decimalFormat.setMaximumFractionDigits(fractionDigits);

        // exception happens on next line.
        formattedAmount = decimalFormat.format(bigDecimal);
    }
    return formattedAmount;
}

Edit: I don't know why my question is marked as a duplicate. It's not that I don't know what a NullPointer is or how to fix it. First of all I'm sure that my variables and paramaters aren't null. Debugging or logging isn't an option because I can't reproduce this issue. And finally the NullPointer isn't even caused by my code. So I think this is really a unique situation that I can't fix myself.

I also came across the Android code on which the nullpointer is raised. And I think it's worth mentioning that most of the crashes occur on Huawei devices. Another difference I found is that the stacktrace is different for some Android versions. The first stacktrace was for devices with Android 9. And this is the stacktrace on lower Android versions (7, 8.0 and 8.1):

java.lang.RuntimeException: 
  at java.lang.reflect.Constructor.newInstance0 (Constructor.java)
  at java.lang.reflect.Constructor.newInstance (Constructor.java:334)
  at androidx.loader.content.ModernAsyncTask$3.done (ModernAsyncTask.java:164)
  at java.util.concurrent.FutureTask.finishCompletion (FutureTask.java:383)
  at java.util.concurrent.FutureTask.setException (FutureTask.java:252)
  at java.util.concurrent.FutureTask.run (FutureTask.java:271)
  at java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1162)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:636)
  at java.lang.Thread.run (Thread.java:784)
Caused by: java.lang.NullPointerException: 
  at java.math.BigInt.putLittleEndianInts (BigInt.java:173)
  at java.math.BigInteger.getBigInt (BigInteger.java:322)
  at java.math.BigInteger.toString (BigInteger.java:856)
  at java.math.BigDecimal.toString (BigDecimal.java:2204)
  at android.icu.text.DigitList.set (DigitList.java:721)
  at android.icu.text.DecimalFormat.format (DecimalFormat.java:1187)
  at android.icu.text.DecimalFormat.format (DecimalFormat.java:1172)
  at java.text.DecimalFormat.format (DecimalFormat.java:658)
  at java.text.DecimalFormat.format (DecimalFormat.java:592)
  at java.text.Format.format (Format.java:157)
  at com.myapp.formatter.Format.formatAmount (Format.java:230)
  at com.myapp.formatter.MyContentProvider.query (MyContentProvider.java:123)
  at android.content.ContentProvider.query (ContentProvider.java:1059)
  at android.content.ContentProvider.query (ContentProvider.java:1151)
  at android.content.ContentProvider$Transport.query (ContentProvider.java:243)
  at android.content.ContentResolver.query (ContentResolver.java:766)
  at android.content.ContentResolver.query (ContentResolver.java:716)
  at androidx.core.content.ContentResolverCompat.query (ContentResolverCompat.java:81)
  at androidx.loader.content.CursorLoader.loadInBackground (CursorLoader.java:63)
  at androidx.loader.content.CursorLoader.loadInBackground (CursorLoader.java:41)
  at androidx.loader.content.AsyncTaskLoader.onLoadInBackground (AsyncTaskLoader.java:307)
  at androidx.loader.content.AsyncTaskLoader$LoadTask.doInBackground$532ebdd5 (AsyncTaskLoader.java:60)
  at androidx.loader.content.AsyncTaskLoader$LoadTask.doInBackground$42af7916 (AsyncTaskLoader.java:48)
  at androidx.loader.content.ModernAsyncTask$2.call (ModernAsyncTask.java:141)
  at java.util.concurrent.FutureTask.run (FutureTask.java:266)
  at java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1162)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:636)
  at java.lang.Thread.run (Thread.java:784)
Wirling
  • 4,810
  • 3
  • 48
  • 78
  • 1
    This isn't caused by a synchronization issue in your code. `decimalFormat` is thread-confined, so there is no way for another thread to be accessing it. – Andy Turner Sep 27 '19 at 12:11
  • @AndyTurner so what could be the issue, as you can see in the stacktrace the NullPointer isn't caused by my code and decimalFormat and bigDecimal are not null. – Wirling Sep 27 '19 at 12:15
  • 1
    Looks like a bug in the library. – Andy Turner Sep 27 '19 at 12:22
  • By any chance, does your `BigDecimal` come out of a unmarshalling? Because the NPE happens inside [`putLittleEndianInts`](https://android.googlesource.com/platform/libcore/+/17d9d33/luni/src/main/java/java/math/BigInt.java#171) which is only possible if the array parameter is `null`, which [come from the wrapped `BigInteger.digits`](https://android.googlesource.com/platform/libcore/+/17d9d33/luni/src/main/java/java/math/BigInteger.java#322), which is `transient`. – Didier L Sep 27 '19 at 15:59
  • @DidierL I think it does as the BigDecimal comes from a Parcelable class. – Wirling Sep 27 '19 at 17:02

0 Answers0