Add jep511 module: module import declarations on JDK 25 and 27
Runnable sources, deliberate compile failures, three small modules and nine captured transcripts for import module (JEP 511). Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01TF9JWFvJSNm6HVzswzZU5a
This commit is contained in:
@@ -64,6 +64,7 @@ These modules keep only sources, a `run.sh` and their transcripts (`output/`). T
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| [`jep512/`](jep512) | Compact Source Files and Instance Main Methods in Java 25 (JEP 512) | `java Hello.java` on JDK 21 vs 25 vs 27, the class `javac` builds around `void main()`, implicit `java.base` imports, `java.lang.IO`, the measured launch order, a private-constructor difference between 25 and 27 |
|
| [`jep512/`](jep512) | Compact Source Files and Instance Main Methods in Java 25 (JEP 512) | `java Hello.java` on JDK 21 vs 25 vs 27, the class `javac` builds around `void main()`, implicit `java.base` imports, `java.lang.IO`, the measured launch order, a private-constructor difference between 25 and 27 |
|
||||||
| [`jep513/`](jep513) | Flexible Constructor Bodies in Java 25 (JEP 513): Validate Before super() | statements before `super(...)`/`this(...)`, the parent-calls-child bug and its fix, twelve compile errors that remain with the 25 and 27 wordings side by side, `--release` rejection |
|
| [`jep513/`](jep513) | Flexible Constructor Bodies in Java 25 (JEP 513): Validate Before super() | statements before `super(...)`/`this(...)`, the parent-calls-child bug and its fix, twelve compile errors that remain with the 25 and 27 wordings side by side, `--release` rejection |
|
||||||
|
| [`jep511/`](jep511) | Module Import Declarations in Java 25 (JEP 511) | `import module java.base;` against seven single imports, ambiguity errors and how single-type, on-demand and same-package names beat a module import, what one module import does and does not bring in (`java.sql` does not give `java.base`; `java.se` needs `--add-modules`), named modules, JEP 512 compact files, jshell's default imports, identical bytecode |
|
||||||
|
|
||||||
## Captured output (`docs/output/`)
|
## Captured output (`docs/output/`)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# jep511 — Module Import Declarations (JEP 511, final in Java 25)
|
||||||
|
|
||||||
|
Companion code for the ankurm.com article **Module Import Declarations in Java 25 (JEP 511)**. The explanation lives in the article;
|
||||||
|
this folder holds the runnable sources and the transcripts they produced.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
JDK25=/path/to/jdk-25 JDK27=/path/to/jdk-27 [JDK21=/path/to/jdk-21] ./run.sh # regenerates output/*.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
`src/` compiles and runs; `broken/` fails to compile on purpose (the diagnostics are captured in `output/`). `multi/` holds three small
|
||||||
|
modules (`acme.text` is a library, `demo.app` uses it, `demo.bad` and `demo.noreq` show the two ways to get it wrong). Tested on
|
||||||
|
Temurin 25.0.4.1+1 and 27+35; JDK 21 is used only for the "before" error and the jshell import list.
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import module java.base;
|
||||||
|
import module java.sql;
|
||||||
|
|
||||||
|
public class AmbiguousDate {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
Date d = new Date(0L);
|
||||||
|
System.out.println(d);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import module java.base;
|
||||||
|
import module java.desktop;
|
||||||
|
|
||||||
|
public class AmbiguousList {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
List<String> names = new ArrayList<>();
|
||||||
|
names.add("x");
|
||||||
|
System.out.println(names);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
public class ClassicNoImport {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
System.out.println(List.of("no imports at all"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
public class ModuleInsideClass {
|
||||||
|
import module java.base;
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import module java.nosuch;
|
||||||
|
|
||||||
|
public class NoSuchModule {
|
||||||
|
public static void main(String[] args) { }
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import module java.sql;
|
||||||
|
|
||||||
|
public class SqlDoesNotGiveBase {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
List<String> l = List.of("x"); // java.util.List lives in java.base
|
||||||
|
System.out.println(l);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package acme.text;
|
||||||
|
|
||||||
|
public final class Slug {
|
||||||
|
private Slug() {}
|
||||||
|
public static String of(String s) { return acme.text.internal.Helper.clean(s); }
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package acme.text.internal;
|
||||||
|
|
||||||
|
public final class Helper {
|
||||||
|
public static String clean(String s) { return s.toLowerCase().replaceAll("[^a-z0-9]+", "-").replaceAll("^-|-$", ""); }
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
module acme.text {
|
||||||
|
exports acme.text; // acme.text.internal is NOT exported
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package demo;
|
||||||
|
|
||||||
|
import module acme.text; // imports Slug, but not acme.text.internal.Helper
|
||||||
|
|
||||||
|
public class App {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
System.out.println(Slug.of("Import Module: Hello, World!"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
module demo.app {
|
||||||
|
requires acme.text;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package demo;
|
||||||
|
|
||||||
|
import module acme.text;
|
||||||
|
|
||||||
|
public class Bad {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
System.out.println(Helper.clean("x")); // Helper is in the non-exported acme.text.internal
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
module demo.bad {
|
||||||
|
requires acme.text;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package demo;
|
||||||
|
|
||||||
|
import module java.sql; // demo.noreq never says "requires java.sql"
|
||||||
|
|
||||||
|
public class NoReq {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
System.out.println(Connection.TRANSACTION_NONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
module demo.noreq { }
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
$ javac src/OldWay.java && java OldWay (25.0.4.1+1-LTS)
|
||||||
|
{a=[apple, avocado], b=[banana, blueberry], c=[cherry]}
|
||||||
|
a/b/c.txt on 2026-09-15
|
||||||
|
|
||||||
|
$ javac src/OldWay.java && java OldWay (27+35)
|
||||||
|
{a=[apple, avocado], b=[banana, blueberry], c=[cherry]}
|
||||||
|
a/b/c.txt on 2026-09-15
|
||||||
|
|
||||||
|
$ javac src/NewWay.java && java NewWay (25.0.4.1+1-LTS)
|
||||||
|
{a=[apple, avocado], b=[banana, blueberry], c=[cherry]}
|
||||||
|
a/b/c.txt on 2026-09-15
|
||||||
|
|
||||||
|
$ javac src/NewWay.java && java NewWay (27+35)
|
||||||
|
{a=[apple, avocado], b=[banana, blueberry], c=[cherry]}
|
||||||
|
a/b/c.txt on 2026-09-15
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
$ javac broken/AmbiguousDate.java (25.0.4.1+1-LTS)
|
||||||
|
broken/AmbiguousDate.java:6: error: reference to Date is ambiguous
|
||||||
|
Date d = new Date(0L);
|
||||||
|
^
|
||||||
|
both class java.sql.Date in java.sql and class java.util.Date in java.util match
|
||||||
|
broken/AmbiguousDate.java:6: error: reference to Date is ambiguous
|
||||||
|
Date d = new Date(0L);
|
||||||
|
^
|
||||||
|
both class java.sql.Date in java.sql and class java.util.Date in java.util match
|
||||||
|
2 errors
|
||||||
|
|
||||||
|
$ javac broken/AmbiguousDate.java (27+35)
|
||||||
|
broken/AmbiguousDate.java:6: error: reference to Date is ambiguous
|
||||||
|
Date d = new Date(0L);
|
||||||
|
^
|
||||||
|
both class java.sql.Date in java.sql and class java.util.Date in java.util match
|
||||||
|
broken/AmbiguousDate.java:6: error: reference to Date is ambiguous
|
||||||
|
Date d = new Date(0L);
|
||||||
|
^
|
||||||
|
both class java.sql.Date in java.sql and class java.util.Date in java.util match
|
||||||
|
2 errors
|
||||||
|
|
||||||
|
$ javac broken/AmbiguousList.java (25.0.4.1+1-LTS)
|
||||||
|
broken/AmbiguousList.java:6: error: reference to List is ambiguous
|
||||||
|
List<String> names = new ArrayList<>();
|
||||||
|
^
|
||||||
|
both class java.awt.List in java.awt and interface java.util.List in java.util match
|
||||||
|
1 error
|
||||||
|
|
||||||
|
$ javac broken/AmbiguousList.java (27+35)
|
||||||
|
broken/AmbiguousList.java:6: error: reference to List is ambiguous
|
||||||
|
List<String> names = new ArrayList<>();
|
||||||
|
^
|
||||||
|
both class java.awt.List in java.awt and interface java.util.List in java.util match
|
||||||
|
1 error
|
||||||
|
|
||||||
|
$ javac src/FixedDate.java && java FixedDate (25.0.4.1+1-LTS)
|
||||||
|
java.util.Date
|
||||||
|
java.sql.Date
|
||||||
|
|
||||||
|
$ javac src/FixedDate.java && java FixedDate (27+35)
|
||||||
|
java.util.Date
|
||||||
|
java.sql.Date
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
$ javac src/StarImportWins.java && java StarImportWins (25.0.4.1+1-LTS)
|
||||||
|
java.util.Date
|
||||||
|
|
||||||
|
$ javac src/StarImportWins.java && java StarImportWins (27+35)
|
||||||
|
java.util.Date
|
||||||
|
|
||||||
|
$ javac src/StarImportWinsSql.java && java StarImportWinsSql (25.0.4.1+1-LTS)
|
||||||
|
java.sql.Date
|
||||||
|
|
||||||
|
$ javac src/StarImportWinsSql.java && java StarImportWinsSql (27+35)
|
||||||
|
java.sql.Date
|
||||||
|
|
||||||
|
$ javac src/SamePackageWins.java && java SamePackageWins (25.0.4.1+1-LTS)
|
||||||
|
my own Date
|
||||||
|
java.util.Date
|
||||||
|
|
||||||
|
$ javac src/SamePackageWins.java && java SamePackageWins (27+35)
|
||||||
|
my own Date
|
||||||
|
java.util.Date
|
||||||
|
|
||||||
|
$ javac src/UnusedAmbiguity.java && java UnusedAmbiguity (25.0.4.1+1-LTS)
|
||||||
|
no Date mentioned, no error
|
||||||
|
|
||||||
|
$ javac src/UnusedAmbiguity.java && java UnusedAmbiguity (27+35)
|
||||||
|
no Date mentioned, no error
|
||||||
|
|
||||||
|
$ javac src/DuplicateImports.java && java DuplicateImports (25.0.4.1+1-LTS)
|
||||||
|
[dups are fine]
|
||||||
|
|
||||||
|
$ javac src/DuplicateImports.java && java DuplicateImports (27+35)
|
||||||
|
[dups are fine]
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
$ java --describe-module java.sql (25.0.4.1+1-LTS)
|
||||||
|
[email protected]
|
||||||
|
exports java.sql
|
||||||
|
exports javax.sql
|
||||||
|
requires java.xml transitive
|
||||||
|
requires java.logging transitive
|
||||||
|
requires java.transaction.xa transitive
|
||||||
|
requires java.base mandated
|
||||||
|
uses java.sql.Driver
|
||||||
|
|
||||||
|
$ java --describe-module java.se | grep -c 'requires' (25.0.4.1+1-LTS)
|
||||||
|
20
|
||||||
|
|
||||||
|
$ java --describe-module java.se | grep -c exports (25.0.4.1+1-LTS)
|
||||||
|
0
|
||||||
|
|
||||||
|
$ javac src/Transitive.java && java Transitive (25.0.4.1+1-LTS)
|
||||||
|
demo
|
||||||
|
0
|
||||||
|
java.xml
|
||||||
|
[java.base needed its own import]
|
||||||
|
|
||||||
|
$ javac src/Transitive.java && java Transitive (27+35)
|
||||||
|
demo
|
||||||
|
0
|
||||||
|
java.xml
|
||||||
|
[java.base needed its own import]
|
||||||
|
|
||||||
|
$ javac broken/SqlDoesNotGiveBase.java (25.0.4.1+1-LTS)
|
||||||
|
broken/SqlDoesNotGiveBase.java:5: error: cannot find symbol
|
||||||
|
List<String> l = List.of("x"); // java.util.List lives in java.base
|
||||||
|
^
|
||||||
|
symbol: class List
|
||||||
|
location: class SqlDoesNotGiveBase
|
||||||
|
broken/SqlDoesNotGiveBase.java:5: error: cannot find symbol
|
||||||
|
List<String> l = List.of("x"); // java.util.List lives in java.base
|
||||||
|
^
|
||||||
|
symbol: variable List
|
||||||
|
location: class SqlDoesNotGiveBase
|
||||||
|
2 errors
|
||||||
|
|
||||||
|
$ javac broken/SqlDoesNotGiveBase.java (27+35)
|
||||||
|
broken/SqlDoesNotGiveBase.java:5: error: cannot find symbol
|
||||||
|
List<String> l = List.of("x"); // java.util.List lives in java.base
|
||||||
|
^
|
||||||
|
symbol: class List
|
||||||
|
location: class SqlDoesNotGiveBase
|
||||||
|
broken/SqlDoesNotGiveBase.java:5: error: cannot find symbol
|
||||||
|
List<String> l = List.of("x"); // java.util.List lives in java.base
|
||||||
|
^
|
||||||
|
symbol: variable List
|
||||||
|
location: class SqlDoesNotGiveBase
|
||||||
|
2 errors
|
||||||
|
|
||||||
|
$ javac src/JavaSe.java (25.0.4.1+1-LTS)
|
||||||
|
src/JavaSe.java:1: error: unnamed module does not read: java.se
|
||||||
|
import module java.se;
|
||||||
|
^
|
||||||
|
1 error
|
||||||
|
|
||||||
|
$ javac src/JavaSe.java (27+35)
|
||||||
|
src/JavaSe.java:1: error: unnamed module does not read: java.se
|
||||||
|
import module java.se;
|
||||||
|
^
|
||||||
|
1 error
|
||||||
|
|
||||||
|
$ javac --add-modules java.se src/JavaSe.java && java JavaSe (25.0.4.1+1-LTS)
|
||||||
|
[a] x 0
|
||||||
|
|
||||||
|
$ javac --add-modules java.se src/JavaSe.java && java JavaSe (27+35)
|
||||||
|
[a] x 0
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
$ javac src/InPackage.java && java demo.mod.InPackage (25.0.4.1+1-LTS)
|
||||||
|
[1, 2, 3] from package demo.mod
|
||||||
|
|
||||||
|
$ javac src/InPackage.java && java demo.mod.InPackage (27+35)
|
||||||
|
[1, 2, 3] from package demo.mod
|
||||||
|
|
||||||
|
$ javac -d out --module-source-path multi --module acme.text,demo.app && java -p out -m demo.app/demo.App (25.0.4.1+1-LTS)
|
||||||
|
import-module-hello-world
|
||||||
|
|
||||||
|
$ javac -d out --module-source-path multi --module acme.text,demo.bad (25.0.4.1+1-LTS)
|
||||||
|
multi/demo.bad/demo/Bad.java:7: error: cannot find symbol
|
||||||
|
System.out.println(Helper.clean("x")); // Helper is in the non-exported acme.text.internal
|
||||||
|
^
|
||||||
|
symbol: variable Helper
|
||||||
|
location: class Bad
|
||||||
|
1 error
|
||||||
|
|
||||||
|
$ javac -d out --module-source-path multi --module demo.noreq (25.0.4.1+1-LTS)
|
||||||
|
multi/demo.noreq/demo/NoReq.java:3: error: module demo.noreq does not read: java.sql
|
||||||
|
import module java.sql; // demo.noreq never says "requires java.sql"
|
||||||
|
^
|
||||||
|
1 error
|
||||||
|
|
||||||
|
$ javac -d out --module-source-path multi --module acme.text,demo.app && java -p out -m demo.app/demo.App (27+35)
|
||||||
|
import-module-hello-world
|
||||||
|
|
||||||
|
$ javac -d out --module-source-path multi --module acme.text,demo.bad (27+35)
|
||||||
|
multi/demo.bad/demo/Bad.java:7: error: cannot find symbol
|
||||||
|
System.out.println(Helper.clean("x")); // Helper is in the non-exported acme.text.internal
|
||||||
|
^
|
||||||
|
symbol: variable Helper
|
||||||
|
location: class Bad
|
||||||
|
1 error
|
||||||
|
|
||||||
|
$ javac -d out --module-source-path multi --module demo.noreq (27+35)
|
||||||
|
multi/demo.noreq/demo/NoReq.java:3: error: module demo.noreq does not read: java.sql
|
||||||
|
import module java.sql; // demo.noreq never says "requires java.sql"
|
||||||
|
^
|
||||||
|
1 error
|
||||||
|
|
||||||
|
$ javac -p lib src/ThirdParty.java (25.0.4.1+1-LTS)
|
||||||
|
src/ThirdParty.java:1: error: unnamed module does not read: acme.text
|
||||||
|
import module acme.text;
|
||||||
|
^
|
||||||
|
1 error
|
||||||
|
|
||||||
|
$ javac -p lib --add-modules acme.text src/ThirdParty.java && java -p lib --add-modules acme.text -cp out ThirdParty (25.0.4.1+1-LTS)
|
||||||
|
a-script-using-a-library-module
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
$ javac src/CompactNoImport.java && java CompactNoImport (25.0.4.1+1-LTS)
|
||||||
|
[no imports at all]
|
||||||
|
|
||||||
|
$ javac src/CompactNoImport.java && java CompactNoImport (27+35)
|
||||||
|
[no imports at all]
|
||||||
|
|
||||||
|
$ javac src/CompactPlusImport.java && java CompactPlusImport (25.0.4.1+1-LTS)
|
||||||
|
[java.base came implicitly] 0
|
||||||
|
|
||||||
|
$ javac src/CompactPlusImport.java && java CompactPlusImport (27+35)
|
||||||
|
[java.base came implicitly] 0
|
||||||
|
|
||||||
|
$ javac src/CompactAmbiguous.java (25.0.4.1+1-LTS)
|
||||||
|
src/CompactAmbiguous.java:4: error: reference to Date is ambiguous
|
||||||
|
Date d = new Date(0L); // implicit java.base (java.util.Date) vs explicit java.sql (java.sql.Date)
|
||||||
|
^
|
||||||
|
both class java.sql.Date in java.sql and class java.util.Date in java.util match
|
||||||
|
src/CompactAmbiguous.java:4: error: reference to Date is ambiguous
|
||||||
|
Date d = new Date(0L); // implicit java.base (java.util.Date) vs explicit java.sql (java.sql.Date)
|
||||||
|
^
|
||||||
|
both class java.sql.Date in java.sql and class java.util.Date in java.util match
|
||||||
|
2 errors
|
||||||
|
|
||||||
|
$ javac src/CompactAmbiguous.java (27+35)
|
||||||
|
src/CompactAmbiguous.java:4: error: reference to Date is ambiguous
|
||||||
|
Date d = new Date(0L); // implicit java.base (java.util.Date) vs explicit java.sql (java.sql.Date)
|
||||||
|
^
|
||||||
|
both class java.sql.Date in java.sql and class java.util.Date in java.util match
|
||||||
|
src/CompactAmbiguous.java:4: error: reference to Date is ambiguous
|
||||||
|
Date d = new Date(0L); // implicit java.base (java.util.Date) vs explicit java.sql (java.sql.Date)
|
||||||
|
^
|
||||||
|
both class java.sql.Date in java.sql and class java.util.Date in java.util match
|
||||||
|
2 errors
|
||||||
|
|
||||||
|
$ javac broken/ClassicNoImport.java (25.0.4.1+1-LTS)
|
||||||
|
broken/ClassicNoImport.java:3: error: cannot find symbol
|
||||||
|
System.out.println(List.of("no imports at all"));
|
||||||
|
^
|
||||||
|
symbol: variable List
|
||||||
|
location: class ClassicNoImport
|
||||||
|
1 error
|
||||||
|
|
||||||
|
$ javac broken/ClassicNoImport.java (27+35)
|
||||||
|
broken/ClassicNoImport.java:3: error: cannot find symbol
|
||||||
|
System.out.println(List.of("no imports at all"));
|
||||||
|
^
|
||||||
|
symbol: variable List
|
||||||
|
location: class ClassicNoImport
|
||||||
|
1 error
|
||||||
|
|
||||||
|
$ java src/Script.java (25.0.4.1+1-LTS)
|
||||||
|
a-b-c 2026-09-15
|
||||||
|
|
||||||
|
$ java src/Script.java (27+35)
|
||||||
|
a-b-c 2026-09-15
|
||||||
|
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
$ diff <(javap -c -p OldWay) <(javap -c -p NewWay) (25.0.4.1+1-LTS, class name normalised to X)
|
||||||
|
no differences (57 lines of javap output each)
|
||||||
|
|
||||||
|
$ cmp OldWay.class NewWay.class (javac -g:none; NewWay renamed to OldWay inside the class file)
|
||||||
|
identical, byte for byte (2249 bytes)
|
||||||
|
|
||||||
|
$ diff <(javap -c -p -l OldWay) <(javap -c -p -l NewWay) (default javac -g keeps line numbers)
|
||||||
|
8c8
|
||||||
|
< line 9: 0
|
||||||
|
---
|
||||||
|
> line 3: 0
|
||||||
|
52,57c52,57
|
||||||
|
< line 11: 0
|
||||||
|
< line 12: 14
|
||||||
|
< line 13: 25
|
||||||
|
< line 14: 37
|
||||||
|
< line 15: 44
|
||||||
|
< line 16: 90
|
||||||
|
---
|
||||||
|
> line 5: 0
|
||||||
|
> line 6: 14
|
||||||
|
> line 7: 25
|
||||||
|
> line 8: 37
|
||||||
|
> line 9: 44
|
||||||
|
> line 10: 90
|
||||||
|
67c67
|
||||||
|
< line 13: 0
|
||||||
|
---
|
||||||
|
> line 7: 0
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
$ javac broken/NoSuchModule.java (25.0.4.1+1-LTS)
|
||||||
|
broken/NoSuchModule.java:1: error: imported module not found: java.nosuch
|
||||||
|
import module java.nosuch;
|
||||||
|
^
|
||||||
|
1 error
|
||||||
|
|
||||||
|
$ javac broken/NoSuchModule.java (27+35)
|
||||||
|
broken/NoSuchModule.java:1: error: imported module not found: java.nosuch
|
||||||
|
import module java.nosuch;
|
||||||
|
^
|
||||||
|
1 error
|
||||||
|
|
||||||
|
$ javac broken/ModuleInsideClass.java (25.0.4.1+1-LTS)
|
||||||
|
broken/ModuleInsideClass.java:2: error: illegal start of type
|
||||||
|
import module java.base;
|
||||||
|
^
|
||||||
|
broken/ModuleInsideClass.java:2: error: ';' expected
|
||||||
|
import module java.base;
|
||||||
|
^
|
||||||
|
broken/ModuleInsideClass.java:2: error: <identifier> expected
|
||||||
|
import module java.base;
|
||||||
|
^
|
||||||
|
3 errors
|
||||||
|
|
||||||
|
$ javac broken/ModuleInsideClass.java (27+35)
|
||||||
|
broken/ModuleInsideClass.java:2: error: illegal start of type
|
||||||
|
import module java.base;
|
||||||
|
^
|
||||||
|
broken/ModuleInsideClass.java:2: error: ';' expected
|
||||||
|
import module java.base;
|
||||||
|
^
|
||||||
|
broken/ModuleInsideClass.java:2: error: <identifier> expected
|
||||||
|
import module java.base;
|
||||||
|
^
|
||||||
|
3 errors
|
||||||
|
|
||||||
|
$ javac --release 21 src/NewWay.java (25.0.4.1+1-LTS)
|
||||||
|
src/NewWay.java:1: error: module imports are not supported in -source 21
|
||||||
|
import module java.base;
|
||||||
|
^
|
||||||
|
(use -source 25 or higher to enable module imports)
|
||||||
|
1 error
|
||||||
|
exit=1
|
||||||
|
|
||||||
|
$ javac --release 24 src/NewWay.java (25.0.4.1+1-LTS)
|
||||||
|
src/NewWay.java:1: error: module imports are not supported in -source 24
|
||||||
|
import module java.base;
|
||||||
|
^
|
||||||
|
(use -source 25 or higher to enable module imports)
|
||||||
|
1 error
|
||||||
|
exit=1
|
||||||
|
|
||||||
|
$ javac --release 25 src/NewWay.java (25.0.4.1+1-LTS)
|
||||||
|
exit=0
|
||||||
|
|
||||||
|
$ javac src/NewWay.java (21.0.10+7-Ubuntu-124.04)
|
||||||
|
src/NewWay.java:1: error: '.' expected
|
||||||
|
import module java.base;
|
||||||
|
^
|
||||||
|
1 error
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
$ jshell --feedback concise src/imports.jsh (21.0.10+7-Ubuntu-124.04)
|
||||||
|
import java.io.*
|
||||||
|
import java.math.*
|
||||||
|
import java.net.*
|
||||||
|
import java.nio.file.*
|
||||||
|
import java.util.*
|
||||||
|
import java.util.concurrent.*
|
||||||
|
import java.util.function.*
|
||||||
|
import java.util.prefs.*
|
||||||
|
import java.util.regex.*
|
||||||
|
import java.util.stream.*
|
||||||
|
[2, 4]
|
||||||
|
|
||||||
|
$ jshell --feedback concise src/imports.jsh (25.0.4.1+1-LTS)
|
||||||
|
import java.base
|
||||||
|
[2, 4]
|
||||||
|
|
||||||
|
$ jshell --feedback concise src/imports.jsh (27+35)
|
||||||
|
import java.base
|
||||||
|
[2, 4]
|
||||||
|
|
||||||
Executable
+88
@@ -0,0 +1,88 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# JEP 511 (Module Import Declarations): final in JDK 25, no --enable-preview.
|
||||||
|
# Regenerates every file in output/. JDK25=/path JDK27=/path [JDK21=/path] ./run.sh
|
||||||
|
set -uo pipefail
|
||||||
|
unset JAVA_TOOL_OPTIONS
|
||||||
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
JDK25="${JDK25:-/opt/jdks/jdk-25.0.4.1+1}"; JDK27="${JDK27:-/opt/jdks/jdk27}"; JDK21="${JDK21:-}"
|
||||||
|
OUT="$HERE/output"; mkdir -p "$OUT"; B="$(mktemp -d)"; trap 'rm -rf "$B"' EXIT
|
||||||
|
cd "$HERE"
|
||||||
|
# the build string of a JDK, e.g. 25.0.4.1+1-LTS
|
||||||
|
t() { "$1/bin/java" -version 2>&1 | sed -n 2p | sed 's/.*(build \(.*\))/\1/'; }
|
||||||
|
# compile SRC (extra javac args in $EXTRA) and run CLASS; header names the JDK
|
||||||
|
cr() { local jdk="$1" src="$2" cls="$3"; shift 3; local o; o="$(mktemp -d -p "$B")"
|
||||||
|
echo "\$ javac ${*:+$* }$src && java $cls ($(t "$jdk"))"
|
||||||
|
"$jdk/bin/javac" "$@" -d "$o" "$src" 2>&1 && "$jdk/bin/java" "$@" -cp "$o" "$cls" 2>&1; }
|
||||||
|
# compile only: shows the diagnostics
|
||||||
|
co() { local jdk="$1" src="$2"; shift 2; local o; o="$(mktemp -d -p "$B")"
|
||||||
|
echo "\$ javac ${*:+$* }$src ($(t "$jdk"))"
|
||||||
|
"$jdk/bin/javac" "$@" -d "$o" "$src" 2>&1 | grep -v '^Note:'; }
|
||||||
|
both() { local fn="$1"; shift; "$fn" "$JDK25" "$@"; echo; "$fn" "$JDK27" "$@"; }
|
||||||
|
|
||||||
|
# 01 - the same program with one import line instead of seven
|
||||||
|
{ both cr src/OldWay.java OldWay; echo; both cr src/NewWay.java NewWay; } > "$OUT/01-old-vs-new.txt"
|
||||||
|
|
||||||
|
# 02 - ambiguity: two modules export a class with the same simple name
|
||||||
|
{ both co broken/AmbiguousDate.java; echo; both co broken/AmbiguousList.java; echo; both cr src/FixedDate.java FixedDate; } > "$OUT/02-ambiguity.txt"
|
||||||
|
|
||||||
|
# 03 - precedence: who wins when several imports offer the same simple name
|
||||||
|
{ both cr src/StarImportWins.java StarImportWins; echo; both cr src/StarImportWinsSql.java StarImportWinsSql; echo
|
||||||
|
both cr src/SamePackageWins.java SamePackageWins; echo; both cr src/UnusedAmbiguity.java UnusedAmbiguity; echo
|
||||||
|
both cr src/DuplicateImports.java DuplicateImports; } > "$OUT/03-precedence.txt"
|
||||||
|
|
||||||
|
# 04 - what one module import actually brings in
|
||||||
|
{ echo "\$ java --describe-module java.sql ($(t "$JDK25"))"; "$JDK25/bin/java" --describe-module java.sql; echo
|
||||||
|
echo "\$ java --describe-module java.se | grep -c 'requires' ($(t "$JDK25"))"; "$JDK25/bin/java" --describe-module java.se | grep -c requires; echo
|
||||||
|
echo "\$ java --describe-module java.se | grep -c exports ($(t "$JDK25"))"; "$JDK25/bin/java" --describe-module java.se | grep -c exports; echo
|
||||||
|
both cr src/Transitive.java Transitive; echo; both co broken/SqlDoesNotGiveBase.java; echo
|
||||||
|
both co src/JavaSe.java; echo; both cr src/JavaSe.java JavaSe --add-modules java.se; } > "$OUT/04-what-a-module-import-brings-in.txt"
|
||||||
|
|
||||||
|
# 05 - packages, named modules and library modules
|
||||||
|
{ both cr src/InPackage.java demo.mod.InPackage; echo
|
||||||
|
for j in "$JDK25" "$JDK27"; do o="$(mktemp -d -p "$B")"
|
||||||
|
echo "\$ javac -d out --module-source-path multi --module acme.text,demo.app && java -p out -m demo.app/demo.App ($(t "$j"))"
|
||||||
|
"$j/bin/javac" -d "$o" --module-source-path multi --module acme.text,demo.app 2>&1 && "$j/bin/java" -p "$o" -m demo.app/demo.App 2>&1; echo
|
||||||
|
echo "\$ javac -d out --module-source-path multi --module acme.text,demo.bad ($(t "$j"))"
|
||||||
|
"$j/bin/javac" -d "$(mktemp -d -p "$B")" --module-source-path multi --module acme.text,demo.bad 2>&1; echo
|
||||||
|
echo "\$ javac -d out --module-source-path multi --module demo.noreq ($(t "$j"))"
|
||||||
|
"$j/bin/javac" -d "$(mktemp -d -p "$B")" --module-source-path multi --module demo.noreq 2>&1; echo
|
||||||
|
done
|
||||||
|
lib="$(mktemp -d -p "$B")"; "$JDK25/bin/javac" -d "$lib" --module-source-path multi --module acme.text 2>&1
|
||||||
|
c="$(mktemp -d -p "$B")"
|
||||||
|
echo "\$ javac -p lib src/ThirdParty.java ($(t "$JDK25"))"; "$JDK25/bin/javac" -p "$lib" -d "$c" src/ThirdParty.java 2>&1; echo
|
||||||
|
echo "\$ javac -p lib --add-modules acme.text src/ThirdParty.java && java -p lib --add-modules acme.text -cp out ThirdParty ($(t "$JDK25"))"
|
||||||
|
"$JDK25/bin/javac" -p "$lib" --add-modules acme.text -d "$c" src/ThirdParty.java 2>&1 && "$JDK25/bin/java" -p "$lib" --add-modules acme.text -cp "$c" ThirdParty 2>&1
|
||||||
|
} > "$OUT/05-packages-and-modules.txt"
|
||||||
|
|
||||||
|
# 06 - together with JEP 512 compact source files, and in the source launcher
|
||||||
|
{ both cr src/CompactNoImport.java CompactNoImport; echo; both cr src/CompactPlusImport.java CompactPlusImport; echo
|
||||||
|
both co src/CompactAmbiguous.java; echo; both co broken/ClassicNoImport.java; echo
|
||||||
|
for j in "$JDK25" "$JDK27"; do echo "\$ java src/Script.java ($(t "$j"))"; "$j/bin/java" src/Script.java 2>&1; echo; done
|
||||||
|
} > "$OUT/06-compact-files-and-scripts.txt"
|
||||||
|
|
||||||
|
# 07 - what the compiler emits: nothing different
|
||||||
|
{ o="$(mktemp -d -p "$B")"; "$JDK25/bin/javac" -d "$o" src/OldWay.java src/NewWay.java
|
||||||
|
for c in OldWay NewWay; do "$JDK25/bin/javap" -c -p -cp "$o" "$c" | sed "s/$c/X/g" | grep -v '^Compiled from' > "$o/$c.txt"; done
|
||||||
|
echo "\$ diff <(javap -c -p OldWay) <(javap -c -p NewWay) ($(t "$JDK25"), class name normalised to X)"
|
||||||
|
diff "$o/OldWay.txt" "$o/NewWay.txt" && echo "no differences ($(wc -l < "$o/OldWay.txt") lines of javap output each)"; echo
|
||||||
|
echo "\$ cmp OldWay.class NewWay.class (javac -g:none; NewWay renamed to OldWay inside the class file)"
|
||||||
|
"$JDK25/bin/javac" -g:none -d "$o/n" src/OldWay.java src/NewWay.java
|
||||||
|
LC_ALL=C sed 's/NewWay/OldWay/g' "$o/n/NewWay.class" > "$o/NewWay.renamed"
|
||||||
|
cmp "$o/n/OldWay.class" "$o/NewWay.renamed" && echo "identical, byte for byte ($(stat -c %s "$o/n/OldWay.class") bytes)"
|
||||||
|
for c in OldWay NewWay; do "$JDK25/bin/javap" -c -p -l -cp "$o" "$c" | sed "s/$c/X/g" | grep -v '^Compiled from' > "$o/$c.l"; done
|
||||||
|
echo; echo "\$ diff <(javap -c -p -l OldWay) <(javap -c -p -l NewWay) (default javac -g keeps line numbers)"
|
||||||
|
diff "$o/OldWay.l" "$o/NewWay.l"
|
||||||
|
} > "$OUT/07-bytecode-identical.txt"
|
||||||
|
|
||||||
|
# 08 - errors that are not about ambiguity, and the language level
|
||||||
|
{ both co broken/NoSuchModule.java; echo; both co broken/ModuleInsideClass.java; echo
|
||||||
|
for r in 21 24 25; do echo "\$ javac --release $r src/NewWay.java ($(t "$JDK25"))"
|
||||||
|
"$JDK25/bin/javac" --release $r -d "$(mktemp -d -p "$B")" src/NewWay.java 2>&1; echo "exit=$?"; echo; done
|
||||||
|
[ -n "$JDK21" ] && co "$JDK21" src/NewWay.java; } > "$OUT/08-errors-and-release-levels.txt"
|
||||||
|
|
||||||
|
# 09 - jshell: what it imports for you
|
||||||
|
{ for j in ${JDK21:+"$JDK21"} "$JDK25" "$JDK27"; do
|
||||||
|
"$j/bin/jshell" --feedback concise src/imports.jsh >/dev/null 2>&1 # first run creates the prefs dir and logs about it
|
||||||
|
echo "\$ jshell --feedback concise src/imports.jsh ($(t "$j"))"; "$j/bin/jshell" --feedback concise src/imports.jsh 2>&1; echo; done
|
||||||
|
} > "$OUT/09-jshell.txt"
|
||||||
|
echo "wrote $OUT"
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import module java.sql;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
Date d = new Date(0L); // implicit java.base (java.util.Date) vs explicit java.sql (java.sql.Date)
|
||||||
|
IO.println(d.getClass().getName());
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
void main() {
|
||||||
|
IO.println(List.of("no imports at all"));
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import module java.sql;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
var l = List.of("java.base came implicitly"); // no import module java.base written
|
||||||
|
IO.println(l + " " + Connection.TRANSACTION_NONE); // java.sql from the explicit import
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import module java.base;
|
||||||
|
import module java.base;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class DuplicateImports {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
System.out.println(List.of("dups are fine"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import module java.base;
|
||||||
|
import module java.sql;
|
||||||
|
import java.util.Date; // a single-type import wins over both module imports
|
||||||
|
|
||||||
|
public class FixedDate {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
Date d = new Date(0L);
|
||||||
|
System.out.println(d.getClass().getName());
|
||||||
|
java.sql.Date s = new java.sql.Date(0L); // the other one is still reachable by its full name
|
||||||
|
System.out.println(s.getClass().getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package demo.mod;
|
||||||
|
|
||||||
|
import module java.base;
|
||||||
|
|
||||||
|
public class InPackage {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
System.out.println(List.of(1, 2, 3) + " from package " + InPackage.class.getPackageName());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import module java.se;
|
||||||
|
import java.util.List; // java.se brings in java.desktop, whose java.awt.List clashes with java.util.List
|
||||||
|
|
||||||
|
public class JavaSe {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
List<String> l = List.of("a"); // java.base
|
||||||
|
Logger lg = Logger.getLogger("x"); // java.logging
|
||||||
|
System.out.println(l + " " + lg.getName() + " " + Connection.TRANSACTION_NONE); // java.sql
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import module java.base;
|
||||||
|
|
||||||
|
public class NewWay {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
List<String> words = List.of("apple", "avocado", "banana", "blueberry", "cherry");
|
||||||
|
Map<Character, List<String>> byInitial = words.stream()
|
||||||
|
.collect(Collectors.groupingBy(w -> w.charAt(0)));
|
||||||
|
System.out.println(byInitial);
|
||||||
|
System.out.println(Path.of("a", "b", "c.txt") + " on " + LocalDate.of(2026, 9, 15));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import java.nio.file.Path;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public class OldWay {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
List<String> words = List.of("apple", "avocado", "banana", "blueberry", "cherry");
|
||||||
|
Map<Character, List<String>> byInitial = words.stream()
|
||||||
|
.collect(Collectors.groupingBy(w -> w.charAt(0)));
|
||||||
|
System.out.println(byInitial);
|
||||||
|
System.out.println(Path.of("a", "b", "c.txt") + " on " + LocalDate.of(2026, 9, 15));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import module java.base;
|
||||||
|
|
||||||
|
class Date { // a class in this file's own package
|
||||||
|
public String toString() { return "my own Date"; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SamePackageWins {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
System.out.println(new Date()); // which Date?
|
||||||
|
System.out.println(new java.util.Date(0L).getClass().getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import module java.base;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
var now = LocalDate.of(2026, 9, 15);
|
||||||
|
IO.println(Stream.of("a", "b", "c").collect(Collectors.joining("-")) + " " + now);
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import java.util.*;
|
||||||
|
import module java.sql;
|
||||||
|
|
||||||
|
public class StarImportWins {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
Date d = new Date(0L);
|
||||||
|
System.out.println(d.getClass().getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import java.sql.*;
|
||||||
|
import module java.base;
|
||||||
|
|
||||||
|
public class StarImportWinsSql {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
Date d = new Date(0L);
|
||||||
|
System.out.println(d.getClass().getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import module acme.text;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
IO.println(Slug.of("A script using a library module"));
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import module java.base;
|
||||||
|
import module java.sql;
|
||||||
|
|
||||||
|
public class Transitive {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
// java.sql declares "requires transitive" java.logging, java.xml and java.transaction.xa,
|
||||||
|
// so importing java.sql also imports their exported packages.
|
||||||
|
Logger log = Logger.getLogger("demo"); // java.util.logging (java.logging)
|
||||||
|
System.out.println(log.getName());
|
||||||
|
System.out.println(XAResource.TMNOFLAGS); // javax.transaction.xa (java.transaction.xa)
|
||||||
|
System.out.println(DocumentBuilderFactory.class.getModule().getName()); // javax.xml.parsers (java.xml)
|
||||||
|
List<String> l = List.of("java.base needed its own import");
|
||||||
|
System.out.println(l);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import module java.base;
|
||||||
|
import module java.sql;
|
||||||
|
|
||||||
|
public class UnusedAmbiguity {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
// Date is ambiguous between the two modules, but it is never used here, so this compiles.
|
||||||
|
System.out.println("no Date mentioned, no error");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
/imports
|
||||||
|
System.out.println(List.of(1, 2).stream().map(x -> x * 2).collect(Collectors.toList()))
|
||||||
|
/exit
|
||||||
Reference in New Issue
Block a user