diff --git a/README.md b/README.md index ef8a566..332fb30 100644 --- a/README.md +++ b/README.md @@ -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 | | [`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/`) diff --git a/jep511/README.md b/jep511/README.md new file mode 100644 index 0000000..66644d2 --- /dev/null +++ b/jep511/README.md @@ -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. diff --git a/jep511/broken/AmbiguousDate.java b/jep511/broken/AmbiguousDate.java new file mode 100644 index 0000000..19cf25c --- /dev/null +++ b/jep511/broken/AmbiguousDate.java @@ -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); + } +} diff --git a/jep511/broken/AmbiguousList.java b/jep511/broken/AmbiguousList.java new file mode 100644 index 0000000..e3f62da --- /dev/null +++ b/jep511/broken/AmbiguousList.java @@ -0,0 +1,10 @@ +import module java.base; +import module java.desktop; + +public class AmbiguousList { + public static void main(String[] args) { + List names = new ArrayList<>(); + names.add("x"); + System.out.println(names); + } +} diff --git a/jep511/broken/ClassicNoImport.java b/jep511/broken/ClassicNoImport.java new file mode 100644 index 0000000..6ab0994 --- /dev/null +++ b/jep511/broken/ClassicNoImport.java @@ -0,0 +1,5 @@ +public class ClassicNoImport { + public static void main(String[] args) { + System.out.println(List.of("no imports at all")); + } +} diff --git a/jep511/broken/ModuleInsideClass.java b/jep511/broken/ModuleInsideClass.java new file mode 100644 index 0000000..b8b4eb4 --- /dev/null +++ b/jep511/broken/ModuleInsideClass.java @@ -0,0 +1,3 @@ +public class ModuleInsideClass { + import module java.base; +} diff --git a/jep511/broken/NoSuchModule.java b/jep511/broken/NoSuchModule.java new file mode 100644 index 0000000..0d3591e --- /dev/null +++ b/jep511/broken/NoSuchModule.java @@ -0,0 +1,5 @@ +import module java.nosuch; + +public class NoSuchModule { + public static void main(String[] args) { } +} diff --git a/jep511/broken/SqlDoesNotGiveBase.java b/jep511/broken/SqlDoesNotGiveBase.java new file mode 100644 index 0000000..e86575a --- /dev/null +++ b/jep511/broken/SqlDoesNotGiveBase.java @@ -0,0 +1,8 @@ +import module java.sql; + +public class SqlDoesNotGiveBase { + public static void main(String[] args) { + List l = List.of("x"); // java.util.List lives in java.base + System.out.println(l); + } +} diff --git a/jep511/multi/acme.text/acme/text/Slug.java b/jep511/multi/acme.text/acme/text/Slug.java new file mode 100644 index 0000000..1742527 --- /dev/null +++ b/jep511/multi/acme.text/acme/text/Slug.java @@ -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); } +} diff --git a/jep511/multi/acme.text/acme/text/internal/Helper.java b/jep511/multi/acme.text/acme/text/internal/Helper.java new file mode 100644 index 0000000..428ed3f --- /dev/null +++ b/jep511/multi/acme.text/acme/text/internal/Helper.java @@ -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("^-|-$", ""); } +} diff --git a/jep511/multi/acme.text/module-info.java b/jep511/multi/acme.text/module-info.java new file mode 100644 index 0000000..1b7f4b5 --- /dev/null +++ b/jep511/multi/acme.text/module-info.java @@ -0,0 +1,3 @@ +module acme.text { + exports acme.text; // acme.text.internal is NOT exported +} diff --git a/jep511/multi/demo.app/demo/App.java b/jep511/multi/demo.app/demo/App.java new file mode 100644 index 0000000..e2fd102 --- /dev/null +++ b/jep511/multi/demo.app/demo/App.java @@ -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!")); + } +} diff --git a/jep511/multi/demo.app/module-info.java b/jep511/multi/demo.app/module-info.java new file mode 100644 index 0000000..c1c5aa8 --- /dev/null +++ b/jep511/multi/demo.app/module-info.java @@ -0,0 +1,3 @@ +module demo.app { + requires acme.text; +} diff --git a/jep511/multi/demo.bad/demo/Bad.java b/jep511/multi/demo.bad/demo/Bad.java new file mode 100644 index 0000000..f2b51fb --- /dev/null +++ b/jep511/multi/demo.bad/demo/Bad.java @@ -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 + } +} diff --git a/jep511/multi/demo.bad/module-info.java b/jep511/multi/demo.bad/module-info.java new file mode 100644 index 0000000..bcb6be1 --- /dev/null +++ b/jep511/multi/demo.bad/module-info.java @@ -0,0 +1,3 @@ +module demo.bad { + requires acme.text; +} diff --git a/jep511/multi/demo.noreq/demo/NoReq.java b/jep511/multi/demo.noreq/demo/NoReq.java new file mode 100644 index 0000000..98b889d --- /dev/null +++ b/jep511/multi/demo.noreq/demo/NoReq.java @@ -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); + } +} diff --git a/jep511/multi/demo.noreq/module-info.java b/jep511/multi/demo.noreq/module-info.java new file mode 100644 index 0000000..1e617d7 --- /dev/null +++ b/jep511/multi/demo.noreq/module-info.java @@ -0,0 +1 @@ +module demo.noreq { } diff --git a/jep511/output/01-old-vs-new.txt b/jep511/output/01-old-vs-new.txt new file mode 100644 index 0000000..c1f18f5 --- /dev/null +++ b/jep511/output/01-old-vs-new.txt @@ -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 diff --git a/jep511/output/02-ambiguity.txt b/jep511/output/02-ambiguity.txt new file mode 100644 index 0000000..e1886c5 --- /dev/null +++ b/jep511/output/02-ambiguity.txt @@ -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 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 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 diff --git a/jep511/output/03-precedence.txt b/jep511/output/03-precedence.txt new file mode 100644 index 0000000..bbcde9b --- /dev/null +++ b/jep511/output/03-precedence.txt @@ -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] diff --git a/jep511/output/04-what-a-module-import-brings-in.txt b/jep511/output/04-what-a-module-import-brings-in.txt new file mode 100644 index 0000000..83db5c1 --- /dev/null +++ b/jep511/output/04-what-a-module-import-brings-in.txt @@ -0,0 +1,71 @@ +$ java --describe-module java.sql (25.0.4.1+1-LTS) +java.sql@25.0.4.1 +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 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 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 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 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 diff --git a/jep511/output/05-packages-and-modules.txt b/jep511/output/05-packages-and-modules.txt new file mode 100644 index 0000000..1314ad6 --- /dev/null +++ b/jep511/output/05-packages-and-modules.txt @@ -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 diff --git a/jep511/output/06-compact-files-and-scripts.txt b/jep511/output/06-compact-files-and-scripts.txt new file mode 100644 index 0000000..a7b1da2 --- /dev/null +++ b/jep511/output/06-compact-files-and-scripts.txt @@ -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 + diff --git a/jep511/output/07-bytecode-identical.txt b/jep511/output/07-bytecode-identical.txt new file mode 100644 index 0000000..b028f42 --- /dev/null +++ b/jep511/output/07-bytecode-identical.txt @@ -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 diff --git a/jep511/output/08-errors-and-release-levels.txt b/jep511/output/08-errors-and-release-levels.txt new file mode 100644 index 0000000..ef9d7d3 --- /dev/null +++ b/jep511/output/08-errors-and-release-levels.txt @@ -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: 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: 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 diff --git a/jep511/output/09-jshell.txt b/jep511/output/09-jshell.txt new file mode 100644 index 0000000..c8ebd25 --- /dev/null +++ b/jep511/output/09-jshell.txt @@ -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] + diff --git a/jep511/run.sh b/jep511/run.sh new file mode 100755 index 0000000..ad75300 --- /dev/null +++ b/jep511/run.sh @@ -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" diff --git a/jep511/src/CompactAmbiguous.java b/jep511/src/CompactAmbiguous.java new file mode 100644 index 0000000..d5b09df --- /dev/null +++ b/jep511/src/CompactAmbiguous.java @@ -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()); +} diff --git a/jep511/src/CompactNoImport.java b/jep511/src/CompactNoImport.java new file mode 100644 index 0000000..10a1ff6 --- /dev/null +++ b/jep511/src/CompactNoImport.java @@ -0,0 +1,3 @@ +void main() { + IO.println(List.of("no imports at all")); +} diff --git a/jep511/src/CompactPlusImport.java b/jep511/src/CompactPlusImport.java new file mode 100644 index 0000000..c27ea1c --- /dev/null +++ b/jep511/src/CompactPlusImport.java @@ -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 +} diff --git a/jep511/src/DuplicateImports.java b/jep511/src/DuplicateImports.java new file mode 100644 index 0000000..b24ea62 --- /dev/null +++ b/jep511/src/DuplicateImports.java @@ -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")); + } +} diff --git a/jep511/src/FixedDate.java b/jep511/src/FixedDate.java new file mode 100644 index 0000000..718c220 --- /dev/null +++ b/jep511/src/FixedDate.java @@ -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()); + } +} diff --git a/jep511/src/InPackage.java b/jep511/src/InPackage.java new file mode 100644 index 0000000..a098f7c --- /dev/null +++ b/jep511/src/InPackage.java @@ -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()); + } +} diff --git a/jep511/src/JavaSe.java b/jep511/src/JavaSe.java new file mode 100644 index 0000000..69398ba --- /dev/null +++ b/jep511/src/JavaSe.java @@ -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 l = List.of("a"); // java.base + Logger lg = Logger.getLogger("x"); // java.logging + System.out.println(l + " " + lg.getName() + " " + Connection.TRANSACTION_NONE); // java.sql + } +} diff --git a/jep511/src/NewWay.java b/jep511/src/NewWay.java new file mode 100644 index 0000000..6321a07 --- /dev/null +++ b/jep511/src/NewWay.java @@ -0,0 +1,11 @@ +import module java.base; + +public class NewWay { + public static void main(String[] args) { + List words = List.of("apple", "avocado", "banana", "blueberry", "cherry"); + Map> 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)); + } +} diff --git a/jep511/src/OldWay.java b/jep511/src/OldWay.java new file mode 100644 index 0000000..0a827f3 --- /dev/null +++ b/jep511/src/OldWay.java @@ -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 words = List.of("apple", "avocado", "banana", "blueberry", "cherry"); + Map> 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)); + } +} diff --git a/jep511/src/SamePackageWins.java b/jep511/src/SamePackageWins.java new file mode 100644 index 0000000..85f07ea --- /dev/null +++ b/jep511/src/SamePackageWins.java @@ -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()); + } +} diff --git a/jep511/src/Script.java b/jep511/src/Script.java new file mode 100644 index 0000000..8fb44ab --- /dev/null +++ b/jep511/src/Script.java @@ -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); +} diff --git a/jep511/src/StarImportWins.java b/jep511/src/StarImportWins.java new file mode 100644 index 0000000..b3d6bed --- /dev/null +++ b/jep511/src/StarImportWins.java @@ -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()); + } +} diff --git a/jep511/src/StarImportWinsSql.java b/jep511/src/StarImportWinsSql.java new file mode 100644 index 0000000..0088149 --- /dev/null +++ b/jep511/src/StarImportWinsSql.java @@ -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()); + } +} diff --git a/jep511/src/ThirdParty.java b/jep511/src/ThirdParty.java new file mode 100644 index 0000000..72a703e --- /dev/null +++ b/jep511/src/ThirdParty.java @@ -0,0 +1,5 @@ +import module acme.text; + +void main() { + IO.println(Slug.of("A script using a library module")); +} diff --git a/jep511/src/Transitive.java b/jep511/src/Transitive.java new file mode 100644 index 0000000..e94f85f --- /dev/null +++ b/jep511/src/Transitive.java @@ -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 l = List.of("java.base needed its own import"); + System.out.println(l); + } +} diff --git a/jep511/src/UnusedAmbiguity.java b/jep511/src/UnusedAmbiguity.java new file mode 100644 index 0000000..dde32f4 --- /dev/null +++ b/jep511/src/UnusedAmbiguity.java @@ -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"); + } +} diff --git a/jep511/src/imports.jsh b/jep511/src/imports.jsh new file mode 100644 index 0000000..3da7fb5 --- /dev/null +++ b/jep511/src/imports.jsh @@ -0,0 +1,3 @@ +/imports +System.out.println(List.of(1, 2).stream().map(x -> x * 2).collect(Collectors.toList())) +/exit