0

I cannot use a class in jar's root when my file is part of a package but when my file is not part of a package, I can.

Possibly another way to descibe the problem: A java file in the default package can refer to a class in the jar's unnamed package (class in the root of jar) but a java file in a named package cannot.

I've created a bash script to reproduce the problem.

build the jar

#!/bin/bash

# Create directory structure
mkdir -p projA/src/java/org/example
mkdir -p projA/out/artifacts

# Create Java source files
cat > projA/src/java/Class2.java << EOF
public class Class2 {
    // Empty class
}
EOF

cat > projA/src/java/org/example/Class1.java << EOF
package org.example;

public class Class1 {
    // Empty class
}
EOF

# Compile Java files
find projA/src/java -name "*.java" > sources.txt
javac -d projA/target @sources.txt

# Create JAR file
jar cf projA/out/artifacts/projA.jar -C projA/target .

# View contents of JAR file
tar tf projA/out/artifacts/projA.jar

use the jar

#!/bin/bash

# Create directory structure
mkdir -p ProjB/src/java/org/fred

# Create Java source files
cat > ProjB/src/java/Main2.java << EOF
public class Main2 {
    public static void main(String[] args) {
        Class2 class2 = new Class2();
    }
}
EOF

cat > ProjB/src/java/org/fred/Main1.java << EOF
package org.fred;

import org.example.Class1;

public class Main1 {
    public static void main(String[] args) {
        Class1 class1 = new Class1();
        // Class2 class2 = new Class2();  // <--- UNCOMMENT THIS TO REVEAL PROBLEM
    }
}
EOF

# Compile Java files
javac -d ProjB/target -cp projA/out/artifacts/projA.jar ProjB/src/java/*.java ProjB/src/java/org/fred/*.java

# Run the classes
java -cp ProjB/target:projA/out/artifacts/projA.jar Main2
java -cp ProjB/target:projA/out/artifacts/projA.jar org.fred.Main1

After running these scripts in a fresh directory, everything should compile, build and run ok. Now uncomment the line with // <--- UNCOMMENT THIS TO REVEAL PROBLEM and run the second script again. Notice the compiler error

ProjB/src/java/org/fred/Main1.java:12: error: cannot find symbol
        Class2 class2 = new Class2();

Why can't I refer to Class2 - its clearly in the root of the jar. I can refer to it successfully from ProjB/src/java/Main2.java but not from ProjB/src/java/org/fred/Main1.java?

Interestingly, I don't need to use an import statement to when I'm referring to classes in the jar unnamed package (class in the root of jar) from a java file in the default package.

abulka
  • 1,316
  • 13
  • 18

1 Answers1

1

All classes are in a package. Even ones without a package statement; these are then considered to be in the unnamed package. That is not a slang term; that is the explicit term used in the JLS (Java Language Specification).

As per the JLS, it is not possible to import from the unnamed package. import Class2; is a syntax error; there has to be at least one dot in there, and no keyword exists to indicate 'the unnamed package, please'.

The JLS also states that any source file trivially star-imports its own package. e.g, given:

package com.foo;

public class Hello {}

It is exactly as if import com.foo.*; was in that file: Any class that is in the com.foo package can just be referred to, and unless that name is explicitly imported, java will assume you meant the one in your package (named imports win over star imports; this is obvious, and specced in the JLS).

The same applies to the unnamed package: Anything in the unnamed package therefore automatically and silently star-imports the unnamed package. There is no way to write an import statement that does that, of course.

These steps add up the following fact:

It is impossible to refer to things in the unnamed package. Except from other types in the unnamed package, because they star-import the unnamed package.

Hence:

A java file in the default package can refer to a class in the jar's unnamed package (class in the root of jar) but a java file in a named package cannot.

Yes. True. And not fixable.

What??

The unnamed package is meant solely for throwaway projects and for extremely simple apps that do not want to mess with packages in any way. That's all. The moment you write package anywhere in the project, that is the moment every source file should gain a package statement.

You may find this inconvenient, but there is no fix for this; the JLS requires that java compilers work this way.

rzwitserloot
  • 85,357
  • 5
  • 51
  • 72
  • 1
    Thanks so much - this was driving me nuts. If it was my design decision the unnamed package would be accessible to all named packages - but it is what it is. Anyway, unfortunately the jar I'm trying to use is a 3rd party jar, created by https://github.com/antlr/grammars-v4/tree/master/javascript/typescript when running `mvn package` which guess what - generates a jar with all the classes in the unnamed package. I guess I'll have to learn maven and try to relocate those jar classes into a named package within the jar, so that I can access them from my java files in a named package. – abulka May 22 '23 at 06:58
  • 1
    I documented some techniques for moving classes within a jar from the root (unnamed, default) package into a named package (e.g. `org.example`) here: https://stackoverflow.com/questions/5370058/change-the-package-of-a-jar-from-the-default-package/76319317#76319317 – abulka May 24 '23 at 00:36