Home » Java » Core Java » Write an auto-debugger to catch Exceptions during test execution

About Gerard Davison

Write an auto-debugger to catch Exceptions during test execution

Previously I have stated that there are some exceptions you would always want to keep an debugger breakpoint on for. This help prevents code rotting away without you noticing – sometimes masking a different problem.

If you take this seriously then it is a good idea to extend this idea to you automated testing; but coming up with a comprehensive solution is not entirely trivial. You could just start with a try/catch but that won’t capture exceptions on other threads. You could also do something using AOP; but depending on the framework you are not guaranteed to catch everything also it does mean you are testing with slightly different code which will worry some.

A few days ago I came across this blog entry on how to write your own debugger, and I wondered if it was possible for java process to debug itself. Turn out you can and here is the code I came up with as part of this little thought experiment.

The first part of the class just contains something fairly hacky code to guess what the port that would be required to connect back to the same VM based on the start up parameters. It might be possible to use the Attach mechanism to start the debugger; but I didn’t seen an obvious way to get it to work. Then there are just a couple of factory methods that take a list of exceptions to look out for.

package com.kingsfleet.debug;

import com.sun.jdi.Bootstrap;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.VirtualMachine;
import com.sun.jdi.connect.AttachingConnector;
import com.sun.jdi.connect.Connector;
import com.sun.jdi.connect.IllegalConnectorArgumentsException;
import com.sun.jdi.event.ClassPrepareEvent;
import com.sun.jdi.event.Event;
import com.sun.jdi.event.EventQueue;
import com.sun.jdi.event.EventSet;
import com.sun.jdi.event.ExceptionEvent;
import com.sun.jdi.event.VMDeathEvent;
import com.sun.jdi.event.VMDisconnectEvent;
import com.sun.jdi.request.ClassPrepareRequest;
import com.sun.jdi.request.EventRequest;
import com.sun.jdi.request.ExceptionRequest;

import java.io.IOException;

import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;

import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class ExceptionDebugger implements AutoCloseable {

   public static int getDebuggerPort() {
       // Try to work out what port we need to connect to

       RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();
       List<String> inputArguments = runtime.getInputArguments();
       int port = -1;
       boolean isjdwp = false;

       for (String next : inputArguments) {
           if (next.startsWith("-agentlib:jdwp=")) {
               isjdwp = true;
               String parameterString = next.substring("-agentlib:jdwp=".length());
               String[] parameters = parameterString.split(",");
               for (String parameter : parameters) {
                   if (parameter.startsWith("address")) {
                       int portDelimeter = parameter.lastIndexOf(":");
                       if (portDelimeter != -1) {
                           port = Integer.parseInt(parameter.substring(portDelimeter + 1));
                       } else {
                           port = Integer.parseInt(parameter.split("=")[1]);
       return port;

   public static ExceptionDebugger connect(final String... exceptions) throws InterruptedException {
       return connect(getDebuggerPort(),exceptions);

   public static ExceptionDebugger connect(final int port, final String... exceptions) throws InterruptedException {

       ExceptionDebugger ed = new ExceptionDebugger(port, exceptions);

       return ed;

The constructor create a simple daemon thread that starts the connection back to the VM. It is very important to do this is a separate thread otherwise obviously the VM will grind to a halt when we hit a breakpoint. It is a good idea to make sure that code in that thread doesn’t throw one of the exceptions – for the moment I am just hoping for the best.

Finally the code just maintains a list of the banned exceptions, if you had a bit more time it should be possible to store the stack trace where the exception occurred.

   // Instance variables

   private final CountDownLatch startupLatch = new CountDownLatch(1);
   private final CountDownLatch shutdownLatch = new CountDownLatch(1);

   private final Set<String> set = Collections.synchronizedSet(new HashSet<String>());
   private final int port;
   private final String exceptions[];
   private Thread debugger;
   private volatile boolean shutdown = false;

   // Object construction and methods

   private ExceptionDebugger(final int port, final String... exceptions) throws InterruptedException {

       this.port = port;
       this.exceptions = exceptions;

       debugger = new Thread(new Runnable() {

           public void run() {
               try {
               } catch (Exception ex) {
       }, "Self debugging");
       debugger.setDaemon(true); // Don't hold the VM open

       // Make sure the debugger has connected
       if (!startupLatch.await(1, TimeUnit.MINUTES)) {
           throw new IllegalStateException("Didn't connect before timeout");

   public void close() throws InterruptedException {
       shutdown = true;
       // Somewhere in JDI the interrupt was being eaten, hence the volatile flag 

    * @return A list of exceptions that were thrown
   public Set<String> getExceptionsViolated() {
       return new HashSet<String>(set);

    * Clear the list of exceptions violated
   public void clearExceptionsViolated() {

The main connect method is a fair simple block of code that ensures the connection and configures any initial breakpoints.

   // Implementation details

   private void connect() throws java.io.IOException {

       try {
           // Create a virtual machine connection
           VirtualMachine attach = connectToVM();


               // Add prepare and any already loaded exception breakpoints

               // We can now allow the rest of the work to go on as we have created the breakpoints
               // we required


               // Process the events
           finally {

               // Disconnect the debugger

               // Give the debugger time to really disconnect
               // before we might reconnect, couldn't find another
               // way to do this

               try {
               } catch (InterruptedException e) {
       } finally {
           // Notify watchers that we have shutdown

Connecting back to self is just a process of finding the right attaching connector, in this case Socket although I guess you could use the shared memory transport on some platforms if you modified the code slightly.

private VirtualMachine connectToVM() throws java.io.IOException {

       List<AttachingConnector> attachingConnectors = Bootstrap.virtualMachineManager().attachingConnectors();
       AttachingConnector ac = null;

       for (AttachingConnector next : attachingConnectors) {
           if (next.name().contains("SocketAttach")) {
               ac = next;


       Map<String, Connector.Argument> arguments = ac.defaultArguments();

       try {
           return ac.attach(arguments);
       } catch (IllegalConnectorArgumentsException e) {
           throw new IOException("Problem connecting to debugger",e);

When you connect the debugger you have no idea as to whether the exceptions you are interested in have been loaded, so you need to register breakpoints for both the point where the classes are prepared and for those that have already been loaded.

Note that the breakpoint is set with a policy only to break the one thread – otherwise for obvious reasons the current VM will grind to a halt if the debugger thread is also put to sleep.

private void createInitialBreakpoints(VirtualMachine attach) {
       // Our first exception is for class loading

       for (String exception : exceptions) {
           ClassPrepareRequest cpr = attach.eventRequestManager().createClassPrepareRequest();

       // Then we can check each in turn to see if it have already been loaded as we might
       // be late to the game, remember classes can be loaded more than once

       for (String exception : exceptions) {
           List<ReferenceType> types = attach.classesByName(exception);
           for (ReferenceType type : types) {
               createExceptionRequest(attach, type);

   private static void createExceptionRequest(VirtualMachine attach, 
                                              ReferenceType refType) {
       ExceptionRequest er = attach.eventRequestManager().createExceptionRequest(
           refType, true, true);

The event processing loop polls for EventSet instances which contain one or more Event instances. Not all of these events are down to a breakpoint request though so you have to take care to not always call resume on the event set. This is because you might have two event sets in a row, with the code calling resume before you even get to read the second one. This results in missed breakpoints as the code caught up.

For some reason JDI appeared to be eating the interrupted flag, hence the boolean property to stop the loop with the close method from before.

private void processEvents(VirtualMachine attach) {
       // Listen for events

       EventQueue eq = attach.eventQueue();
       eventLoop: while (!Thread.interrupted() && !shutdown) {

           // Poll for event sets, with a short timeout so that we can
           // be interrupted if required
           EventSet eventSet = null;
               eventSet = eq.remove(500);
           catch (InterruptedException ex) {
               continue eventLoop;  

           // Just loop again if we have no events
           if (eventSet == null) {
               continue eventLoop;


           boolean resume = false;
           for (Event event : eventSet) {

               EventRequest request = event.request();
               if (request != null) {
                   int eventPolicy = request.suspendPolicy();
                   resume |= eventPolicy != EventRequest.SUSPEND_NONE;

               if (event instanceof VMDeathEvent || event instanceof VMDisconnectEvent) {
                   // This should never happen as the VM will exit before this is called

               } else if (event instanceof ClassPrepareEvent) {

                   // When an instance of the exception class is loaded attach an exception breakpoint
                   ClassPrepareEvent cpe = (ClassPrepareEvent) event;
                   ReferenceType refType = cpe.referenceType();
                   createExceptionRequest(attach, refType);

               } else if (event instanceof ExceptionEvent) {

                   String name = ((ExceptionRequest)event.request()).exception().name();

           // Dangerous to call resume always because not all event suspend the VM
           // and events happen asynchornously.
           if (resume)


So all that remains is a simple test example, since this is JDK 7 and the ExceptionDebugger is AutoCloseable we can do this using the try-with-resources construct as follows. Obviously if doing automated test use the testing framework fixtures of your choice.

public class Target {

   public static void main(String[] args) throws InterruptedException {

       try (ExceptionDebugger ex = ExceptionDebugger.connect(
               NoClassDefFoundError.class.getName())) {




   private static void doSomeWorkThatQuietlyThrowsAnException() {
       // Check to see that break point gets fired

       try {
           Thread t = new Thread(new Runnable() {
                           public void run() {
                                   throw new NoClassDefFoundError();
                               catch (Throwable ex) {

       } catch (Throwable th) {
           // Eat this and don't tell anybody

So if you run this class with the following VM parameter, note the suspend=n otherwise the code won’t start running, you will find that it can connect back to itself and start running.


This gives you the following output, note the extra debug line from the VM:

Listening for transport dt_socket at address: 5656
Listening for transport dt_socket at address: 5656

As every I would be interested to read if this was something that is useful for people and to help remove any obvious mistakes.

Do you want to know how to develop your skillset to become a Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you our best selling eBooks for FREE!


1. JPA Mini Book

2. JVM Troubleshooting Guide

3. JUnit Tutorial for Unit Testing

4. Java Annotations Tutorial

5. Java Interview Questions

6. Spring Interview Questions

7. Android UI Design


and many more ....


Receive Java & Developer job alerts in your Area

I have read and agree to the terms & conditions


Notify of

This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Newest Most Voted
Inline Feedbacks
View all comments
Neil Modi
8 years ago

Excellent, I am going to try this soon, i got the overall idea of coding. Hopoe it will work and if it not i will comment the new code.