Besides writing efficient code, mastering the art of debugging is one of the most useful things Java programmers can do to make their life easier. This is especially important in today’s development environment, where the software world is increasingly moving to distributed architectures and more asynchronous code.
While software bugs are inevitable, it is becoming increasingly challenging to discover and fix bugs in complex application builds. The debugging process is even more of a real pain when we shift to production environments.
Unfortunately, there’s no silver bullet around it — you must debug. So, how can Java developers get to the root cause of software bugs and resolve them more quickly?
This post puts together some of the most effective tips for debugging Java applications in both development and production environments.
Make Good Use of Breakpoints
Let’s start with breakpoints — an obvious yet important feature that lays the foundation of your debugging process. Breakpoints allow you to halt application execution so you can analyze the state of your program and find why the code is misbehaving.
Every debugger offers several types of breakpoints including conditional breakpoints, exception breakpoints, watch points, and trace points.
Learning how and when to apply different types of breakpoint can make your debugging process even smoother. Worth a mention is that some modern tools support non-breaking breakpoints. These allow you to place breakpoints on your code and collect debug data without halting program execution.
Here is an example breakpoint on the Rookout web IDE, set on line number 41.
Show Logical Structure
The “show logical structure” feature, usually available in the variables view, is quite useful when monitoring content within Java classes in your code. With this feature enabled, the variables list shows an array that’s far more helpful in the context of debugging.
This comes in handy especially if your code lacks toString()-methods for objects.
Here is a snapshot showing this feature on the Eclipse IDE “variable view” of the debugging perspective.
The variables view also allows you to directly modify the values of your variables during debugging. This can save you a significant amount of time since you don’t have to restart a debugging session with the changed input data.
Master the Art of Navigating Through Your Codebase
Every Java debugger provides a number of features that allow developers to navigate through different sections of their code when debugging. Some include run to line, step over, step into, and step return, which is safe to assume everyone knows and uses these.
In addition to these, consider incorporating the following:
- Drop to frame — This feature is used to jump back to a point in your stack frame. When you miss some point and need to go back in time, just use the drop-to-frame feature.
- Step filtering — Step filters allow you to skip certain packages during debugging. You don’t have to navigate through all classes of the JDK system when you can simply filter out types you don’t need.
To improve the speed of navigating through code, you should master shortcuts to the most important function keys. These include:
- F5 for stepping into
- F6 for stepping over
- F6 for step return
- F8 for running until next breakpoint is hit
While debugging shortcuts may vary from one IDE to another, memorizing them will eliminate the need for using your mouse from time to time.
Learn How to Resolve Deadlocks
A deadlock scenario occurs when two or more threads are blocked forever after forming a cyclic dependency, as illustrated below.
A set of Java threads are usually waiting for a resource owned by the other, a situation that might cause the application to stall completely. It can be quite challenging to debug jstack deadlocks because they do not exhibit symptoms such as a spike in memory, CPU, or other operating system metrics.
Additionally, they tend to manifest in the worst conditions, such as heavy production load conditions, which are hard to replicate.
There are various approaches for troubleshooting jstack deadlock situations. First, you can capture multiple thread dumps in your JVM cluster to infer thread patterns. In this case, analyzing a static thread dump may not be sufficient. Depending on the JVM cluster size, you might need to trawl multiple files, which is often laborious and time-consuming.
A better approach would be using an application monitoring solution that provides JV level and code level visibility required for isolating thread deadlocks. Luckily there are innovative tools that can help with this- including some modern advanced debuggers as well as commercial APM tools.
Using these tools to gain visibility into your Java code and isolate bugs reduces the time that could have been spent on manual analysis and prognosis.
Leverage the Power of Production Debuggers
A typical debugging flow that most developers usually follow involves replicating the environment, isolating the bug, then applying a fix. However, this is not always possible in every production environment. In such situations, developers can rely on rapid production debuggers for a seamless experience.
Rookout is one such powerful tool that allows you to gather debug data from your live applications without altering the application’s state or flow control.
With Rookout, you can set non-breaking breakpoints to get the full stack trace, capture live variables, or any other application data needed for debugging.
So, instead of using high-overload monitoring solutions for production debugging, use tools like Rookout that provide everything you need to debug live applications without redeploying or writing new code.
Whether you’re working on serverless or containerized applications, Rookout will be a great addition to your debugging arsenal.
Don’t Forget About Remote Debugging
The vast majority of top IDEs such as NetBeans, Eclipse, Intellij IDEA, and Visual Studio support remote debugging, a technique that allows you to debug Java code running on another machine.
This is especially important in situations where the target system does not support local debugging, or on systems that lack sufficient resources to run debuggers.
To perform remote debugging, you must provide configuration details that the debugger will use to connect to the remote port. If you’re using Eclipse, for instance, here are the configurations you should supply to launch a remote debugging session successfully.
Remote debugging also comes in handy when troubleshooting bugs in a production setting, where developers need to connect to an application and fix bugs remotely.
Finally, remember that debugging might sometimes take more time than the actual implementation. As you hone your Java debugging skills, always strive to write clean, efficient code — it pays off when it comes to debugging. And when things seem to get out of hand, it’s always worth taking a break.
A bug is a programming puzzle, and far too many programmers have solved these puzzles without being near their computer.
Use the strategies above to take the pain out of your Java debugging experience. Happy coding!