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)