Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Java  Module 14 – Testing, build tools and code quality

Debugging techniques and logging

Find Java bugs with conditional, exception and field breakpoints in jdb, log with System.Logger and java.util.logging, and keep secrets out of logs.

  • Intermediate
  • 26 minutes
  • Examples run with Java 25.0.4 and Python 3.14.8
  • By MySmartCoPilot
Free core, Pro depth: 9 of 26 minutes free
Free: A debugger or logs · Breakpoints beyond the line, by name · Logging with System.Logger and java.util.logging · Check yourself. With a pass: 3 in-depth parts and 3 interview answers, about 17 minutes.

What you will learn

  • Choose between a debugger and logs for a given bug, and debug with conditional, exception and field breakpoints
  • Log with System.Logger and java.util.logging, and set levels, handlers and formats in logging.properties
  • Write log lines that cost nothing when disabled and help operations without leaking secrets or personal data

Before you start

What you’ll learn · Before you start

What you will learn

  • Choose between a debugger and logs for a given bug, and debug with conditional, exception and field breakpoints
  • Log with System.Logger and java.util.logging, and set levels, handlers and formats in logging.properties
  • Write log lines that cost nothing when disabled and help operations without leaking secrets or personal data

Before you start

On this page

Debugging techniques are the ways to learn why a program does what it does. The first step is always to reproduce the failure. Then either watch it happen with a debugger, which pauses a running JVM at breakpoints, or read what the program recorded about itself in its logs. A breakpoint can stop on a line, but also only when a condition holds, when an exception is thrown or when a field changes. Logging records events while the program runs, at levels from TRACE to ERROR, so that someone can understand a failure where no debugger can be attached, such as in production. Java has a logging API of its own, System.Logger, which writes through java.util.logging unless a library supplies another backend. Use the debugger when you can reproduce a bug, and logs for everything you cannot. A log never contains passwords, tokens or personal data.

This lesson builds on the first debugging lesson, which set line breakpoints and stepped through code in an IDE and in jdb, and runs everything on Eclipse Temurin 25.0.4.1.

A debugger or logs

Question A debugger Logs
What it shows What the program is doing now, at one place, with every variable What the program did, over time, everywhere it logs
What it needs A reproduction, and a JVM that you may pause Nothing more, once the log lines are in the code
In production Rarely: a pause stops real work, and a debug port controls the JVM Yes: that is what logs are for
What it costs Every stop is a round trip between two JVMs A disabled line costs almost nothing when written well
What can go wrong A debug port reachable from a network Secrets and personal data copied into log files

The two meet in the middle. A good log line, added while debugging, keeps answering the same question after the bug is fixed. Both debugging techniques and logging work best with a precise question, such as “which order made the total negative?”, rather than “what is wrong?”.

Breakpoints beyond the line, by name

  • A conditional breakpoint carries a condition, such as amount < 0, and pauses only on the passes where it holds, so a loop of 10,000 rounds shows you the one that matters.
  • An exception breakpoint stops where an exception is thrown, before any catch or stack unwinding hides where it came from; jdb calls it catch.
  • A watchpoint stops when a field is read or changed, which finds the line that changed it; jdb calls it watch.
  • A logpoint in an IDE prints a message when the line runs and does not stop at all.
  • Remote debugging attaches a debugger to a JVM started with the JDWP agent, -agentlib:jdwp=…, for example on a test server.
  • A thread dump, from jcmd <pid> Thread.print, shows every thread’s stack at one moment without stopping the program.

Here is a bug hunt with a conditional breakpoint, step by step. A report of 10,000 orders shows a total that is 54,921 paise lower than it should be. A line breakpoint on total += amount would stop 10,000 times. Ask what a wrong total needs: an amount that cannot be right. So set the breakpoint’s condition to amount < 0, run, and the program stops once, with i telling you which order it was. From there, step back to where amount was computed, and the cause is on the screen. If no condition comes to mind, an exception breakpoint or a watchpoint on the suspicious field usually narrows the search the same way.

Logging with System.Logger and java.util.logging

A log record goes from System.Logger to java.util.logging, past the logger level and the handler level, to standard error.Your codeLOG.log(Level.DEBUG, ...)System.LoggerFinderthe JDK's defaultjava.util.logging.Loggerfirst gate: the logger's levelConsoleHandlersecond gate: the handler's levelSimpleFormatterthe format of each lineStandard errorlogging.propertieslevels, handlers, formatSystem.getLogger(name)DEBUG becomes FINE

The path of one log record, and the two levels it must pass

Text description of the diagram

The diagram shows the path of one log record from top to bottom.

  1. Your code calls LOG.log(Level.DEBUG, ...) on a System.Logger that it got from System.getLogger(name).
  2. The JDK's default System.LoggerFinder hands the record to java.util.logging, and DEBUG becomes the java.util.logging level FINE.
  3. The java.util.logging Logger is the first gate: the record goes on only if its level is at or above the logger's level, or the level the logger inherits from its parents.
  4. The ConsoleHandler is the second gate: it has a level of its own, INFO by default, and drops records below it.
  5. The SimpleFormatter turns the record into one line, in the format that logging.properties sets.
  6. The line is written to standard error.

A dashed box, logging.properties, has dashed arrows to the logger and to the handler: that file sets the levels of both, the handlers and the format.

A settlement job and two logging configurations

payments/src/com/example/payments/Settlement.java

package com.example.payments;

import java.lang.System.Logger;
import java.lang.System.Logger.Level;

/// Settles one day of a shop's UPI payments and logs what it does, at every level that System.Logger has.
public class Settlement {
    private static final Logger LOG = System.getLogger(Settlement.class.getName());

    public static void main(String[] args) {
        long[] payments = {49_900, 125_000, 9_900, 310_000};
        long total = 0;
        for (long paise : payments) {
            LOG.log(Level.TRACE, () -> "adding a payment of " + paise + " paise");
            total += paise;
        }
        LOG.log(Level.DEBUG, "total of {0} payments: {1} paise", payments.length, total);
        LOG.log(Level.INFO, "settled {0} paise for shop {1}", total, "S-1042");
        LOG.log(Level.WARNING, "the bank answered after {0} ms; the limit is {1} ms", 2_400, 2_000);
        LOG.log(Level.ERROR, "refund of payment 3 failed", new IllegalStateException("account closed"));
    }
}

payments/logging.properties

# java.util.logging settings, read when the JVM starts with -Djava.util.logging.config.file=logging.properties.
# One handler: the console, which java.util.logging writes to standard error. It passes on every record that the
# loggers let through.
handlers = java.util.logging.ConsoleHandler
java.util.logging.ConsoleHandler.level = ALL
# Level, logger name, message and the exception, if any. No date or time: a platform that collects the console of
# a service adds its own.
java.util.logging.SimpleFormatter.format = %4$-7s %3$s: %5$s%6$s%n
# Every logger: INFO and above.
.level = INFO

payments/logging-debug.properties

# logging.properties, plus one line: the loggers of the payment code also let FINE through, which is System.Logger's
# DEBUG. All other loggers stay at INFO.
handlers = java.util.logging.ConsoleHandler
java.util.logging.ConsoleHandler.level = ALL
java.util.logging.SimpleFormatter.format = %4$-7s %3$s: %5$s%6$s%n
.level = INFO
com.example.payments.level = FINE
The same job with every logger at INFO, then with the payment loggers at FINE Python · logging_session.py
from session import run

# One day's settlement logged through System.Logger and java.util.logging: first with every logger at INFO, then
# with the payment code's loggers at FINE, which is System.Logger's DEBUG.
run(
    """
javac -d out payments/src/com/example/payments/Settlement.java
java -Djava.util.logging.config.file=payments/logging.properties -cp out com.example.payments.Settlement
java -Djava.util.logging.config.file=payments/logging-debug.properties -cp out com.example.payments.Settlement
"""
)

Output

# openjdk version "25.0.4.1" LTS
$ javac -d out payments/src/com/example/payments/Settlement.java
$ java -Djava.util.logging.config.file=payments/logging.properties -cp out com.example.payments.Settlement
INFO    com.example.payments.Settlement: settled 494,800 paise for shop S-1042
WARNING com.example.payments.Settlement: the bank answered after 2,400 ms; the limit is 2,000 ms
SEVERE  com.example.payments.Settlement: refund of payment 3 failed
java.lang.IllegalStateException: account closed
	at com.example.payments.Settlement.main(Settlement.java:20)

$ java -Djava.util.logging.config.file=payments/logging-debug.properties -cp out com.example.payments.Settlement
FINE    com.example.payments.Settlement: total of 4 payments: 494,800 paise
INFO    com.example.payments.Settlement: settled 494,800 paise for shop S-1042
WARNING com.example.payments.Settlement: the bank answered after 2,400 ms; the limit is 2,000 ms
SEVERE  com.example.payments.Settlement: refund of payment 3 failed
java.lang.IllegalStateException: account closed
	at com.example.payments.Settlement.main(Settlement.java:20)

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 logging_session.py

System.getLogger(name) gave the class a logger named after it, and the JDK’s default System.LoggerFinder handed its records to java.util.logging, which read logging.properties at start-up. With every logger at INFO, the first run printed the INFO, WARNING and ERROR lines, the last one with its exception and stack trace. The second run let the loggers of com.example.payments through at FINE and printed the DEBUG line as well. Neither run printed the TRACE lines, and their message suppliers never ran. The {0} placeholders are filled in by java.text.MessageFormat, which wrote 494,800 with the grouping of the run’s locale.

The levels printed are the names of java.util.logging, because that is the backend:

System.Logger level java.util.logging level Use it for
ERROR SEVERE Something failed and someone must act
WARNING WARNING Something unusual that the program handled
INFO INFO What the program did, at a rate that operations can read
DEBUG FINE Detail for developers, off in production
TRACE FINER Every step, off almost always

The mapping is the one in the System.Logger.Level documentation. Logging set up like this costs one line per class and a properties file per environment. Two more rules keep such logs safe to keep and to share: secrets and personal data are left out or masked, and values that users typed are escaped, so that a line break in a name cannot forge a second record.

Secret & Credential Leak Scanner Paste a few hundred lines of a log to look for tokens, keys and passwords before you share it. PII Redactor (Mask Personal Data) Mask phone numbers, emails and ID numbers in a log extract before you attach it to a bug report.

Check yourself

Check yourself

5 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 5 A field of an object ends up with a wrong value, and you do not know which line set it. Which kind of breakpoint finds that line?

    Choose one answer.

    Show the answer to question 1

    Answer: A watchpoint on the field, which stops when the field changes

    A watchpoint stops at the moment a field is changed (or read, if you ask for that), on whatever line does it. jdb calls it watch; IDEs call it a field watchpoint.

  2. Question 2 of 5 Payments fail now and then in production, and nobody can make it happen on a laptop. What helps most?

    Choose one answer.

    Show the answer to question 2

    Answer: Log lines that record what each payment did, at levels operations can switch on

    A debugger needs a reproduction and a JVM you may pause, and production has neither. Logs record what happened where nobody can look, which is exactly this case.

  3. Question 3 of 5 Your code logs with System.Logger at Level.DEBUG, and java.util.logging is the backend. Which level name appears in the output?

    Choose one answer.

    Show the answer to question 3

    Answer: FINE

    System.Logger's DEBUG maps to java.util.logging's FINE, TRACE to FINER and ERROR to SEVERE. The recorded run with logging-debug.properties printed the debug line as FINE.

  4. Question 4 of 5 With logging.properties, every logger at INFO, how many log records did the settlement job print?

    Type a number.

    Show the answer to question 4

    Answer: 3 records

    INFO, WARNING and SEVERE: the TRACE and DEBUG records were below INFO. The SEVERE record took several lines of output because its exception and stack trace were printed with it.

  5. Question 5 of 5 Which of these may never be written to a log in plain text? Choose every one.

    Choose every answer that is right.

    Show the answer to question 5

    Answer:

    • A customer's one-time password
    • An access token for the payment provider

    Tokens and one-time passwords give access to money and accounts, so they never appear in a log. Counts and durations are exactly what operations need.

Breakpoints beyond the line, measured: conditions, exceptions, watchpoints, attaching Pro: opens with a Pro, Premium or Ultimate pass

Every kind of breakpoint is the debugger asking the JVM to stop on some event, and every stop has a price. This part measures that price, uses exception breakpoints and watchpoints in jdb, and attaches to a JVM the way you would on a test server.

In this part:

  • What a conditional breakpoint costs, measured
  • Exception breakpoints and watchpoints in jdb, reproduced
  • Attaching to a running JVM, and keeping the port closed

5 runnable examples (Java, Python) · about 3 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Logging that costs nothing when it is off, and levels a team can rely on Pro: opens with a Pro, Premium or Ultimate pass

A log line is code that runs on every pass, even when its level is switched off. This part measures what a disabled debug line costs in four spellings, reproduces the most common reason a DEBUG line never appears, and sets out what belongs at each level.

In this part:

  • Four ways to write a debug line, measured
  • Two gates, and the DEBUG line that never appears, reproduced
  • A level policy for a payment service

2 runnable examples (Java, Python) · about 3 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

Logs without secrets or forged lines: a leak found by a test, a forgery reproduced Pro: opens with a Pro, Premium or Ultimate pass

Logs are copied to more places than any database: other machines, log services, support tickets, screenshots. This part finds a leaked token with a test, lists what must never be logged, and reproduces a log line forged by a user.

In this part:

  • A leaked token and payment address, found by a test
  • What never goes into a log
  • A forged log line, reproduced
  • Exercise: fix the order service’s logging

6 runnable examples (ExerciseCheck.java, Java, LeakCheck.java, Order.java, OrderService.java, PaymentClient.java, PaymentClient.java, fixed, Python) · about 3 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

  1. Fresher to mid · Warm-up

    What is System.Logger, and how does it relate to java.util.logging?

    System.Logger is the logging interface built into the JDK since Java 9: code gets a logger with System.getLogger(name) and logs at the levels TRACE, DEBUG, INFO, WARNING and ERROR, with plain messages, placeholders or a Supplier that builds the message only when the level is on. It is only the front. A System.LoggerFinder decides where records go, and the JDK's default finder uses java.util.logging as the backend whenever the java.logging module is present. Then DEBUG becomes FINE, TRACE becomes FINER and ERROR becomes SEVERE, and logging.properties sets the levels, handlers and format. A library such as SLF4J can install a finder of its own, so code that logs through System.Logger works with whatever backend the application chose.

  2. Mid to senior

    A batch job processes 2 million records in 3 seconds, and one record comes out wrong. A colleague sets a conditional breakpoint inside the loop, and the job now seems to hang. Why, and how do you find the bad record quickly?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Mid to senior

    Support asks you to switch on DEBUG logging for the payment service in production for a day, to investigate failed payments. What do you check before you do it, how do you do it safely, and what can go wrong?

    Model answer, Pro. First step and follow-ups

  4. Mid to staff

    The security team finds lines in a payment service's logs that look like real refund approvals, but the code never wrote them. How could that happen, how do you stop it, and how would you notice the next attempt?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • Reproduce first, then choose: a debugger for what you can reproduce, logs for what happens where you cannot look.
  • Conditional, exception and field breakpoints stop where the bug is instead of on every pass.
  • System.Logger writes through java.util.logging by default; its DEBUG is FINE and its ERROR is SEVERE.
  • Levels and handlers are set in logging.properties, and secrets and personal data never reach a log.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.