Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Ports/CLDC11/src/java/lang/Class.java
Original file line number Diff line number Diff line change
Expand Up @@ -278,4 +278,8 @@ public Constructor getEnclosingConstructor() {
public boolean isLocalClass() {
return false;
}

public boolean isRecord() {
return this != java.lang.Record.class && java.lang.Record.class.isAssignableFrom(this);
}
}
37 changes: 37 additions & 0 deletions Ports/CLDC11/src/java/lang/Record.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Codename One designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Codename One through http://www.codenameone.com/ if you
* need additional information or have any questions.
*/
package java.lang;

/**
* Base class for Java record types.
*/
public abstract class Record {
protected Record() {
}

public abstract boolean equals(Object obj);

public abstract int hashCode();

public abstract String toString();
}
153 changes: 153 additions & 0 deletions docs/website/content/blog/official-experimental-java-17-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
---
title: Official Experimental Java 17 Support for Codename One Projects
date: '2026-03-14'
author: Shai Almog
slug: official-experimental-java-17-support
url: /blog/official-experimental-java-17-support/
description: Codename One projects generated with the Initializr can now use official experimental Java 17 support, with record support arriving in Codename One 7.0.229 on March 20, 2026, and more JDK support planned next.
feed_html: '<img src="https://www.codenameone.com/blog/official-experimental-java-17-support.jpg" alt="Official Experimental Java 17 Support for Codename One Projects" /> Codename One projects generated with the Initializr can now use official experimental Java 17 support, with record support arriving in Codename One 7.0.229 on March 20, 2026, and more JDK support planned next.'
---

![Official Experimental Java 17 Support for Codename One Projects](/blog/official-experimental-java-17-support.jpg)

We now have official experimental support for Java 17 in Codename One projects.

This is available through the [Initializr](/initializr/). To use it you should generate a new project and select **Java 17** during project creation.

Support for Java 17 required work across the toolchain, the generated projects and several targets. This wasn't just a matter of changing a version number and hoping for the best.

## How This Works

You can use these new Java 17 projects with practically any JDK for the build itself. We tested this with JDK 21 and even JDK 25. In other words, you select Java 17 for the generated project, but your local machine does not need to run on JDK 17 just to build it.

## Caveats

There are a few caveats you should know before jumping in:

- The desktop target is not currently supported for Java 17 projects. If there is demand we can add that. You can still use the `jar` target to build desktop applications with Codename One, and that works fine with Java 17.
- UWP will not be supported. That target was already deprecated, so there is no point in doing the extra work there.
- Other targets should just work.

## What Java 17 Syntax Works?

This support includes modern language syntax that makes day to day code much nicer to write.

At the moment this includes features like `var`, switch expressions and text blocks. Record support is planned for Codename One `7.0.229`, slated for release on March 20, 2026.

For example, `var` lets us remove obvious type boilerplate from local variables:

```java
var greeting = "Hello";
var target = "Codename One";
```

Switch expressions are more concise and make it easier to return a value directly from the switch:

```java
var message = switch (greeting.length()) {
case 5 -> greeting + " " + target;
default -> "unexpected";
};
```

Text blocks are also supported, so multi-line strings are much more readable:

```java
var textBlock = """
Java 17 language features
should compile in tests.
""";
```

Record syntax is also queued for Codename One `7.0.229`, slated for release on March 20, 2026, so Java 17 projects will be able to use compact immutable data carriers too.

For example, this record will compile once `7.0.229` is available on March 20, 2026:

```java
record Person(String name, int age) {}

var person = new Person("Duke", 29);
System.out.println(person.name());
```

That means you can start using language improvements that make code cleaner and easier to read, and records will join that list in `7.0.229` on March 20, 2026.

This still does not include support for newer JDK APIs such as streams. As mentioned in the previous post, if you need streams today there is already a cn1lib solution for that, and we hope to improve the built-in story over time.

## What About Java 21 and 25?

Since we mentioned JDK 21 and 25 above, it is worth clarifying the roadmap.

We do plan to add support for newer language levels in a coming update. That is a different challenge, because Android only supports up to Java 17 today.

We would still like to introduce that support, but at the moment Java 17 is the more important milestone.

## Other Updates Worth Mentioning

A few other useful things landed over the past week.

The [Initializr](/initializr/) now includes more advanced CSS theme editing with live preview, and there were several follow-up fixes to make theming behave more reliably. This is relevant if you are starting a fresh project anyway, because the [Initializr](/initializr/) has become a much stronger starting point than it was even a couple of weeks ago.

We also finished updating all macOS build servers to use Xcode 26.

Another useful update is a newer version of the Codename One Bluetooth LE cn1lib, available in the [bluetoothle-codenameone repository](https://github.com/codenameone/bluetoothle-codenameone). This library now uses a native Codename One bridge implementation adapted from the original Bluetooth LE plugin lineage.

If you are using a Maven-based Codename One project, add it with:

```xml
<dependency>
<groupId>com.codenameone</groupId>
<artifactId>cn1-bluetooth-lib</artifactId>
<version>1.0.0</version>
<type>pom</type>
</dependency>
```

If you are using a classic Codename One project, you can still integrate it as a `.cn1lib` by adding the library to your project's `lib/` directory, running `Refresh Libs`, and rebuilding. The library requires Java 8.

Here is a small usage sample:

```java
final Bluetooth bt = new Bluetooth();
Form main = new Form("Bluetooth Demo");
main.setLayout(new BoxLayout(BoxLayout.Y_AXIS));
main.add(new Button(new Command("enable bluetooth") {
@Override
public void actionPerformed(ActionEvent evt) {
try {
if (!bt.isEnabled()) {
bt.enable();
}
if (!bt.hasPermission()) {
bt.requestPermission();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
}));
main.add(new Button(new Command("initialize") {
@Override
public void actionPerformed(ActionEvent evt) {
try {
bt.initialize(true, false, "bluetoothleplugin");
} catch (IOException ex) {
ex.printStackTrace();
}
}
}));
```

## Try Java 17 on a Real Project

If you have been waiting for a more modern Java syntax level in Codename One, this is the time to give it a try.

Create a new project with the [Initializr](/initializr/), select Java 17 and see how it feels in a real application. If you run into issues on Android, iOS, JavaScript or any other target, let us know. That feedback is what will help us polish this support and move it forward.

---

## Discussion

_Join the conversation via GitHub Discussions._

{{< giscus >}}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -3607,9 +3607,15 @@ public void usesClassMethod(String cls, String method) {

String compileSdkVersion = "'android-21'";

String java8P2 = "";
if(useJava8SourceLevel) {
java8P2 = " compileOptions {\n" +
int projectJavaVersion = parseVersionStringAsInt(request.getArg("java.version", "8"));
String javaCompileOptions = "";
if (projectJavaVersion >= 17 && useGradle8) {
javaCompileOptions = " compileOptions {\n" +
" sourceCompatibility JavaVersion.toVersion(17)\n" +
" targetCompatibility JavaVersion.toVersion(17)\n" +
" }\n";
} else if(useJava8SourceLevel) {
javaCompileOptions = " compileOptions {\n" +
" sourceCompatibility JavaVersion.VERSION_1_8\n" +
" targetCompatibility JavaVersion.VERSION_1_8\n" +
" }\n";
Expand Down Expand Up @@ -3770,7 +3776,7 @@ public void usesClassMethod(String cls, String method) {
+ multidex
+ request.getArg("android.xgradle_default_config", "")
+ " }\n"
+ java8P2
+ javaCompileOptions
+ " sourceSets {\n"
+ " main {\n"
+ " aidl.srcDirs = ['src/main/java']\n"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
codename1.android.keystore=
codename1.android.keystoreAlias=
codename1.android.keystorePassword=
codename1.arg.android.useAndroidX=true
codename1.arg.ios.applicationQueriesSchemes=cydia
codename1.arg.ios.newStorageLocation=true
codename1.arg.ios.uiscene=false
codename1.arg.java.version=17
codename1.cssTheme=true
codename1.displayName=HelloCodenameOne
codename1.icon=icon.png
codename1.arg.android.useAndroidX=true
codename1.ios.appid=Q5GHSKAL2F.com.codenameone.examples.hellocodenameone
codename1.ios.certificate=
codename1.ios.certificatePassword=
Expand All @@ -28,4 +30,3 @@ codename1.rim.signtoolDb=
codename1.secondaryTitle=Hello World
codename1.vendor=CodenameOne
codename1.version=1.0
codename1.cssTheme=true
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ public final class Cn1ssDeviceRunner extends DeviceRunner {
new OrientationLockScreenshotTest(),
new InPlaceEditViewTest(),
new BytecodeTranslatorRegressionTest(),
new Java17Tests(),
new BackgroundThreadUiAccessTest(),
new VPNDetectionAPITest(),
new CallDetectionAPITest(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.codenameone.examples.hellocodenameone.tests;

public class Java17Tests extends BaseTest {
record MyRecord(int val, String otherVal) {
@Override
public String toString() {
return "MyRecord[val=" + val + ", otherVal=" + otherVal + "]";
}

@Override
public int hashCode() {
return 31 * val + otherVal.hashCode();
}

@Override
public boolean equals(Object obj) {
if (!(obj instanceof MyRecord)) {
return false;
}
MyRecord other = (MyRecord)obj;
return val == other.val && otherVal.equals(other.otherVal);
}
}

@Override
public boolean shouldTakeScreenshot() {
return false;
}

@Override
public boolean runTest() throws Exception {
try {
var greeting = "Hello";
var target = "Codename One";
var record = new MyRecord(2, "V");

var message = switch (greeting.length()) {
case 5 -> greeting + " " + target;
default -> "unexpected";
};

var textBlock = """
Java 17 language features
should compile in tests.
""";

assertEqual("Hello Codename One", message);
assertEqual("Java 17 language features\nshould compile in tests.\n", textBlock);
assertEqual(2, record.val());
assertEqual("V", record.otherVal());
assertEqual("MyRecord[val=2, otherVal=V]", record.toString());
assertEqual(148, record.hashCode());
assertTrue(record.equals(new MyRecord(2, "V")));
return true;
} catch (Throwable t) {
fail(String.valueOf(t));
return false;
} finally {
done();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public boolean runTest() {
protected void onShowCompleted() {
CN.lockOrientation(false);
waitForOrientation(this, false, () -> {
waitFor(50);
Cn1ssDeviceRunnerHelper.emitCurrentFormScreenshot("landscape");
CN.lockOrientation(true);
waitForOrientation(this, true, OrientationLockScreenshotTest.this::done);
Expand Down

This file was deleted.

4 changes: 4 additions & 0 deletions vm/JavaAPI/src/java/lang/Class.java
Original file line number Diff line number Diff line change
Expand Up @@ -341,4 +341,8 @@ public Constructor getEnclosingConstructor() {
public boolean isLocalClass() {
return false;
}

public boolean isRecord() {
return this != java.lang.Record.class && java.lang.Record.class.isAssignableFrom(this);
}
}
Loading
Loading