Building Java ME Apps with Modern Tooling

本文为原文前 6,000 字符的节选翻译,完整内容请查看原文。

Building Java ME Apps with Modern Tooling

Building Java ME Apps with Modern Tooling 24 October 2026 1558 wordsGuidesJava Java ME (Micro Edition, also referred to as J2ME) is a platform for running Java applications on small resource constrained devices, such as feature phones from before the iPhone era. It is largely considered a dead platform with the advent of smartphones, and modern feature phones such as KaiOS use web technologies instead for their apps. However it is likely that you or someone you know have one of these old phones lying around, and you may be interested in how you can make Java apps that will run on it. This post goes through how to build a simple Java ME app solely with modern tooling in a modern environment, without needing anything from the old Sun Java ME SDKs.

使用现代工具构建 Java ME 应用 2026年10月24日 1558字 指南 Java Java ME(Micro Edition,也称为 J2ME)是一个用于在资源受限的小型设备(如 iPhone 时代之前的功能手机)上运行 Java 应用程序的平台。随着智能手机的出现,它在很大程度上被认为是一个已死的平台,而像 KaiOS 这样的现代功能手机则改用 Web 技术来运行应用。然而,你或你认识的人很可能手头还有这些旧手机,你可能对如何制作能在其上运行的 Java 应用感兴趣。本文将介绍如何仅使用现代环境中的现代工具来构建一个简单的 Java ME 应用,而无需任何旧版 Sun Java ME SDK。

Requirements You will need some distribution of OpenJDK 8, such as the Temurin builds for various platforms. You should ideally make the binaries available on the PATH (and check that it’s the right version with java -version), for convenience. Also download MicroEmulator, because we will use their stub JARs that provides the Java ME APIs for compilation. Also download ProGuard (I use 7.1.1 for this), which will be used for the preverification step. The instructions in this blog post assume a Linux-y terminal environment, but you could also follow along with Git Bash or a similar environment on Windows.

要求:你需要某种 OpenJDK 8 发行版,例如适用于各种平台的 Temurin 构建版本。为了方便起见,最好将二进制文件添加到 PATH 中(并使用 java -version 检查版本是否正确)。此外,请下载 MicroEmulator,因为我们将使用其提供的存根 JAR 文件来进行 Java ME API 编译。同时下载 ProGuard(我使用的是 7.1.1 版本),它将用于预验证步骤。本博文中的说明假设使用类 Linux 终端环境,但你也可以在 Windows 上使用 Git Bash 或类似环境进行操作。

Hello World Here is a simple Hello World MIDlet that we will use. It will show a simple form using the LCDUI API with the text “Hello World!”: import javax.microedition.midlet.MIDlet; import javax.microedition.lcdui.Display; import javax.microedition.lcdui.Form; public class HelloMIDlet extends MIDlet { public void startApp() { Form form = new Form(“Hello”); form.append(“Hello World!”); Display.getDisplay(this).setCurrent(form); } public void pauseApp() {} public void destroyApp(boolean unconditional) {} } Save it as src/HelloMIDlet.java.

Hello World:这是我们将要使用的一个简单的 Hello World MIDlet。它将使用 LCDUI API 显示一个简单的表单,其中包含文本“Hello World!”:import javax.microedition.midlet.MIDlet; import javax.microedition.lcdui.Display; import javax.microedition.lcdui.Form; public class HelloMIDlet extends MIDlet { public void startApp() { Form form = new Form(“Hello”); form.append(“Hello World!”); Display.getDisplay(this).setCurrent(form); } public void pauseApp() {} public void destroyApp(boolean unconditional) {} } 将其保存为 src/HelloMIDlet.java。

Compile the class We will compile this with javac, but we first need the stub JARs from MicroEmulator available in the lib directory. Extract cldcapi11.jar and midpapi20.jar and put them in a lib directory next to the src directory. The cldcapi11.jar provides the CLDC 1.1 API and midpapi20.jar provides the MIDP 2.0 API. MIDP 2.0 is circa 2002, and is probably what you want to use for widest compatibility, while it has some important features that MIDP 1.0 lacks. Then we can compile the Java source file with the following command: mkdir -p build/classes javac -source 1.3 -target 1.1 -bootclasspath “lib/cldcapi11.jar:lib/midpapi20.jar” \ -d build/classes src/HelloMIDlet.java -source 1.3 sets the source compatibility to 1.3, which means that your source code will be Java 1.3 code. -target 1.1 sets the target compatibility to 1.1, which means that the generated bytecode will be compatible with Java 1.1 (bytecode version 45.3). This is what is required for MIDP 2.0, which is probably what you want to target. -bootclasspath specifies the libraries it should include on the classpath for compiling code, and specifically we want these libraries to override all the built-in libraries in OpenJDK 8 which are for the regular Java SE API. (To compile multiple .java files in src, you would put $(find src -name “*.java”) instead.)

编译类:我们将使用 javac 进行编译,但首先需要将 MicroEmulator 中的存根 JAR 文件放入 lib 目录。解压 cldcapi11.jar 和 midpapi20.jar,并将它们放入 src 目录旁边的 lib 目录中。cldcapi11.jar 提供 CLDC 1.1 API,midpapi20.jar 提供 MIDP 2.0 API。MIDP 2.0 大约出现在 2002 年,为了获得最广泛的兼容性,你可能需要使用它,因为它具备一些 MIDP 1.0 所缺乏的重要功能。然后,我们可以使用以下命令编译 Java 源文件:mkdir -p build/classes javac -source 1.3 -target 1.1 -bootclasspath “lib/cldcapi11.jar:lib/midpapi20.jar” \ -d build/classes src/HelloMIDlet.java。-source 1.3 将源代码兼容性设置为 1.3,这意味着你的源代码将是 Java 1.3 代码。-target 1.1 将目标兼容性设置为 1.1,这意味着生成的字节码将与 Java 1.1(字节码版本 45.3)兼容。这是 MIDP 2.0 所要求的,也是你可能想要的目标。-bootclasspath 指定了编译代码时应包含在类路径中的库,我们特别希望这些库覆盖 OpenJDK 8 中用于常规 Java SE API 的所有内置库。(若要编译 src 中的多个 .java 文件,你可以改用 $(find src -name “*.java”)。)

Package the JAR MIDlets need some metadata in the META-INF manifest file to work. Most of the fields are self-explanatory: Manifest-Version: 1.0 MIDlet-Name: hello-midlet MIDlet-Version: 1.0.0 MIDlet-Vendor: The Author MIDlet-1: HelloMIDlet,/icon.png,HelloMIDlet MicroEdition-Configuration: CLDC-1.1 MicroEdition-Profile: MIDP-2.0 For the MIDlet-1 field, the format is a comma-separated list of three values: The human-readable name of the MIDlet, the path to the icon inside of the JAR, and the fully qualified class name of the entrypoint class. Since the above Java code specifies no package, it will be in the default package at the root of the JAR. Save this to resources/META-INF/MANIFEST.MF, creating a resources/ directory to house assets to put in the final JAR. Also put a 16x16 PNG icon in resources/icon.png. Then let’s package everything together into a JAR: cp -r resources/* build/classes jar cfm build/input.jar resources/META-INF/MANIFEST.MF -C build/classes . Now we have a JAR file build/input.jar… But we need to preverify it before it can be run on a Java ME device.

打包 JAR:MIDlet 需要 META-INF 清单文件中的一些元数据才能工作。大多数字段是不言自明的:Manifest-Version: 1.0 MIDlet-Name: hello-midlet MIDlet-Version: 1.0.0 MIDlet-Vendor: The Author MIDlet-1: HelloMIDlet,/icon.png,HelloMIDlet MicroEdition-Configuration: CLDC-1.1 MicroEdition-Profile: MIDP-2.0。对于 MIDlet-1 字段,格式是由三个值组成的逗号分隔列表:MIDlet 的人类可读名称、JAR 内图标的路径以及入口类的全限定类名。由于上述 Java 代码未指定包,它将位于 JAR 根目录的默认包中。将其保存到 resources/META-INF/MANIFEST.MF,并创建一个 resources/ 目录来存放最终 JAR 中所需的资源。同时在 resources/icon.png 中放入一个 16x16 的 PNG 图标。然后我们将所有内容打包成一个 JAR:cp -r resources/* build/classes jar cfm build/input.jar resources/META-INF/MANIFEST.MF -C build/classes . 现在我们有了一个 JAR 文件 build/input.jar……但在它能在 Java ME 设备上运行之前,我们需要对其进行预验证。

Preverification Preverification is a process required for the KVM (the minimal Java virtual machine used in Java ME) to ensure that the bytecode is valid. Traditionally this would be done by a preverify tool from the original Java ME SDK, but ProGuard supports doing the same thing and is more modern. java -jar “lib/proguard.jar” \ -injars “build/input.jar” -outjars “app.jar” \ -libraryjars “lib/cldcapi11.jar” \ -libraryjars “lib/midpapi20.jar” \ -dontshrink -dontoptimize -dontobfuscate \ -microedition You can also use ProGuard to shrink, optimise and obfuscate your code to reduce the footprint of the resulting JAR, which is especially important for Java ME apps where the JAR size limit can be very strict. But for this example we disable all of that for now and just specify -microedition to do the preverification. Now you have a app.jar file that can actually be run on a Java ME device or in an emulator. You can use MicroEmulator to run it, or something with more features like FreeJ2ME-plus, currently the best J2ME emulator for desktop platforms.

预验证:预验证是 KVM(Java ME 中使用的最小 Java 虚拟机)所需的一个过程,用于确保字节码有效。传统上,这会由原始 Java ME SDK 中的预验证工具完成,但 ProGuard 支持执行相同的操作,而且更现代。java -jar “lib/proguard.jar” \ -injars “build/input.jar” -outjars “app.jar” \ -libraryjars “lib/cldcapi11.jar” \ -libraryjars “lib/midpapi20.jar” \ -dontshrink -dontoptimize -dontobfuscate \ -microedition。你也可以使用 ProGuard 来压缩、优化和混淆代码,以减小生成的 JAR 的占用空间,这对于 JAR 大小限制可能非常严格的 Java ME 应用尤为重要。但在本例中,我们暂时禁用了所有这些功能,仅指定 -microedition 来进行预验证。现在你拥有了一个可以在 Java ME 设备或模拟器上实际运行的 app.jar 文件。你可以使用 MicroEmulator 来运行它,或者使用功能更丰富的工具,例如目前桌面平台上最好的 J2ME 模拟器 FreeJ2ME-plus。

Getting the JAR to your phone Now that you have gotten it running in an emulator it would be nice to get it running on the real hardware. The feature phone I had lying around that’s the most functional is the Sony Ericsson S312, a budget feature phone from 2009. It even has a camera! Java feature phones tend to have connectivity and storage mediums that are generally considered obsolete nowadays. My Sony Ericsson S312 has a proprietary charger cable with no USB on either end - supposedly you can use a special USB transfer cable, but that was a separate accessory that doesn’t seem worthwhile to

将 JAR 传送到手机:既然你已经让它在模拟器中运行,那么让它在真实硬件上运行会更好。我手头功能最全的功能手机是索尼爱立信 S312,这是一款 2009 年的入门级功能手机。它甚至还有一个摄像头!Java 功能手机往往具有如今通常被认为已过时的连接和存储介质。我的索尼爱立信 S312 有一根专用的充电线,两端都没有 USB 接口——据说可以使用特殊的 USB 传输线,但那是一个单独的配件,似乎不值得……