Posted on 12/10/2025, 2:54:00 AM
Modified on 1/29/2026, 8:15:45 AM
Reading time 5min.
Hello everyone!
I think everyone here is either old enough, or just a retro‑enthusiast, and knows which phones were used in the 2000s.
At the beginning of 9th grade (late 2022) I wrote a small J2ME application exactly for such a device.

Later I made a remake in 2024 because I lost the original source code, but despite the setbacks we’ll still go through the steps.
To develop for the device, the first thing you need are the development tools, as ironic as that sounds.
I Googled “Symbian SDK”, and almost all the results were for the wrong tools, but that’s the usual story.
Most SDKs on those sites point to SDKs for more advanced models and use C++.
All of that is irrelevant, and yes, I tested and downloaded all of it – no, I’m not crazy.
Finally I realized that I have a Series 40 phone, so I needed the SDK for that series. I stumbled upon this link with an archival Java version, thanks to the now‑closed and removed StackOverflow post…

I started looking at examples. For the test app I needed a 3‑D sound‑playback example, so I also downloaded Apache Ant, because it is used for building. Its version can be any; Java 6 is required only for the utilities.

The 3‑D sound test app is very simple; after tweaking everything I made a small game.
From my observations, the emulator works for a limited time without registration, after which it asks for registration – which didn’t really work for me under Wine. The preverify utility looks extremely home‑grown; it’s hard to tell what it actually does.
From my own code I updated the Ant build file so that it takes values from environment variables (there’s no point explaining that here; it’s visible in the repository). I just set the variables to the previously‑hardcoded values.
<project name="MathThemRight" default="build">
<property environment="env"/>
<property name="build" value="build" /> <property name="classes" value="${build}/classes" /> <property name="src" value="src" /> <property name="rsrc" value="rsrc" /> <property name="midp_rsrc" value="${rsrc}/midp" /> <property name="bundle" value="${build}/bundle" /> <property name="preverified" value="${build}/preverified" /> <property name="bin" value="bin" /> <property name="midp_bin" value="${midp}/bin" /> <property name="targets" value="targets" /> <property name="javadoc" value="doc/javadoc" /> <property name="javadoc_rsrc" value="${rsrc}/javadoc" />
<property name="midp" value="." />
<property name="java_cmd" value="${env.JAVA_6_CMD}" /> <property name="sdk_location" value="${env.NOKIA_SERIES40_SDK}" />
<property name="preverify" value="${env.NOKIA_SERIES40_SDK_PREVERIFY}" />
<description> Math Them Right MIDlet java build project file </description>
<target name="clean"> <delete dir="${bin}" /> </target>
<target name="cleanSrc" description="deletes all class files from src folder"> <delete> <fileset dir="${src}" includes="**/*.class"/> </delete> </target>
<target name="build" description="clean, compile and build"> <antcall target="clean" /> <antcall target="compile" /> <antcall target="preverify" /> <antcall target="package" /> </target>
<target name="compile">
<mkdir dir="${classes}" /> <javac destDir="${classes}" srcDir="${src}" source="1.4" target="1.4" classpathref="project.class.path" includeAntRuntime="no" includeJavaRuntime="no" fork="yes" executable="${java_cmd}"> <bootclasspath> <fileset dir="${sdk_location}/lib"> <include name="**/*.jar"/> <include name="**/*.zip"/> </fileset> </bootclasspath>
</javac> <copy todir="${classes}"> <fileset dir="${src}" excludes="**/*.java"/> </copy> </target>
<path id="project.class.path"> <fileset dir="${sdk_location}/lib"> <include name="**/*.jar"/> <include name="**/*.zip"/> </fileset> </path>
<property name="myclasspath" refid="project.class.path"/>
<target name="preverify">
<mkdir dir="${preverified}"/> <exec executable="${preverify}" failonerror="true">
<arg line="-classpath ${myclasspath}"/> <arg line="-d ${preverified}"/> <arg line="${classes}"/> </exec> <copy todir="${preverified}" failonerror="false"> <fileset dir="${src}" excludes="**/*.java"/> </copy> </target>
<target name="package"> <mkdir dir="${bin}" /> <jar basedir="${preverified}" jarfile="${bin}/${ant.project.name}.jar" manifest="${midp_rsrc}/MANIFEST.MF" compress="true"> </jar>
<length file="${bin}/${ant.project.name}.jar" property="length.jarfile" /> <copy file="${midp_rsrc}/${ant.project.name}.jad" toDir="${bin}" /> <replace file="${bin}/${ant.project.name}.jad" token="@@@" value="${length.jarfile}"/>
</target>
</project>Then I completed the rest of the game, adding music according to Sun’s (yes, not Oracle’s!) MIDP documentation.
package com.foxelyss.maththemright;
import java.io.InputStream;import java.util.Random;
import javax.microedition.lcdui.*;import javax.microedition.media.Manager;import javax.microedition.media.Player;import javax.microedition.midlet.MIDlet;import javax.microedition.midlet.MIDletStateChangeException;
public class Game extends MIDlet implements CommandListener {
private static final String START_FORM_TITLE = "Math Them Right";
private static final String START_MESSAGE = "Please press Next";
private static final String CONTENT_TYPE = "audio/x-wav";
private static final String WIN_SOUND = "/com/foxelyss/maththemright/res/win.wav"; private static final String LOSE_SOUND = "/com/foxelyss/maththemright/res/lose.wav";
Display display;
ChoiceGroup answers;
Command startCommand = new Command("Next", Command.OK, 1); Command exitCommand = new Command("Exit", Command.EXIT, 2);
Player win_player; Player lose_player;
InputStream win_sound_stream; InputStream lose_sound_stream;
Form startForm;
int win_strike = 0; int right_answer_index = -3;
public Game() { display = Display.getDisplay(this); }
public void startApp() throws MIDletStateChangeException { initAudio(); startForm = new Form(START_FORM_TITLE); startForm.append(START_MESSAGE); startForm.addCommand(startCommand); startForm.addCommand(exitCommand); startForm.setCommandListener(this);
answers = new ChoiceGroup("Answers", Choice.EXCLUSIVE); startForm.append(answers); display.setCurrent(startForm); }
public void pauseApp() { }
public void destroyApp(boolean unconditional) { try { win_player.stop(); lose_player.stop(); win_player = null; lose_player = null; } catch (Exception e) { e.printStackTrace(); } }
void initAudio() { try { win_sound_stream = getClass().getResourceAsStream(WIN_SOUND); lose_sound_stream = getClass().getResourceAsStream(LOSE_SOUND);
win_player = Manager.createPlayer(win_sound_stream, CONTENT_TYPE);
win_player.realize(); win_player.prefetch();
lose_player = Manager.createPlayer(lose_sound_stream, CONTENT_TYPE);
lose_player.realize(); lose_player.prefetch();
} catch (Exception e) { e.printStackTrace(); } }
public void commandAction(Command c, Displayable d) { if (c == startCommand) { if (checkAnswer()) { win_strike++; try { win_player.start(); } catch (Exception e) { } } else { win_strike = 0; try { lose_player.start(); } catch (Exception e) { } }
generateExercise(); } else if (c == exitCommand) { destroyApp(true); notifyDestroyed(); } }
public boolean checkAnswer() { return right_answer_index == answers.getSelectedIndex(); }
public void generateExercise() { Random random = new Random();
int a = random.nextInt(200); int b = random.nextInt(200);
int operation = random.nextInt(4);
int right_result = a / b;
char operand = '/'; switch (operation) { case 0: operand = '+'; right_result = a + b; break; case 1: operand = '-'; right_result = a - b; break; case 2: operand = '*'; right_result = a * b; break; }
String question = a + " " + operand + " " + b + "\t="; ((StringItem) startForm.get(0)).setText(question + "\n" + "Winning streak:\t" + win_strike);
right_answer_index = random.nextInt(4);
answers.deleteAll(); for (int i = 0; i < 4; i++) { int offset = random.nextInt(24); String answer = "" + right_result; if (i != right_answer_index) { answer = (right_result + (offset < 12 ? -1 * (1 + offset) : offset)) + ""; }
answers.append(answer, null); } }
}The final game is available in the releases – see the GitHub repository. That’s it, more or less!