Intermediate

Java Debugging

Debugging Java training teaches how to apply debugging techniques throughout the software development process. When writing code, you inevitably write bugs. Knowing how to quickly identif...

Prerequisites: Basic knowledge of Java


Table of Contents

  1. Training overview
  2. Develop a debugging mindset
  1. Avoid Common Mistakes
  1. Find the problem, fix the problem
  1. What else can go wrong?
  1. Increase productivity with an IDE
  1. Code files — Globomantics Project

1. Training overview

Debugging Java training teaches how to apply debugging techniques throughout the software development process. When writing code, you inevitably write bugs. Knowing how to quickly identify and correct these bugs is essential to producing quality work and moving forward efficiently.

Main objectives

  • Understand common issues inside and outside of code
  • Learn how to collect information from errors to fix bugs
  • Use the tools of an IDE (Integrated Development Environment)

What you will learn

At the end of this training, you will have a wide range of tools and techniques applicable both when writing code and when problems appear, to debug your Java code quickly and efficiently.

Prerequisites

A basic understanding of Java is required before beginning this training.


2. Développer un état d’esprit de débogage

2.1 Write bugs and fix code

Whether you’re just starting out or have been working with Java for a while, debugging is an integral part of writing code. Learning to debug more effectively can really boost your productivity and reduce your frustration levels.

Java Versions and Applicability

This course was created with specific versions of Java (Java 17 and later as a base), but since debugging is as much about strategy and concepts as it is about syntax, the principles presented apply to all versions of Java.

The three key moments to find and fix bugs

  1. At the time of entry
  • Spelling and syntax errors recognized immediately
  • Iteration cycle of a few seconds
  • IDEs with real-time compilation facilitate immediate detection
  1. At compile time
  • The compiler takes into account the entire file or project
  • IDEs report potential input errors using real-time compilation
  • On large team projects, broken dependencies and bugs may only appear during the full build
  1. At runtime (runtime)
  • Unexpected behavior visible in test or production environment
  • Some errors only appear under certain execution conditions
  • Sometimes related to data used or external dependencies

Iteration cycle (iteration cycle)

The iteration cycle is the time between discovering a bug and verifying the fix:

Moment of discoveryIteration cycle
Input (typos, syntax)A few seconds
Local compilationA few minutes
Shared Integrated Test EnvironmentSeveral minutes to several days

Improving your knowledge of the language helps shorten the iteration cycle, allowing you to fix more bugs in less time.

Common issues outside of application code

These issues do not concern the business logic of the code itself, but its configuration or dependencies:

  • Database configuration: no connection to the database, or connection to the wrong database
  • API Endpoints: endpoint unavailable, or API contract (what we send and receive) has changed
  • Environment variables: configuration pointing to the wrong environment (e.g.: dev instead of test)
  • Code deployment (code deployment): the application is still running on the old version of the code after a deployment
  • Network Issues: Unexpected behavior due to network infrastructure issues

Recognizing these common issues outside of application code can save you hours of searching in the wrong places.


2.2 Understanding verification and validation

Car and boat analogy

Suppose you build a perfectly functional car. The customer is unhappy because he needed a boat to travel on water, not on land.

  • Verification (Verification): Did you build the thing correctly? (Did you build it right?) → Does the car have all the components of a car and do they work as expected?

  • Validation (Validation): Did you build the right thing? (Did you build the right thing?) → The car is not the right thing — a boat was needed.

The three categories of bugs

CategoryDescriptionSolution
Verification errorThe code is incorrect (bad syntax, bad implementation logic)Fix the code
Validation errorThe code is syntactically correct but does the wrong thingChange code behavior
New featureThere is no code for the desired functionalityImplement the feature

Most verification bugs are detected by the compiler. Validation focuses on the needs of stakeholders.

Causes of validation errors

  • Unclear or ambiguous requirements
  • Frequently requested visual changes to data presentation
  • Modification of business logic (business logic) (e.g.: updating tax rates)
  • Missing features not obvious before using the system
  • Change of opinion of stakeholders

3. Avoid Common Mistakes

3.1 Create correct control instructions

Common syntax errors

Most missing values ​​generate an immediately detectable syntax error:

  • Forgot a semicolon (;) at the end of a statement
  • Forgot to close parentheses, braces, square brackets
  • Forgot to close single or double quotes

Multi-line text blocks (text blocks)

Old method (error-prone):

String multi1 =
    "this is an example\n" +
    "of a multi-line string\n" +
    "be sure to remember newlines\n";

New method with text blocks (Java 13+):

String multi2 =
    """
    this is another way 
    to create a multi-line string.
    It's easier to create and edit.
    """;

text blocks reduce the risk of errors when creating and editing multi-line strings.

if statements — Formats and pitfalls

Java supports multiple formatting styles for if statements. All are syntactically valid, but some are more readable and less risky:

// Style recommandé — lisible et clair
if (condition) {
    doTrue();
} else {
    doFalse();
}

// Style avec accolades sur leur propre ligne
if (condition)
{
    doTrue();
}
else
{
    doFalse();
}

// Sans accolades — RISQUÉ lors de l'ajout de lignes
if (condition)
    doTrue();
else
    doFalse();

In Java, indentation does not matter to the compiler. All of these styles do the same thing.

Assignment operator vs comparison operator in an if

Common BUG — use = instead of ==:

boolean a = true;
boolean b = false;

// BUG : assigne b à a (a vaut maintenant false), n'effectue PAS une comparaison
if (a = b) {
    doTrue();
} else {
    doFalse(); // Ce bloc s'exécute TOUJOURS
}
// Effet de bord : a vaut maintenant false !

Correct code:

// Correct : compare a et b
if (a == b) {
    doTrue();
} else {
    doFalse();
}

For booleans, it is even more readable to simply write if (freeShipping) or if (!freeShipping) without the == operator.

.equals() vs == for objects

The .equals() method compares the values of an object’s attributes (depending on its implementation). The == operator compares references (if two variables point to the same instance).

Bucket one = new Bucket(10, "red");
Bucket two = new Bucket(10, "red");

// true — même taille et couleur (comparaison par valeur)
one.equals(two)

// false — instances différentes (comparaison par référence)
one == two

// true — même référence
Bucket three = one;
one == three

Typically use .equals() to compare objects by their attribute values.

switch instructions — Forgetting break (fall-through)

Current BUG — switch without break:

String color = "red";

// BUG : sans break, tous les cases après "red" s'exécutent aussi
switch (color) {
    case "red":
        System.out.println("In red");    // Exécuté
    case "orange":
        System.out.println("In orange"); // Exécuté aussi !
    case "yellow":
        System.out.println("In yellow"); // Exécuté aussi !
    default:
        System.out.println("In default");// Exécuté aussi !
}

Correct code with break:

switch (color) {
    case "red":
        System.out.println("In red");
        break;
    case "orange":
        System.out.println("In orange");
        break;
    case "yellow":
        System.out.println("In yellow");
        break;
    default:
        System.out.println("In default");
        break;
}

Modern solution — switch with arrow statements (Java 14+):

// Pas de fall-through possible avec la syntaxe flèche
switch (color) {
    case "red"    -> System.out.println("In red");
    case "orange" -> System.out.println("In orange");
    case "yellow" -> System.out.println("In yellow");
    default       -> System.out.println("In default");
}

Arrow syntax (->) in switch eliminates fall-through behavior and simplifies the code.

Loops — Common Errors

off-by-one errors (offset of one)

These errors often occur with array indices (0-based in Java):

String[] topOrders = new String[topSize];

// BUG : i commence à 1 — le premier élément (index 0) est manqué
for (int i = 1; i < topSize; i++) {
    topOrders[i] = orderList[i]; // topOrders[0] restera null
}

// Correct : commencer à 0
for (int i = 0; i < topSize; i++) {
    topOrders[i] = orderList[i];
}
Infinite loops (infinite loops)

Common causes:

CauseExample
Bad update of control variableIncrement instead of decrement
Bad test expressionUse > instead of <
Forgot to initialize or updateControl variable never modified

Case sensitivity of variables (case sensitivity)

In Java, variable names are case sensitive:

// "value" et "Value" sont deux variables DIFFÉRENTES
int value = 10;
int Value = 20;

Variable scope (variable scope)

Scope defines where a variable is visible in a program. Classes, methods, and control structures have different scopes.

Common BUG — confusion between a class variable and a method parameter:

public class OrderProcess {
    // Variable de classe — portée classe
    private int topSize = 3;

    // BUG : paramètre "topsize" (minuscule) ≠ "topSize" (capital S)
    // La variable de classe topSize = 3 est utilisée, PAS le paramètre !
    public String[] getTopOrders(String[] orderList, int topsize) {
        String[] topOrders = new String[topSize]; // utilise la variable de CLASSE (= 3)
        // ...
    }
}

Correction:

// Corriger la casse du paramètre
public String[] getTopOrders(String[] orderList, int topSize) {
    String[] topOrders = new String[topSize]; // utilise maintenant le PARAMÈTRE
    // ...
}

3.2 Demo: Fix control structure and variable errors

Scenario 1 — Assignment operator bug (OutboundProcess)

Background: The accounting department has detected an unusual drop in shipping charges collected.

Symptom: Even with freeShipping=false, the system used a special carrier and did not apply shipping charges.

Cause: In the assignCarrier method, the assignment operator = was used instead of the comparison operator == in the if statement.

// BUG : assigne true à freeShipping, qui passe ensuite à recordShippingCharge
if (freeShipping = true) { // assigne true à freeShipping !
    out("Use special carrier.");
} else {
    out("Use regular carrier.");
}

Correction:

// Option 1 : utiliser ==
if (freeShipping == true) { ... }

// Option 2 (recommandée pour les booléens) : utiliser directement la variable
if (freeShipping) {
    out("Use special carrier.");
} else {
    out("Use regular carrier.");
}

Scenario 2 — switch bug without break (InboundOrder)

Context: Commands should have only one tag applied, but multiple tags are attached simultaneously.

Cause: The tagOrder method had a switch without break instructions, causing fall-through behavior.

Correction: Add break after each case, or convert to arrow syntax:

public void tagOrder(String customerType) {
    switch (customerType) {
        case "bronze" -> out("Tag: level2");
        case "silver" -> out("Tag: level3");
        case "gold"   -> out("Tag: level4");
        default       -> out("Tag: level1");
    }
}

Scenario 3 — off-by-one error in getTopOrders (OrderProcess)

Context: Top order reports were missing items based on inventory.

Cause: The count variable i was initialized to 1 instead of 0, causing an off-by-one error.

// BUG : i commence à 1, le premier élément du tableau est manqué
for (int i = 1; i < topSize; i++) { ... }

// Correction : commencer à 0
for (int i = 0; i < topSize; i++) { ... }

Scenario 4 — Variable scope/case confusion (OrderProcess)

Context: All top order lists have the same length even though they should be different depending on the department.

Cause: The parameter topsize (all lowercase) did not match the class variable topSize (capital S). Java is case sensitive, so the class variable topSize=3 was always used.

private int topSize = 3; // variable de CLASSE

// BUG : paramètre "topsize" avec s minuscule ≠ variable de classe "topSize"
public String[] getTopOrders(String[] orderList, int topsize) {
    String[] topOrders = new String[topSize]; // utilise TOUJOURS la variable de classe = 3
}

// Correction : harmoniser la casse
public String[] getTopOrders(String[] orderList, int topSize) {
    String[] topOrders = new String[topSize]; // utilise maintenant le paramètre
}

3.3 Refactoring for readability and maintainability

Quote from Brian Kernighan

“Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.”

There is real value in making your code clear and easy to understand. Even if you are the only one maintaining a project, after several months the code might as well have been written by someone else.

Techniques to improve readability

Code organization
  • Prefer several small methods with targeted objectives rather than one giant method that does 10 things
  • Use packages and classes to group code by related functionality
  • Good organization makes debugging easier
Whitespace
  • Add blank lines, indentation and spaces to group methods, operators and code blocks
  • Make code structure and flow easier to follow visually
Descriptive variable names
// Mauvais — noms incompréhensibles
public double calcTot(double prc, double rt, double dr) {
    double disc = prc * dr;
    double subTot = prc - disc;
    double tx = subTot * rt;
    double tot = subTot + tx;
    return tot;
}

// Bon — noms descriptifs, code auto-documenté
public double calculateTotal(double price, double taxRate, double discountRate) {
    double discount = price * discountRate;
    double subTotal = price - discount;
    double tax = subTotal * taxRate;
    double total = subTotal + tax;
    return total;
}

Java is not faster with shorter variable names. The limit on the length of a variable name is much larger than you will practically use.

Practical rules:

  • Name an order array orders, not array
  • Avoid excessive abbreviations
  • Names must represent the value or role of the variable

Coding standards (coding standards)

Coding standards guide the implementation of:

  • Indentation
  • Spacing
  • Naming conventions
  • Other aspects affecting maintainability

The goal is to create a consistent experience across the entire codebase (codebase), rather than having hundreds of variations for each developer.

Automatic formatting tools allow these standards to be defined and applied automatically.

Use standard libraries (standard libraries)

Before writing code from scratch, consider using the Java standard libraries:

  • These widely used libraries have had much more time for their bugs to be found and fixed
  • They save development time
  • They reduce the number of potential bugs in your own code

4. Trouver le problème, corriger le problème

“Find the problem, fix the problem.” — Advice from a senior developer, useful for working through endless logs

Debugging is an iterative process. The first thing you fix doesn’t always make the whole problem go away. Sometimes fixing one bug reveals two more. Other times, fixing one bug eliminates three at once.

4.1 Read stack traces and logs

Anatomy of a simple stack trace

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 8 out of bounds for length 7
    at com.globomantics.orders.OrderProcess.getTopOrders(OrderProcess.java:8)
    at com.globomantics.orders.OrderProcess.main(OrderProcess.java:24)

The stack trace reads from bottom to top:

  • Bottom: entry point (main)
  • Top: exact line where the exception occurred

In real applications with frameworks and application servers, stack traces are much longer. Strategy for analyzing them:

  1. Identify the exception at the top of the stack trace (e.g.: NullPointerException)
  2. Use package names as map:
  • org.apache.tomcat, coyote, catalina → application server related, probably not your code to fix
  • javax.servlet → servlet API, probably not your code
  • org.springframework → Spring framework, probably not your code
  • java.base → Java platform APIs, probably not your code
  • com.globomantics → your application code → where to look for bugs
  1. Avoid line wrapping (line wrapping): seeing the stack trace without newlines makes it easier to filter packages

  2. Focus on application packages: zoom in on the lines corresponding to your code

Example of localization with a complex stack trace

com.globomantics.OrderService.processOrder(OrderService.java:13)

Line 13 of OrderService.java is the best place to start. The actual problem line may be a line or two above or below, but it’s a great place to start.

System.out.println vs logging frameworks

CharacteristicSystem.out.printlnLogging framework
ExitAlways onControllable by log level
ContextNoneTimestamp, level, source class
GranularityNoneTRACE, DEBUG, INFO, WARN, ERROR
Recommended useFast debuggingProduction applications

Log levels (log levels)

From most verbose to least verbose:

LevelTypical usage
TRACEVery detailed information, the most output
DEBUGDebug Info
INFOGeneral information about the process
WARNNon-blocking warnings
ERRORActual errors, used least often

Logging frameworks provide a standard way to manage the information that goes into your logs, and allow you to control what is displayed through the use of log levels.


4.2 Fix common exceptions

Runtime exceptions in Java are not checked by the compiler. The code may compile perfectly, but a problem may occur only at runtime, and sometimes only under certain conditions.

Most common runtime exceptions

ExceptionTypical cause
NullPointerExceptionCalling a method or accessing a field on a null reference
ArrayIndexOutOfBoundsExceptionAccessing a non-existent array index
IndexOutOfBoundsExceptionAccessing an out-of-bounds index of a collection
ArithmeticException: / by zeroDivision by zero

These exceptions are among the most common during debugging because they only appear at runtime.


4.3 Demo: Find bugs using stack traces

This demo uses the getTopOrders method to demonstrate several common runtime exceptions and how to fix them.

Bug 1 — NullPointerException

Symptom: Exception thrown when calling toString() on a null object.

Cause: Initializing the loop to i = 1 (off-by-one error) caused topOrders[0] to remain null.

Fix utility method out:

// Version robuste de la méthode out
public static void out(Object o) {
    System.out.println(o); // println gère déjà le null
}

Note: System.out.println already converts the object to a string and handles null values ​​— so an explicit call to toString() is not necessary.

Bug 2 — ArithmeticException: / by zero

Symptom: Exception thrown when calculating topPercent when totalOrders = 0.

// BUG : division par zéro si totalOrders = 0
double topPercent = (double)topSize / (double)totalOrders;

Fix — protect against division by zero:

double topPercent = 0;
if (totalOrders != 0) {
    topPercent = (double)topSize / (double)totalOrders;
}

Bug 3 — Integer division (integer division)

Symptom: topPercent is 0 even with topSize = 2 and totalOrders = 3.

Cause: Both operands were int. Integer division in Java truncates fractions.

// BUG : division entière — 2 / 3 = 0 (pas 0.66)
double topPercent = topSize / totalOrders;

Fix — cast to double to get floating point division:

double topPercent = (double)topSize / (double)totalOrders;
// Résultat : 0.6666... (deux tiers)

This bug illustrates what often happens: when looking for an exception (/ by zero), we find another bug (the division did not work as expected).

Bug 4 — IndexOutOfBoundsException

Symptom: Exception thrown when topSize > orderList.length.

Cause: Attempted to access an index that does not exist in the table.

java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3

Correction — validate input:

public String[] getTopOrders(String[] orderList, int topSize, int totalOrders) {
    // Protéger contre un topSize supérieur à la taille de la liste
    if (topSize > orderList.length) {
        topSize = orderList.length;
    }
    // ... reste de la méthode
}

The input must always be validated to ensure it is valid for the code. Business clarifications (business requirements) may be necessary to define the exact expected behavior.


5. What else can go wrong?

This module covers more advanced issues that may arise when running more complex Java programs.

5.1 Dealing with bad configurations

Integration with external systems

As your application grows, you begin to integrate with additional systems. Example: a database to store customer and carrier information.

Managing multiple environments

Most systems have separate environments:

EnvironmentFeatures
DevelopmentMasked/fictitious data (names and account numbers changed to protect privacy)
TestShared environment to validate integration
ProductionReal and precise data

Imagine if your phone number was part of a test that runs multiple times a day — this is why development environments use dummy data.

Configuration via configuration files or environment variables:

  • Development activities must use the development database
  • Production activities must use the production database

First reflexes when debugging a connection problem:

  1. Check environment configuration
  2. Ensure that all integrated systems connect to the correct instances for the environment where they are deployed

Deployment issues (deployment)

Symptom: You make a change to your code, but the application behavior does not change.

Possible cause: An old version of the code is still running.

Possible solutions:

  • Restart application to load new changes
  • Perform a build and deploy
  • Use hot reload tools available in IDEs (changes automatically pushed to running application)

The hot reload shortens the iteration cycle by avoiding having to stop the process, recompile and restart.


5.2 Handle memory exceptions

heap and garbage collection

  • Heap (heap): general amount of memory available for your Java program from the system
  • Can be viewed as a box with some amount of empty space
  • Garbage collection (GC): process that runs to remove unused objects from the heap
  • Can be viewed as sorting the box to remove certain items

OutOfMemoryError

If the heap is completely filled, you will get an OutOfMemoryError.

Common causes:

CauseDescriptionSolution
Heap too smallThe allocated memory is not sufficient for the needs of the programIncrease maximum heap size (-Xmx) or run on hardware with more memory
Memory leak (memory leak)Code creating objects that cannot be collected by the GCUse memory profiling tools to find the faulty code
GC too slowGarbage collector not running fast enoughIncrease processing power or memory speed

Real story: A persistent OutOfMemoryError issue in production was resolved by taking the servers out of factory-enabled eco mode (power saving). The application was very intensive and needed all the server’s power for the garbage collector.

stop-the-world event

A stop-the-world GC event is a situation where program execution is suspended until the garbage collector has finished.

Impact:

  • The application may appear to be frozen (frozen)
  • An operation that normally takes 50ms may suddenly take 5 seconds
  • Inconsistent performance may indicate garbage collection issues

Inconsistent performance can be difficult to reproduce: when the bug was reported, the GC may have been running, but when you attempted to reproduce, the operation completed within the expected time.


6. Increase productivity with an IDE

6.1 Use IDE features to debug faster

IDEs (Integrated Development Environments) have many tools that make it easier to write and debug code.

Preventive features (avoid bugs)

Real-time syntax checking (real-time syntax checking)

The IDE runs real-time compiler checks while typing:

  • Highlighting unclosed parentheses and braces
  • Reporting misspelled variables
  • Detecting syntax errors before even attempting to execute code

Reduces the iteration cycle between writing a bug and fixing it.

Autocomplete (autocomplete)
  • Suggest valid variables in the current context (in scope)
  • Does not suggest out-of-scope variables or incomplete control statements
  • Avoid typing every character, especially useful for long, descriptive variable names

Autocomplete suggestions are syntactically valid, but are not guaranteed to meet the requirements of the written code.

Debug Features

Run mode vs Debug mode
FashionDescription
RunJust run the code
DebugRuns code storing additional information to enable debugging features

To debug, you must always launch in Debug mode.

Breakpoints
TypeDescription
Static breakpointPause execution on a specific line
Conditional BreakpointStops only when a condition is true (eg: i > 3)
Exceptional breakpointTriggers when certain exceptions are thrown
Stepping

After stopping at a breakpoint, you can navigate the code:

ActionDescription
Step OverExecute the current line and move on to the next one
Step IntoEnters the method called on the current line
Step OutExits the current method without having to go through each line
Run to CursorRuns to cursor position
SummaryResumes execution until next breakpoint
Watch variables and expressions
  • Watches: Selected variables automatically update in a dashboard — easy to monitor as they change
  • Expressions: allows you to create expressions in valid Java syntax that are evaluated during debugging (e.g.: topSize * totalOrders)
  • Modification of values: possibility of changing the value of a variable during a debugging session
Launch profiles (launch profiles)

Allows you to configure:

  • The Java SDK to use
  • Program arguments (program arguments)
  • Environment variables (environment variables)
  • Which project or class to launch

Particularly useful if you work on multiple projects and multiple environments.

Reload functionality (reload)

Allows changes to be taken from the source code and introduced into the running session.

  • Compile and Reload File: reloads the modified file in the current debug session
  • Helps quickly iterate on solutions while debugging
  • Limitation: only certain types of changes are supported during reload; for unsupported changes, you must restart the debugging session

6.2 Demo: Debugging in IntelliJ

This demo demonstrates the functionality shown using the IntelliJ IDEA IDE.

Note: This is an overview of the debugging tools and not an exhaustive demo of all possible options in the debugger.

Demo — Real-time syntax checking

When adding a calculateShipping method:

  1. The word calculateShipping is highlighted in red — missing return type
  2. After adding the double return type, the closing brace is set to red — missing return statement
  3. Once the method is complete, the red lines disappear

Demo — Autocomplete

When entering the body of the calculateShipping method:

  • IDE suggests distance variable (method parameter, in scope)
  • Pressing Enter inserts suggestion automatically
  • costFactor variable is also suggested

Autocompletion is especially useful with long, descriptive variable names — no need to type every character.

Demo — Launch Profiles

The launch profiles menu remembers each class for which a main method has been executed, allowing these classes to be restarted at any time without navigating to the file.

Demo — Breakpoint on NullPointerException

  1. The IDE offers to create a breakpoint for the NullPointerException during execution
  2. Clicking on the suggestion opens the Breakpoints menu
  3. A breakpoint on Java Exception Breakpoints is created for NullPointerException
  4. Run in Debug mode
  5. Execution stops on the line where the exception is thrown
  6. Fix the problem, then restart the process

Demo — Static Breakpoint and Step-by-Step Navigation

// Cliquer dans la gouttière (gutter) à gauche de la ligne
// Un point rouge apparaît et la ligne est surlignée

Navigation:

  • Step Over: executes one line at a time
  • Run to Cursor: useful for quickly passing a loop
  • Step Into: enters the called method (e.g.: calculateShipping)
  • Step Out: exits the method without going through each line
  • Resume: resumes until the next breakpoint

Demo — Conditional Breakpoint

// Condition : i > 3
// Avec topSize = 3 : le breakpoint ne s'arrête pas (i ne dépasse jamais 3)
// Avec topSize = 5 : le breakpoint s'arrête quand i = 4

Creating a conditional breakpoint:

  • Create a standard breakpoint (click in the gutter)
  • Right click on the breakpoint to open its properties
  • Enter any valid Java expression in the condition field

Demo — Variables and expressions in the Debug window

  • Variable values appear next to running lines of code
  • The Debug window displays the list of variables (e.g.: topSize, totalOrders)
  • Add a watch: enter i in the list of watches to see it at the top of the list
  • Create an expression: enter topSize * totalOrders, press Enter — the result is displayed

Demo — Change a value while debugging

Select a variable (eg: in orderList) and set a new value — this value is now changed for this execution.

Demo — Compile and Reload File

To modify code during a debug session:

  1. Right click in the source file
  2. Select Compile and Reload File
  3. Check the message confirming that the class has been reloaded

Only certain changes are supported by this feature. For unsupported changes, restart the debug session.


6.3 Summary

This training covered:

  1. The different types of bugs and how some are not caused by bad code (configuration issues, external dependencies)

  2. Common errors in Java:

  • off-by-one errors in loops
  • Operator = vs ==
  • switch without break
  • Case sensitivity of variables
  • Variable scope
  1. The importance of code readability to facilitate debugging (descriptive names, organization, coding standards)

  2. Reading stack traces to determine the error and where to look in the code

  3. Garbage collection: concept of cleaning up memory space in the heap where the Java code executes

  4. IDE tools for:

  • Avoid writing bugs (real-time checking, autocompletion)
  • Debug more efficiently (breakpoints, stepping, variable watches, expressions, reload)

7. Code files — Globomantics Project

The examples in this training use a fictitious order processing system for the company Globomantics.

Project structure:

materials/
└── src/
    └── main/
        └── java/
            └── com/
                └── globomantics/
                    ├── orders/
                    │   ├── InboundOrder.java
                    │   └── OrderProcess.java
                    ├── shipping/
                    │   └── OutboundProcess.java
                    └── slides/
                        ├── Bucket.java
                        └── Samples.java

Note: Samples.java is used for training slides and is not really part of a working system. Some files contain intentional bugs created for demonstration purposes. The files presented here represent the final corrected version after the demos.


7.1 OutboundProcess.java

Manages outbound order processing, including assigning carrier and recording shipping charges.

package com.globomantics.shipping;

public class OutboundProcess {

  public static void main(String[] args) {
    OutboundProcess testOutboundProcess = new OutboundProcess();
    boolean freeShipping = false;
    out("free Shipping: " + freeShipping);
    testOutboundProcess.assignCarrier(freeShipping);
  }

  public void assignCarrier(boolean freeShipping) {
    if (freeShipping) {
      out("Use special carrier.");
    } else {
      out("Use regular carrier.");
    }
    recordShippingCharge(freeShipping);
  }

  public void recordShippingCharge(boolean freeShipping) {
    if (!freeShipping) {
      out("Add $5 shipping charge");
    } else {
      out("No shipping charge");
    }
  }

  public static void out(Object o) {
    System.out.println(o.toString());
  }

}

Key points:

  • assignCarrier(boolean freeShipping): directly uses the freeShipping boolean in the if statement (corrected version — no = operator vs ==)
  • recordShippingCharge(boolean freeShipping): uses the ! operator for the negation of the boolean
  • out(Object o): display utility method

7.2 InboundOrder.java

Manages inbound order processing, including application of tags based on customer type.

package com.globomantics.orders;

public class InboundOrder {

  public static void main(String[] args) {
    InboundOrder testInboundOrder = new InboundOrder();
    String customerType = "silver";
    out("customer type: " + customerType);
    testInboundOrder.tagOrder(customerType);
  }

  public void tagOrder(String customerType) {
    switch (customerType) {
      case "bronze" -> out("Tag: level2");
      case "silver" -> out("Tag: level3");
      case "gold"   -> out("Tag: level4");
      default       -> out("Tag: level1");
    }
  }

  public static void out(Object o) {
    System.out.println(o.toString());
  }

}

Key points:

  • tagOrder(String customerType): final version using arrow syntax (->) for the switch
  • No break needed with arrow syntax — no fall-through possible
  • Client type default → tag level1

Tag levels by customer type:

Customer TypeTag applied
bronzelevel2
silverlevel3
goldlevel4
Other (default)level1

7.3 OrderProcess.java

Manages order processing, including retrieving top orders and calculating shipping costs.

package com.globomantics.orders;

public class OrderProcess {

  public String[] getTopOrders(String[] orderList, int topSize, int totalOrders) {
    if (topSize > orderList.length){
      topSize = orderList.length;
    }
    String[] topOrders = new String[topSize];
    for (int i = 0; i < topSize; i++) {
      topOrders[i] = orderList[i];
    }
    //newMethod();
    double topPercent = 0;
    if (totalOrders != 0){
      topPercent = (double)topSize / (double)totalOrders;
    }
    out(topPercent);
    calculateShipping(5, .1);
    return topOrders;
  }

  public void newMethod(){
    out("In new method");
  }

  public double calculateShipping(double distance, double costFactor){
    double result = distance * costFactor;
    return result;
  }

  public static void main(String[] args) {
    String[] orderList = {"shirt", "pants", "shoes", "socks", "belt", "coat", "gloves"};
    int topSize = 5;
    int totalOrders = 7;
    OrderProcess orderProcess = new OrderProcess();
    String[] topList = orderProcess.getTopOrders(orderList, topSize, totalOrders);

    for (String order: topList) {
      out(order);
    }
  }

  public static void out(Object o) {
    System.out.println(o);
  }

}

Key points:

  • getTopOrders(String[], int, int) — final version corrected with:
  • Validation: topSize > orderList.length → adjustment to the size of the list
  • Loop starting at i=0 (fixed from i=1)
  • Protection against division by zero: if (totalOrders != 0)
  • Cast to (double) for floating point division: (double)topSize / (double)totalOrders
  • calculateShipping(double distance, double costFactor): demonstration of Step Into in the IDE demos
  • out(Object o): uses System.out.println(o) — no need for explicit call to toString() because println handles it

7.4 Bucket.java

Example class to illustrate the difference between .equals() and ==.

package com.globomantics.slides;

import java.util.Objects;

public class Bucket {
  private int size;
  private String color;

  public Bucket(int size, String color) {
    this.size = size;
    this.color = color;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Bucket bucket = (Bucket) o;
    return size == bucket.size && Objects.equals(color, bucket.color);
  }

  public int getSize() { return size; }
  public void setSize(int size) { this.size = size; }
  public String getColor() { return color; }
  public void setColor(String color) { this.color = color; }

  @Override
  public int hashCode() {
    return Objects.hash(size, color);
  }
}

Key points:

  • Implemented equals(Object o) to compare size and color by value
  • Using Objects.equals(color, bucket.color) to handle null values securely
  • Implementation of hashCode() with Objects.hash(size, color) — good practice: always redefine hashCode() when redefining equals()
  • Check this == where first for optimization (same reference → automatically equal)

Usage demonstration:

Bucket one = new Bucket(10, "red");
Bucket two = new Bucket(10, "red");
Bucket three = one;

one.equals(two) // true  — même taille et couleur (comparaison par valeur)
one == two      // false — instances différentes (comparaison par référence)
one == three    // true  — même référence

7.5 Samples.java

Demo class used for training slides. Illustrates different code patterns and their behaviors.

package com.globomantics.slides;

public class Samples {

  public static void main(String[] args) {
    // Méthode main vide — point d'entrée pour les tests
  }

  // --- Text blocks ---

  public void stringExample(){
    // Ancienne méthode (concaténation avec \n)
    String multi1 =
        "this is an example\n" +
            "of a multi-line string\n" +
            "be sure to remember newlines\n";

    // Nouvelle méthode avec text blocks (Java 13+)
    String multi2 =
        """
        this is another way 
        to create a multi-line string.
        It's easier to create and edit.
        """;
  }

  // --- Switch statements ---

  // Switch avec break — correct
  public void switchSample1(){
    String color = "red";
    switch (color) {
      case "red":
        System.out.println("In red");
        break;
      case "orange":
        System.out.println("In orange");
        break;
      default:
        System.out.println("In default");
        break;
    }
  }

  // Switch sans break — BUG (fall-through)
  public void switchSample2(){
    String color = "red";
    switch (color) {
      case "red":
        System.out.println("In red");    // fall-through vers tous les cases suivants !
      case "orange":
        System.out.println("In orange");
      default:
        System.out.println("In default");
    }
  }

  // Switch avec syntaxe flèche — pas de fall-through
  public void switchSample3(){
    String color = "red";
    switch (color) {
      case "red"    -> System.out.println("In red");
      case "orange" -> System.out.println("In orange");
      default       -> System.out.println("In default");
    }
  }

  // --- If statements ---

  // Démonstration de l'opérateur = (assignation) vs == (comparaison)
  public void ifWithAssignmentOperator(){
    boolean a = true;
    boolean b = false;
    if (a = b) { // BUG : assigne b à a — a vaut maintenant false !
      doTrue();
    } else {
      doFalse(); // S'exécute TOUJOURS
    }
    // a vaut maintenant false (effet de bord)
  }

  public void ifWithEqualityOperator(){
    boolean a = true;
    boolean b = false;
    if (a == b) { // Correct : compare a et b
      doTrue();
    } else {
      doFalse(); // S'exécute car a != b
    }
    // a vaut toujours true (pas d'effet de bord)
  }

  // --- Equals vs == ---

  public void equalsTest() {
    Bucket one = new Bucket(10, "red");
    Bucket two = new Bucket(10, "red");

    one.equals(two) // true  — comparaison par valeur (size et color)
    one == two      // false — instances différentes

    Bucket three = one;
    one == three    // true  — même référence
  }

  // --- Nommage : abréviation vs descriptif ---

  // Mauvais — noms abrégés, difficile à comprendre
  public double calcTot(double prc, double rt, double dr){
    double disc = prc * dr;
    double subTot = prc - disc;
    double tx = subTot * rt;
    double tot = subTot + tx;
    return tot;
  }

  // Bon — noms descriptifs, code auto-documenté
  public double calculateTotal(double price, double taxRate, double discountRate){
    double discount = price * discountRate;
    double subTotal = price - discount;
    double tax = subTotal * taxRate;
    double total = subTotal + tax;
    return total;
  }

  private void doTrue() { System.out.println("true"); }
  private void doFalse() { System.out.println("false"); }
}

Concepts illustrated:

MethodIllustrated concept
stringExample()Text blocks Java 13+ vs concatenation with \n
switchSample1()Standard switch with break — correct behavior
switchSample2()Switch without breakfall-through behavior (bug)
switchSample3()Switch with arrow syntax — no fall-through
ifWithAssignmentOperator()Bug: = instead of == in an if + side effect
ifWithEqualityOperator()Correct: == in an if — without side effects
equalsTest()Difference between .equals() (value) and == (reference)
calcTot()Bad naming — incomprehensible abbreviations
calculateTotal()Good naming — descriptive names, readable code


Search Terms

java · debugging · backend · architecture · full-stack · web · bug · bugs · errors · stack · debug · fix · scenario · variable · breakpoint · exceptions · features · syntax · trace · variables · assignment · autocomplete · avoid · break

Interested in this course?

Contact us to book it or get a custom training plan for your team.