First consider how much effort you want to expend on this.
Modern
MIDP/CLDC devices have far more memory than the original specifications
envisaged. If you must reduce memory size, bear in mind that all the
following techniques will reduce the readability and manageability of
your code. In no particular order...

Don't create separate classes to act as event listeners -
use a class that already exists. For example, use your MIDlet's main
class as the event listener for form events, rather than creating a
separate class. Remember that even the most trivial, anonymous inner
class requires 200 bytes of bytecode, plus some state information for
each instance.

Where possible, use ordinary named classes, not inner
classes. It is conventional in AWT programming to use inner classes to
act as event listeners. The state information required to support an
instance of an inner class can be surprisingly extensive, because of
the way that inner classes are managed by the JVM.

Don't subclass the built-in classes unless you have to. For
example, you could create a subclass of Form to serve as your
applications main user interface, but you could also use an ordinary
Form object, and drive it from the MIDlet's main class. Subclassing
Form is more `OO', but imposes adds a few hundred bytes to the
footprint. Every class you subclass has the same effect.

Don't subclass your own classes unless you have to. The
flatter your class hierarchy, the few the classes; the fewer the
classes, the smaller the bytecode.

Don't put your classes into packages. J2ME applications are
self-contained, so there is no prospect for name clashes between
applications. The packages names have to be stored in the bytecode, on
every occasion that a class is defined or referenced. A better solution
than de-packaging all your code is to use a software tool to do it.
Bytecode obfuscators do this as part of their normal operation.

Remember that array initialisations translate into lots of
repeated bytecode operations. So initializing an array of month names
like this:

String[] months = {"January", "February"...

generates a surprising amount of bytecode. If you have very
large arrays of strings or numbers, it generates less bytecode to
define it in a long, delimited String, and split it into individual
values in a loop. This may seem mad, but it's true.

If you have to support multiple locales, plan on
distributing different versions of the application for different
locales, rather than supporting multiple locales in the same
application. There's no point providing a load of French and Japanese
text if the user wants to see only Italian.

If an application creates a Java instance, and knows that
it is no longer required, it should re-use if with different data, or
set it to null. Re-use is the preferred option, where this is
practicable, but setting to null will help the garbage collector
determine which objects are out of scope.