Skip to main content

Debugging on J2ME/CLDC devices

Posted by bakksjo on September 22, 2005 at 4:10 AM PDT

I'm convinced; the people who made the CLDC spec gathered early in the process and decided: "We'll make sure nobody is ever going to be able to debug CLDC applications on a real device."

At least that's how it seems.

System.out and System.err go into a black hole. Throwable objects do have a stack trace, but what can you do with it? You can print it to System.out. Which is a black hole.

In J2SE you can print the stack trace to a stream of your choice. This enables you to present the stack trace in any way you want. In CLDC, they removed that possibility. They also removed the chaining possibility from all the standard exception classes in the platform.

Your program may run fine in an emulator, but things are really different on a physical device. It's truly "write once, debug everywhere". Depending on the vendor, your application may crash with helpful messages such as these:

  • "Program failure"
  • "MontyThread-7"
  • "-6031"
  • "KERN-EXEC: 3"
  • "Exception: jab.dab.doo.someth..."

If you are lucky enough to see anything resembling an exception class name, this message is typically truncated before you get to the interesting part, and the device displays the message for a couple of seconds, then clears the screen. Very nice.

Checked exceptions are not a problem - you know where they're thrown, you catch them, you handle them. The problem is RuntimeExceptions, such as e.g. NullPointerExceptions. A stack trace would be really helpful, but that's impossible. Here are some things that you can do:

1) At the top level of all your application-defined threads, catch and show RuntimeExceptions:

public class MyThread
   extends
      Thread
{
   public void run()
   {
      try {
         runCore()
      } catch (RuntimeException e) {
         displayException(e);


         // Then pause or sleep, whichever applies
         // sleeping is easiest, does not require user interaction
         sleep(10000); Interrupt handling omitted

         throw e; // This will probably terminate the application
      }
   }

   private void runCore()
   {
      // The core run method of this thread
   }

   private void displayException(RuntimeException e)
   {
      // Display in the simplest possible way
   }
}

The displayException method should be really simple, so that you don't risk getting another exception in the midst of displaying one. Also, you might want to put it in a separate class, perhaps as a static method, so that you don't have to implement it in each of your threads.

2) Not all threads in the application are created or controlled by your code. Apply the technique above to all methods invoked by the runtime - typically all your methods that implement an interface or extend a class in the CLDC/MIDP specification, such as Midlet.notifyPaused, CommandListener.commandAction etc.

At this stage, you should always be able to see the type (class name) and message of any unexpected exception encountered in your application. Now, if you want to take things a step further:

3) Create your own RuntimeException type:

public class MyException
   extends
      RuntimeException
{
   private String location;
   private RuntimeException cause;

   public MyException(String location, RuntimeException cause)
   {
      this.location = location;
      this.cause = cause;
   }

   public String getMessage()
   {
      return location + ":" + cause.getMessage();
   }
}

Now, apply the pattern below to any non-top-level methods you desire:

public class AnyClass
{
   public void anyMethod(anyParams)
   {
      try {
         anyMethodCore(anyParams);
      } catch (RuntimeException e) {
         throw new MyException("AnyClass.anyMethod()", e);
      }
   }

   private void anyMethodCore(anyParams)
   {
      // The original anyMethod
   }
}

A non-top-level method is any method not covered in steps 1 and 2. Coding like this bloats your application with extra class methods and method invocations, but doing this will give you a call chain, which is not as good as a line-numbered call stack, but definitely better than the usual blurb you get from the device. Also, you can use preprocessor techniques to apply this pattern only in builds used during testing, and then remove the bloat from the build that you publish/deploy.

Comments

Stack traces on actual devices

Sorry to comment on an ancient blog post but I just have to ;-) "... nobody is ever going to be able to debug CLDC applications on a real device." After years of frustration I created a tool to prove them wrong: http://jarrut.sourceforge.net