JDK-8287885 : Local classes cause ClassLoader error if the type names are similar but not same
  • Type: Enhancement
  • Component: tools
  • Sub-Component: javac
  • Affected Version: 18
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • OS: windows
  • CPU: x86_64
  • Submitted: 2022-06-03
  • Updated: 2023-08-21
  • Resolved: 2023-03-27
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 21
21 b16Fixed
Related Reports
Relates :  
Sub Tasks
JDK-8314673 :  
Description
ADDITIONAL SYSTEM INFORMATION :
OS = Windows 11, but I am 99% certain that this occurs for most Windows Operating systems. The relevant ones to test would be Windows 10 and Windows 8.

$ javac --version
javac 18

$ java --version
java 18 2022-03-22
Java(TM) SE Runtime Environment (build 18+36-2087)
Java HotSpot(TM) 64-Bit Server VM (build 18+36-2087, mixed mode, sharing)

A DESCRIPTION OF THE PROBLEM :
Here is a relevant StackOverflow post -- https://stackoverflow.com/q/72438283/10118965

On Windows, if I have an ABC.java and an Abc.java in the same folder, then they are treated as the same filename, and thus, forbidden, as you cannot have duplicate filenames in the same folder. That is because Windows is case-insensitive.

Java, on the other hand, IS case-sensitive. Therefore, ABC.java and Abc.java are considered meaningfully unique type names, and thus, are permitted.

This causes problems when making local classes (and maybe other parts of Java too). In the attached example, I have a local enum called ABC inside of method1(), and another local enum called Abc inside of method2(). Then, I use the following command to compile the class.

javac        ClassLoaderIssue.java

Then I use the following command to run it.

java    ClassLoaderIssue

Which then throws the following error.

Error: Could not find or load main class ClassLoaderIssue
Caused by: java.lang.NoClassDefFoundError: io/github/davidalayachew/ClassLoaderIssue (wrong name: ClassLoaderIssue)

If you look at the folder to see the generated .class files, you will see that there was only 1 of the local enums generated. On my machine, I see that only ClassLoaderIssue$1ABC.class was created, but there was no ClassLoaderIssue$1Abc.class. That is because Java treats these 2 names is unique, and therefore, is unable to create 2 different files of the same name. If I had to guess, the first local enum is created, and then the second one is created and either overwrites the first one, or silently fails to create a file.

Regardless, the point is, this is a problem. As you can see, this is a Windows specific problem, but if I am on a Linux system (or another operating system that IS case sensitive), then this problem will not exist.

I believe there is a very easy and simple fix here - simply increment the number when creating the local enum. Doing so should solve the problem, whether or not the underlying operating is case sensitive or case insensitive.

So, the expected output of compiling this program should be something like the following.

ClassLoaderIssue.class
ClassLoaderIssue$1ABC.class
ClassLoaderIssue$2Abc.class

This way, we can be case sensitive regardless of the underlying operating system file name case sensitivity.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1 - Create a new folder named ClassLoaderIssue

2 - Place the attached ClassLoaderIssue.java file into that folder

3 - Open a command line terminal and navigate into the above folder containing the above file

4 - Enter the following command

javac        ClassLoaderIssue.java

5 - Look at the contents of the folder. If you are using CommandPrompt, that would be dir. If you are using a Linux system, that would be ls

6 - Notice how there is only ClassLoaderIssue.java, and then only a single ClassLoaderIssue$1...........java file. We should expect 2 files of that format because there are 2 local enums inside of ClassLoaderIssue.java

7 - Execute the following command

java    ClassLoaderIssue

8 - If you are running this on Windows, you should receive the error mentioned in the description

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
I expected my class to run without error
ACTUAL -
It received the following error upon execution

Error: Could not find or load main class ClassLoaderIssue
Caused by: java.lang.NoClassDefFoundError: io/github/davidalayachew/ClassLoaderIssue (wrong name: ClassLoaderIssue)


---------- BEGIN SOURCE ----------
/** There seems to be a class loader error when running the below method in main(). */
public class ClassLoaderIssue
{

   /** Method 1. */
   private void method1()
   {

      enum ABC { A, B, C, ; }
      System.out.println(ABC.A);

   }

   /** Method 2. */
   private void method2()
   {

      enum Abc { A, B, C, ; }
      System.out.println(Abc.A);

   }


   /**
    *
    * Main method.
    *
    * @param   args  commandline arguments that we don't care about for this example.
    *
    */
   public static void main(String[] args)
   {

      new ClassLoaderIssue().method1();

   }

}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
I could rename the enum. Regardless, this is still a problem and something that definitely needs to be fixed.

FREQUENCY : always



Comments
Changeset: 14b970dc Author: Archie L. Cobbs <archie.cobbs@gmail.com> Committer: Vicente Romero <vromero@openjdk.org> Date: 2023-03-27 21:33:01 +0000 URL: https://git.openjdk.org/jdk/commit/14b970dc9e8d0fe1173039c01cced8a9422ec1ae
27-03-2023

A pull request was submitted for review. URL: https://git.openjdk.org/jdk/pull/12754 Date: 2023-02-25 21:22:57 +0000
20-03-2023

FYI discussion on compiler-dev: https://mail.openjdk.org/pipermail/compiler-dev/2023-February/022222.html
22-02-2023

A pull request was submitted for review. URL: https://git.openjdk.org/jdk/pull/12678 Date: 2023-02-20 20:50:48 +0000
20-02-2023

Additional Information from submitter: ====================================== I understand how now might not be the best time to tackle this issue. After all, this may be a bigger change than advertised. Changing the naming structure of .class files is changing a foundational feature of the compiler's behaviour. However, I believe calling this a "nice to have" minimizes the damage this problem causes. More specifically, it can lead to unexpected behaviour that may mask itself as a valid error. I firmly believe that labeling this as an enhancement is a mistake. First off, there are no errors or warnings upon compilation. The .class file in my example is either overwritten by whichever class gets written second, or it silently fails to generate one. Because of this, there is no indicator that all necessary .class files are created and present. At best, I can open up my directory and dig through the pile of .class files, and figure out which combination of ...$3ErrorCode.class maps to the local enum I am talking about. That is unideal. This is especially problematic because the only error you receive occurs at runtime, and it only tells you that it cannot find the correct .class file. It doesn't tell you that it was the compiler itself who overwrote it. As a result, the user is sent on a wild goose chase to track down a problem that has many possible causes, when in reality, it was the compiler itself that caused it. Next, this damages Java's maintainability in massive ways. If I make great use of local enums to keep my domain logic at the edge, and I one day decide to switch from a Linux virtual machine to a Windows virtual machine, I will be in for a rude (and very long-winded) awakening. In my example above, I only had 2 methods -- method1 and method2. But what if there were 8 methods, each with their own casing? You would think that you would receive 7 error messages for each unique casing, but you do not. You receive exactly 1, as the JVM will not fail until it attempts to use the class in question. And that is especially bad as well. If I have a portion of my code base that has this issue, I will not receive the error until I attempt to use the class that is missing its .class file. So I could have my system fully start up and operational, lulling me into a false sense of security until I run one of my infrequent operations, only to find out what's actually going on. And even if I immediately identify the source of my problem, I will then have to rewrite each and every similarly sounding local class/enum/interface/record to have a new name. And this would be especially difficult because these are local classes, so it is not as if I can search the file directory to find it. And remember, you can only find these errors one at a time, so you may be forced to restart and reattempt this process 7 times in order to validate all 8 .class files have been properly created. Obviously, when moving to a new environment, some porting is necessary. But to tell me that I must rename many of my local classes because the compiler refuses to name them with respect to the operating system? Classes are supposed to be one of the most basic and simple features of the Java language. To imply that I must port something as integral as a class implies that very little of this language can be considered platform agnostic. That portability is supposed to be this languages strong suit. Finally (and frankly), generating proper .class files from the source code is one of the compiler's most important responsibilities. If the compiler can't even do that correctly for the operating system it is on, then how can we call this language portable? How can we look at this as anything other than a bug? That would be like saying there is no problem with the grocery store putting fresh produce next to a warm kitchen because the only job of the grocery store is to put the products out, not care about how their placement affects the product. And in case it isn't clear, putting fresh fruit nearby a warm kitchen means it will spoil very quickly and go bad in little time. For most grocery stores, the produce area is the coldest area of the building, save for the freezers. If this is something that just cannot be cleanly fixed, then let's acknowledge it for what it is - a bug where fixing it would require more effort than it is worth. I am ignorant to the level of effort, so maybe you can explain why it is so difficult. But regardless, calling this an enhancement is a mistake for the reasons I've listed above. Please reconsider.
04-07-2022

this could be a nice to have in any case, not a bug, but I'm not sure right now that it is a good idea to change this area
24-06-2022

stackoverflow discussion: https://stackoverflow.com/questions/72438283/why-is-there-a-classloader-exception-when-there-are-2-objects-with-similar-type
07-06-2022

Windows uses class insensitive names. So, ClassLoaderIssue$ABC.class ClassLoaderIssue$Abc.class are same and thus only one of them can exist on the disl. Using the following compiled names would solve the problem on case insensitive operating system. ClassLoaderIssue.class ClassLoaderIssue$1ABC.class ClassLoaderIssue$2Abc.class
07-06-2022