1

I have recently upgraded an application from Java 1.8 to Java 11 and started seeing this error

com.pricemax.util.exceptions.IllegalUsageError: java.lang.ClassCastException: class [B cannot be cast to class [C ([B and [C are in module java.base of loader 'bootstrap')
    at com.pricemax.util.helpers.CharHelper.toCharArray(CharHelper.java:77)
    at com.pricemax.etl.steps.transform.price.PricePatternParser.parse(PricePatternParser.java:87)
    at com.pricemax.etl.steps.transform.price.PatternPriceSelector.cleanPrice(PatternPriceSelector.java:227)
    at com.pricemax.etl.steps.transform.price.PatternPriceSelector.cleanPrice(PatternPriceSelector.java:152)
    at com.pricemax.etl.steps.transform.price.PatternPriceSelector.performTransform(PatternPriceSelector.java:99)
    at com.pricemax.etl.steps.transform.BaseTransform.performTransformARC(BaseTransform.java:62)
    at com.pricemax.etl.steps.transform.BaseTransform.transformARC(BaseTransform.java:49)
    at com.pricemax.etl.process.parallel.TransformThread.doWork(TransformThread.java:99)
    at com.pricemax.etl.process.parallel.BaseParallelThread.run(BaseParallelThread.java:57)
    at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.lang.ClassCastException: class [B cannot be cast to class [C ([B and [C are in module java.base of loader 'bootstrap')
    at com.pricemax.util.helpers.CharHelper.toCharArray(CharHelper.java:73)
    ... 9 more
08/05/2022 17:46:47:0511 - [WARN] - Fatal error in parallel process

and when I got to line 77 in my charHelper.java class I see this function -

public static char[] toCharArray(String str) throws IllegalAccessException
  {
    try {
        
      return (char[]) field.get(str); //line number 77 
    }
    catch (Exception ex) {
        
      throw new IllegalUsageError(ex);
    }
  }

where field is an instance of java.lang.reflect.Field class and at least from the error what I can interpret is the error is converting class type Byte (B) to class type Character (C). Correct me if I am understanding this wrong and How can I resolve this error? NOTE - I noticed some stackoverflow articles around same problem and few people mentioned that they had the same problem in moving from java 1.8 but did not find any solution to fix this. Thanks

Gourav
  • 21
  • 1
  • 5
  • 1
    The error simply tells us that the value returned by `field.get(str);` is not of a type that can be cast to a character array. It seems unlikely that changing from Java 1.8 to 11 is the cause of this issue, are you sure that nothing else has changed in your code base? – sorifiend Aug 08 '22 at 03:24
  • Hint: Add some debugging to work out exactly what type of variable is being returned by the `Field.get(...)` method, see the answers here for a way to do this: [Retrieving the data type for an object using reflection](https://stackoverflow.com/questions/24670772/retrieving-the-data-type-for-an-object-using-reflection) – sorifiend Aug 08 '22 at 03:29
  • Looks like you are correct that this was a bug in javac versions prior to Java 9, see: https://stackoverflow.com/questions/55204967/classcastexception-in-java-11-but-not-in-java-8-when-using-hashmap and https://bugs.openjdk.org/browse/JDK-8058199 You may need to update the code to fix the existing error, or use a compiler with the older behaviour. – sorifiend Aug 08 '22 at 03:36
  • 2
    @sorifriend this was not a bug. The bug you linked to isn't relevant to this question. OP is digging into private API which changed; private API is allowed to do that without calling that 'a bug' (hence the name 'private'). – rzwitserloot Aug 08 '22 at 03:56
  • This code is trying to cast what is now a `byte[]` array to a `char[]` array. The correct way to write the code is to return `str.toCharArray()` and forget about these reflective shenanigans. Probably the entire method should be deleted, and probably its entire containing class. There was never any advantage. – user207421 Aug 08 '22 at 04:03

1 Answers1

6

Some code you're using is using unsupported hackery. As unsupported hackery is wont to do, a java upgrade 'broke it'. That isn't java's fault - the point of documentation that states 'do not do this - this is not supported' (and private is "documentation" that yells out: Do not do this - this is not supported), is that a future version may change this part and will not go out of its way to say: DANGER! Do not upgrade to this version without taking these steps!

The specific hackery it was abusing is that until java 8 or so, the contents of a java.lang.String instance were basically just a single thing: a char[].

This is inefficient; char uses 16 bits to store a single character.

These days, instead, String consists of a byte[] and a few flags. The flags are 'free' on 64-bit systems (given that all objects are aligned to fall on a divisible-by-64 boundary, and most pointers are compressed). One flag tells the string what the bytes represent. Possibly simple ASCII for simple strings with no fancy pants characters, otherwise it'll be UTF; there's room for further schemes, but as of this writing, those are the only 2.

Whatever code you are using is relying on the notion that it is a char[]. it is not, and using reflection to dig your way in and get it, is a broken act, and for which there never really was much point. I don't know why the library you are using is doing this (or if it's your code, why you are), but I doubt it's proper. As in, whatever problem it is trying to solve, I bet there was a much better way to do it.

You can't just simply update this code and fix the char[] to byte[] part: You'd also have to read that flag out to know what the bytes mean. Whatever code is doing this would need to be considerably more complicated.

From your paste, it looks like you wrote a helper method named toCharArray which does the exact same thing as str.toCharArray(), except in this hacky way that you shouldn't be doing. I cannot fathom the point or purpose to this. Perhaps a misplaced crusade of 'efficiency', because this hack avoids an array copy? That's not how you get to efficient code, generally. Given that for the vast majority of projects, less than 1% of the code takes 99% of the resources, you optimize just that 1%. Often, to do that right, you want flexible, highly abstracted code everywhere, so that changing the things that 'surround' this 1% 'hot path' can be trivially adjusted to feed precisely what the hot path must have to run as fast as possible. These kinds of 'micro optimizations' hurt far more than they help for performance alone. Let alone how fragile it makes your code. And if you need proof of that - look no further than this very question :)

rzwitserloot
  • 85,357
  • 5
  • 51
  • 72