Effective debugging in IntelliJ IDEA

From Zero to Hero

Sergey Yaushev
Towards Dev

--

Photo by Pixabay from Pexels
Photo by Pixabay from Pexels

I continue a series of articles about two developers — Steve (S) and Jack (J). Steve is an experienced senior developer, while Jack has just landed his first job.

In this article, you will get the basic skills of debugging your applications in IntelliJ IDEA. And at the end of the article, we will even analyze how maven plugins might be debugged.

Why we need debugging

Steve McConnell gives us the definition of debugging in his book Clean Code.

Debugging is the process of identifying and eliminating the causes of errors. This is where debugging differs from testing, which focuses on finding bugs.

In this article, I would like to cover the technical details of debugging and show how the debugging process helps in learning and understanding how your software works.

Level 0. Debugging basics or how to debug your code.

Let’s consider the primitive example of code to understand the basics of debugging.

Assume that we have a HelloWorldService that should return the name by saying hello and capitalizing the first letter for the name.

public class HelloWorldService {
public String sayHello(String name) {
return "Hello " + name;
}
}

And let’s write a test for it.

class HelloWorldServiceTest {
@Test
void shouldSayHelloJack() {
//Arrange
HelloWorldService helloWorldService = new HelloWorldService();
//Act
String result = helloWorldService.sayHello("jack");
//Assert
assertThat(result).isEqualTo("Hello Jack");
}
}

Very simple. I think it is obvious that this test will fail because “jack” will be without the capitalized first letter. But this example allows us to understand the basics of debugging.

S: How would you find the reason for this bug?

J: I would put the logging into the method and run the test again.

For example:

public class HelloWorldService {
public String sayHello(String name) {
System.out.println(name);
return "Hello " + name;
}
}
Console output: Name is Jack
Steve after Jack’s answer.

S: Yes, sounds good. But what if the program is very large and maybe you can’t even change the code. Debugging is a more useful tool for such purposes.

J: Got it. How can I debug this program?

S: So. Let’s start from “breakpoints”. Breakpoints allow you to pause the program execution in a place where you want. To set a breakpoint in IntelliJ IDEA you can left-click on the left column of your code.

Setting breakpoint

But this is not enough. To start debugging you should start the program in debug mode. For this purpose, you can select “Debug” from the context menu or press the “Debug button” on the panel in the right up corner.

Start debugging button from the context menu
Debug button on the start panel

The program is paused on the line with the breakpoint after starting.

Example of pausing program

S: The main keys that I use are the F7 and F8.

F8 is the key that allows you to take a step over. That means that you will go to the next line.

Example of using F8 key (step over)

F7 is the key that allows you to take the step into the method. For example, now we can step into the method “sayHello”.

Example of using F7 key (step into)

J: Great. I can go through all my programs.

Jack is happy!

S: Yes. Let’s take a look at how you can find your bugs.

Here we can see the debug section that contains two main blocks: Frames and Variables.

The frames block contains the whole stack trace of your program.

Variables block contains available variable names and values. For example, here we can see that the variable name is “jack” and not “Jack”. This is the reason why we have the failed test.

Debug panel in Intellij IDEA

J: I see that this is a very powerful tool.

S: Yes, but this is not all.

You can change your variables in runtime and see the program behavior. To do it you can press “Alt + F8” (this is a shortcut for “Evaluate expression”).

Changing the variable value in runtime

Now you can see that the test works as expected.

Test passed

So we were able to find the reason for the bug and we know how to fix it.

J: Thanks! I will use this tool every day!

S: Yes. Now you know the basics of debugging. I just forget to show you one more key to do it more fastly.

The F9 key allows us to move from one breakpoint to another. For example, if we press F9 after pausing the program on breakpoint 1 we will be moved to line 6 of the “sayHello” method.

The breakpoint on the sayHello() method

Level 1. Debugging in multi-threaded software.

J: Looks like now I know everything about debugging.

After some time…

J: Hey Steve! I can’t debug my multi-threaded program. When I put breakpoint other threads keep working.

Jack is confused

S: Yes, here you should use another approach to be aware of all threads you want to debug.

J: Hmm…

S: Let’s consider the following simple example.

public class Main {
public static void main(String[] args) throws InterruptedException {
Executor executor = Executors.newFixedThreadPool(2);
HelloWorldService helloWorldService = new HelloWorldService();
executor.execute(()->{
helloWorldService.printHello("Jack");
});
executor.execute(()->{
helloWorldService.printHello("Steve");
});
Thread.sleep(5000);
}
}

Here we have an application with 2 threads.

Both threads use the “HelloWorldService” as in the previous example. Let’s assume you want to debug two methods simultaneously.

If you put the breakpoint in both methods then it will not guarantee that both threads will be stopped. So the program will be stopped in one thread only and another one will finish the work.

Example of two threads

J: Yes, looks like I have the same case. How to overcome it?

S: Before we used suspended breakpoint. If you right-click on your breakpoint in IntelliJ IDEA you can see that “Suspend is checked”.

Suspend checkbox

You should uncheck it. It will make breakpoint yellow.

J: But now the program will not be paused. How I will debug it?

S: Here you can debug by using logging. Let’s take a look at the setting that appears after “Suspend” is unchecked.

Breakpoint settings

Here I print the name to find the incoming name value.

J: Thanks. I see this configuration contains a lot of inputs. What about conditions?

S: You can use this field if you want to say that this breakpoint will only work under certain conditions. For example, here I want to print the name only when it starts from “J”.

Using conditions

J: It is a very powerful tool when you want to filter out unnecessary calls.

S: Exactly!

Level 2. Debugging and learning third-party libraries.

J: What else should I know about debugging applications?

S: I believe the next thing is debugging third-party libraries.

J: But why do we need to debug external applications? We can’t change it. I consider it pointless.

S: The main goal here is not to change the application, but to understand how it works. That is why this chapter is called “Debugging and learning”.

A lot of applications are open source and allow us to download source codes and debug them as it is our application.

For example here is the StringUtils class from org.springframework.util. You can press download sources and Intellij IDEA will download source codes for this library.

Decompiled StringUtils class

Even if it is not an open-source library, the IDEA decompiles the byte code very well and we can use it.

Decompiled countOccurrencesOf() method

But anyway source code looks quite better.

Source code of countOccurrencesOf() method

To download sources via maven you can use the following command

mvn dependency:sources

The result of downloading sources is the .jar file that is located in your .m2 repository.

Jar file with sources in m2 folder

Level 3. Debugging applications that launched via mvn command.

Sometimes, for various reasons, we run the application through maven commands. Sometimes it’s just unit tests, and sometimes it’s an entire application. (for example, in case of starting via mvn spring-boot:run).

In this case, we cannot use the debug tool without additional settings because the program will not be paused.

To debug the application, as usual, we need to follow the following steps.

  1. Right-click on desired maven goal in IntelliJ IDEA and select “Debug

2. Stop the application

Press the “Stop” button

3. Press “Edit configuration

Press the “Edit configuration” button

4. Click “Modify” under “Java Options” and select “Add VM Options

Press the “Modify” button

5. Add “-DforkMode=never

Change “VM options”

6. Use debugging as usual.

Debugging when the app started via maven

Level 4. Debugging and learning maven plugins.

There are also cases when we need to deal with the work of the plugin.

For example, let’s assume that we have an error with using “swagger-maven-plugin”.

The error contains the following stack trace:

Stacktrace of exception

Here we can see the class and package when the error was thrown and find this file in the m2 repository.

To debug this class we should follow the following steps.

  1. Open decompiled code in IntelliJ IDEA. For example, let’s open the “OpenAPIV3Parser.class” code. To do this, go to the directory m2.
.m2 directory

Open jar file by any archiver. (7Zip for example). Here we can find classes that can be decompiled by Intellij IDEA. To decompile the needed class just move it to IDEA.

Moving .class file to IntelliJ IDEA

2. Now we can add breakpoint as in the usual application.

3. Add a new “Remote Debug Configuration” in the “Run/Debug Configurations” window and set the port to 8000.

Adding new configuration for remote debugging
Port is 8000

4. Run the build by

mvnDebug clean install

5. Press debug button.

J: Thanks, Steve. I can’t wait to go hone my new skills.

Conclusion

In this article, we have studied the following:

  1. Basics of debugging simple Java programs.
  2. Features of debugging in a multi-threaded environment.
  3. Debugging third-party code.
  4. Debugging Maven plugins.

I hope you found something new here and this knowledge will help you in your work as helped Jack.
If you have any questions, you can write me on Twitter at @sergey_yaushev.

--

--

Hi! I am a software engineer. Have a good experience with working in international companies. I am utilizing Java 17, Spring boot, Microservices, etc.