From 0a60a6857036a185072795cb385ea7493d7a72be Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Thu, 30 Apr 2026 20:04:41 +0300 Subject: [PATCH 1/2] Fail fast when running on JDK < 11 and document JDK 11-25 as supported runtime The simulator and "Run as desktop app" goals fork the JVM with --add-exports=java.desktop/com.apple.eawt=ALL-UNNAMED and similar options, which JDK 8 rejects with "Could not create the Java Virtual Machine". The desktop run also surfaced as the opaque "An exception occured while executing the Java class ...Stub" from exec-maven-plugin. Add a runtime JDK check in JavaVersionUtil and call it from PrepareSimulatorClasspathMojo and GenerateDesktopAppWrapperMojo so the user gets a clear, actionable MojoFailureException naming the detected version, JAVA_HOME, and a pointer to Adoptium before anything is forked. Update README.md, BUILDING.md, CLAUDE.md, and the developer guide (Index.asciidoc, Maven-Getting-Started.adoc, Working-With-CodenameOne-Sources) to call out JDK 11 through 25 as the supported runtime range while keeping JDK 8 as the build-time requirement for the core framework. Co-Authored-By: Claude Opus 4.7 (1M context) --- BUILDING.md | 3 +- CLAUDE.md | 9 ++-- README.md | 4 +- docs/developer-guide/Index.asciidoc | 2 +- .../Maven-Getting-Started.adoc | 19 ++++++++ .../Working-With-CodenameOne-Sources.asciidoc | 4 +- .../maven/GenerateDesktopAppWrapperMojo.java | 2 + .../com/codename1/maven/JavaVersionUtil.java | 47 +++++++++++++++++++ .../maven/PrepareSimulatorClasspathMojo.java | 2 + 9 files changed, 85 insertions(+), 7 deletions(-) diff --git a/BUILDING.md b/BUILDING.md index 7e13a0714b..d62623f736 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -4,7 +4,8 @@ This guide explains how to build Codename One from source using Maven. It provid ## Prerequisites -- **JDK 8** +- **JDK 8** for compiling the core framework (the core modules use `-source 1.5 -target 1.5` for backward compatibility) +- **JDK 11 through 25** for *running* the simulator and the "Run as desktop app" target - **JDK 17** for building the Android port - **Apache Maven 3.6+** - macOS with Xcode (required only for the iOS port) diff --git a/CLAUDE.md b/CLAUDE.md index 2c2f4690b8..18c665e795 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -20,7 +20,8 @@ The project is transitioning from Ant to Maven. **Maven is the preferred build s ### Building from Source **Requirements:** -- JDK 8 (required for core build) +- JDK 8 (required for the core framework build) +- JDK 11 through 25 (required at *runtime* for the simulator and "Run as desktop app") - JDK 17 (required for Android port) - Apache Maven 3.6+ - macOS with Xcode (for iOS port only) @@ -166,7 +167,8 @@ To use locally-built version, edit the generated `pom.xml`: - **Tooling/Plugins**: Can use Java 8+ - **Tests**: Can use Java 11+ - **Android build**: Requires JDK 17 in JAVA17_HOME -- **Main JAVA_HOME**: Must be JDK 8 +- **Main JAVA_HOME (for building the framework)**: Must be JDK 8 +- **Runtime JDK for simulator / desktop run**: JDK 11 through 25 is supported. The Codename One Maven plugin checks this on entry to `prepare-simulator-classpath` and `generate-desktop-app-wrapper` and aborts with a friendly error when an older JDK is in use. ### Working with Native Code @@ -251,7 +253,8 @@ The JavaSE port serves as the simulator with: ### Common Issues -- **JDK version mismatch**: Ensure JAVA_HOME is JDK 8, JAVA17_HOME is JDK 17 +- **JDK version mismatch**: Ensure JAVA_HOME is JDK 8 for building the framework, JAVA17_HOME is JDK 17 for the Android port +- **`Unrecognized option: --add-exports=...`** when running the simulator or desktop app: the project is being executed on a JDK older than 11. Switch to JDK 11 through 25 (Eclipse Temurin from ) and re-run. The Codename One Maven plugin now detects this and prints a friendly error before the JVM is forked. - **Missing cn1-binaries**: Run `setup-workspace.sh` or manually clone to `../cn1-binaries` - **Build client missing**: Copy `maven/CodeNameOneBuildClient.jar` to `~/.codenameone/` - **macOS ARM JDK8**: Setup script downloads x64 version (works via Rosetta) diff --git a/README.md b/README.md index 6d31deb4e4..ccecde3525 100644 --- a/README.md +++ b/README.md @@ -177,7 +177,9 @@ The setup is covered in depth in [this article and video](https://www.codenameon " img width="80%"> -**IMPORTANT:** Building Codename One requires **JDK 8**, currently. You cannot use JDK 11 as some sub-modules must use `-source 1.5` and `-target 1.5` to maintain backward compatibility with parts of the toolchain. +**IMPORTANT:** *Building* the Codename One framework from source requires **JDK 8** -- some sub-modules must use `-source 1.5` and `-target 1.5` to maintain backward compatibility with parts of the toolchain, and newer JDKs cannot emit those targets. + +*Running* a Codename One application (the simulator or the "Run as desktop app" target) supports **JDK 11 through 25** (Eclipse Temurin: ). ### Quick Start with Maven diff --git a/docs/developer-guide/Index.asciidoc b/docs/developer-guide/Index.asciidoc index 3a530f23b7..2221d5bd52 100644 --- a/docs/developer-guide/Index.asciidoc +++ b/docs/developer-guide/Index.asciidoc @@ -214,7 +214,7 @@ This means that in runtime a user might revoke a permission. A good example for === Installing Codename One -IMPORTANT: Codename One requires either JDK 11 or JDK 8. Other JDK versions aren't supported now. +IMPORTANT: Codename One supports JDK 11 through 25 for running the simulator and the desktop ("Run as desktop app") target. Eclipse Temurin (https://adoptium.net) is the easiest way to install a supported JDK. Codename One projects are built with Maven. Typical Maven targets such as `package`, `clean` and `install` work out of the box, but the Codename One integrations that ship with each IDE provide dedicated Run and Build actions for a smoother workflow. diff --git a/docs/developer-guide/Maven-Getting-Started.adoc b/docs/developer-guide/Maven-Getting-Started.adoc index 45747bc260..772ad2980f 100644 --- a/docs/developer-guide/Maven-Getting-Started.adoc +++ b/docs/developer-guide/Maven-Getting-Started.adoc @@ -1,5 +1,24 @@ == Getting started +[#prerequisites] +=== Prerequisites + +Codename One supports JDK 11 through 25 for running the simulator and the +"Run as desktop app" target. https://adoptium.net[Eclipse Temurin] is the +easiest source of a supported JDK on macOS, Windows, and Linux. + +After installing the JDK, point `JAVA_HOME` at it and confirm with: + +[source,bash] +---- +java -version +mvn -v +---- + +Both should report Java 11 or newer. The Codename One Maven plugin checks the +runtime JDK version before launching the simulator or the desktop wrapper, and +fails fast with a friendly message if it is older than 11. + [#creating-app-project] === Creating a new project diff --git a/docs/developer-guide/Working-With-CodenameOne-Sources.asciidoc b/docs/developer-guide/Working-With-CodenameOne-Sources.asciidoc index d5e9dc68cc..4af76abbaf 100644 --- a/docs/developer-guide/Working-With-CodenameOne-Sources.asciidoc +++ b/docs/developer-guide/Working-With-CodenameOne-Sources.asciidoc @@ -11,7 +11,9 @@ Codename One uses multiple JDKs and Maven profiles during the build. Make sure your development machine includes the following tooling (see `BUILDING.md` for platform-specific installation tips): -* JDK 8 (required for the core build and Maven invocations) +* JDK 8 (required for the core build of the framework itself) +* JDK 11 through 25 (required at *runtime* to launch the simulator or + "Run as desktop app" target) * JDK 17 (required when compiling the Android port) * Apache Maven 3.6 or newer * macOS with Xcode (if you plan to build or test the iOS port) diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/GenerateDesktopAppWrapperMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/GenerateDesktopAppWrapperMojo.java index a86539da83..2805152e96 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/GenerateDesktopAppWrapperMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/GenerateDesktopAppWrapperMojo.java @@ -22,6 +22,8 @@ public class GenerateDesktopAppWrapperMojo extends AbstractCN1Mojo { @Override protected void executeImpl() throws MojoExecutionException, MojoFailureException { + JavaVersionUtil.requireRuntimeJavaVersion(JavaVersionUtil.MIN_RUNTIME_JAVA_VERSION, + "run or build the JavaSE desktop app"); String iconPath = properties.getProperty("codename1.icon"); File iconFile = new File(iconPath); if (!iconFile.isAbsolute()) { diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/JavaVersionUtil.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/JavaVersionUtil.java index 13dd8bc5f1..3fdd006ecb 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/JavaVersionUtil.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/JavaVersionUtil.java @@ -3,9 +3,13 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.apache.maven.plugin.MojoFailureException; + final class JavaVersionUtil { private static final Pattern MAJOR_VERSION_PATTERN = Pattern.compile("^(?:1\\.)?(\\d+)"); + static final int MIN_RUNTIME_JAVA_VERSION = 11; + private JavaVersionUtil() { } @@ -27,4 +31,47 @@ static int parseJavaVersion(String version, int defaultValue) { return defaultValue; } } + + /** + * Returns the major Java version of the JVM running this code (e.g. 8, 11, 17). + * Falls back to {@code defaultValue} if the version cannot be parsed. + */ + static int getRuntimeMajorVersion(int defaultValue) { + String spec = System.getProperty("java.specification.version"); + int parsed = parseJavaVersion(spec, -1); + if (parsed > 0) { + return parsed; + } + return parseJavaVersion(System.getProperty("java.version"), defaultValue); + } + + /** + * Aborts the current Maven goal with a friendly, actionable message when the JVM + * Maven is running on is older than the supplied minimum. + * + * @param minimumMajorVersion smallest acceptable major version (e.g. 11) + * @param operationLabel short description of what the user was trying to do (used in the error message) + */ + static void requireRuntimeJavaVersion(int minimumMajorVersion, String operationLabel) throws MojoFailureException { + int current = getRuntimeMajorVersion(-1); + if (current >= minimumMajorVersion) { + return; + } + String detected = current > 0 + ? "Java " + current + : "an unknown Java version (java.version=" + System.getProperty("java.version") + ")"; + String javaHome = System.getProperty("java.home"); + StringBuilder msg = new StringBuilder(); + msg.append('\n'); + msg.append("Codename One supports JDK ").append(minimumMajorVersion).append(" through 25 to ") + .append(operationLabel).append(".\n"); + msg.append("Detected ").append(detected); + if (javaHome != null) { + msg.append(" at ").append(javaHome); + } + msg.append(".\n\n"); + msg.append("Install JDK ").append(minimumMajorVersion).append(" or newer (e.g. Eclipse Temurin\n") + .append("from https://adoptium.net), point JAVA_HOME at it, and re-run this goal.\n"); + throw new MojoFailureException(msg.toString()); + } } diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/PrepareSimulatorClasspathMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/PrepareSimulatorClasspathMojo.java index 2f23cfe7fe..a75940a912 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/PrepareSimulatorClasspathMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/PrepareSimulatorClasspathMojo.java @@ -70,6 +70,8 @@ private String prepareClasspath() { @Override protected void executeImpl() throws MojoExecutionException, MojoFailureException { + JavaVersionUtil.requireRuntimeJavaVersion(JavaVersionUtil.MIN_RUNTIME_JAVA_VERSION, + "run the Codename One simulator"); getLog().info("Preparing Simulator Classpath"); Properties props = project.getModel().getProperties(); if (props == null) { From df08e60c79094afe2d63aa08d3c2aea23fa23c5e Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Thu, 30 Apr 2026 20:21:34 +0300 Subject: [PATCH 2/2] Narrow JDK 11 runtime check to cn1:run / cn1:debug The previous commit gated generate-desktop-app-wrapper and prepare-simulator-classpath on JDK 11+, which broke the JDK 8 CI build: the executable-jar profile (used by build.sh jar / migrate-googlemapsdemo integration test) calls generate-desktop-app-wrapper to generate icons, and that mojo only emits PNGs and adjusts the source path -- it does not need JDK 11+. Likewise prepare-simulator-classpath runs in many build flows that legitimately work on JDK 8. Move the check to CN1RunMojo and CN1DebugMojo, which are the explicit "actually launch the simulator" entry points (mvn cn1:run / cn1:debug). Build-only goals stay unguarded. Co-Authored-By: Claude Opus 4.7 (1M context) --- CLAUDE.md | 2 +- docs/developer-guide/Maven-Getting-Started.adoc | 5 +++-- .../src/main/java/com/codename1/maven/CN1DebugMojo.java | 3 +++ .../src/main/java/com/codename1/maven/CN1RunMojo.java | 3 +++ .../com/codename1/maven/GenerateDesktopAppWrapperMojo.java | 2 -- .../com/codename1/maven/PrepareSimulatorClasspathMojo.java | 2 -- 6 files changed, 10 insertions(+), 7 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 18c665e795..781e8ece65 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -168,7 +168,7 @@ To use locally-built version, edit the generated `pom.xml`: - **Tests**: Can use Java 11+ - **Android build**: Requires JDK 17 in JAVA17_HOME - **Main JAVA_HOME (for building the framework)**: Must be JDK 8 -- **Runtime JDK for simulator / desktop run**: JDK 11 through 25 is supported. The Codename One Maven plugin checks this on entry to `prepare-simulator-classpath` and `generate-desktop-app-wrapper` and aborts with a friendly error when an older JDK is in use. +- **Runtime JDK for simulator / desktop run**: JDK 11 through 25 is supported. The Codename One Maven plugin checks this on entry to `cn1:run` and `cn1:debug` and aborts with a friendly error when an older JDK is in use. The build-time goals (`generate-desktop-app-wrapper`, `prepare-simulator-classpath`, the `executable-jar` profile) are not gated -- they still work on JDK 8 because they only generate icons / classpath metadata. ### Working with Native Code diff --git a/docs/developer-guide/Maven-Getting-Started.adoc b/docs/developer-guide/Maven-Getting-Started.adoc index 772ad2980f..acdf985307 100644 --- a/docs/developer-guide/Maven-Getting-Started.adoc +++ b/docs/developer-guide/Maven-Getting-Started.adoc @@ -16,8 +16,9 @@ mvn -v ---- Both should report Java 11 or newer. The Codename One Maven plugin checks the -runtime JDK version before launching the simulator or the desktop wrapper, and -fails fast with a friendly message if it is older than 11. +runtime JDK version when you invoke `mvn cn1:run` or `mvn cn1:debug` and fails +fast with a friendly message if it is older than 11. Build-only goals (such as +`-Pexecutable-jar`) still work on older JDKs. [#creating-app-project] === Creating a new project diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1DebugMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1DebugMojo.java index e148a8c0f8..b80f005d73 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1DebugMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1DebugMojo.java @@ -24,6 +24,9 @@ protected void executeImpl() throws MojoExecutionException, MojoFailureException return; } + JavaVersionUtil.requireRuntimeJavaVersion(JavaVersionUtil.MIN_RUNTIME_JAVA_VERSION, + "debug the Codename One simulator"); + InvocationRequest request = new DefaultInvocationRequest(); diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1RunMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1RunMojo.java index 05f0c0c021..769e534b75 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1RunMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1RunMojo.java @@ -25,6 +25,9 @@ protected void executeImpl() throws MojoExecutionException, MojoFailureException return; } + JavaVersionUtil.requireRuntimeJavaVersion(JavaVersionUtil.MIN_RUNTIME_JAVA_VERSION, + "run the Codename One simulator"); + InvocationRequest request = new DefaultInvocationRequest(); diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/GenerateDesktopAppWrapperMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/GenerateDesktopAppWrapperMojo.java index 2805152e96..a86539da83 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/GenerateDesktopAppWrapperMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/GenerateDesktopAppWrapperMojo.java @@ -22,8 +22,6 @@ public class GenerateDesktopAppWrapperMojo extends AbstractCN1Mojo { @Override protected void executeImpl() throws MojoExecutionException, MojoFailureException { - JavaVersionUtil.requireRuntimeJavaVersion(JavaVersionUtil.MIN_RUNTIME_JAVA_VERSION, - "run or build the JavaSE desktop app"); String iconPath = properties.getProperty("codename1.icon"); File iconFile = new File(iconPath); if (!iconFile.isAbsolute()) { diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/PrepareSimulatorClasspathMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/PrepareSimulatorClasspathMojo.java index a75940a912..2f23cfe7fe 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/PrepareSimulatorClasspathMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/PrepareSimulatorClasspathMojo.java @@ -70,8 +70,6 @@ private String prepareClasspath() { @Override protected void executeImpl() throws MojoExecutionException, MojoFailureException { - JavaVersionUtil.requireRuntimeJavaVersion(JavaVersionUtil.MIN_RUNTIME_JAVA_VERSION, - "run the Codename One simulator"); getLog().info("Preparing Simulator Classpath"); Properties props = project.getModel().getProperties(); if (props == null) {