16.2 Java Logging API Basics: Logger, Levels, Handlers, and Configuration

Key Takeaways

  • java.util.logging defines seven standard levels from SEVERE (1000) down to FINEST (300), plus OFF and ALL.

  • With the default JDK configuration the root logger and its ConsoleHandler use INFO, so fine() messages are not printed.

  • A log record must pass the logger's level and then each handler's level, so lowering only the logger level to FINE still hides FINE output.

  • Records also go to parent handlers unless setUseParentHandlers(false) is called, which is why an added handler can print duplicate lines.

  • The java.util.logging.config.file system property loads a logging.properties file with entries such as .level=INFO and com.shop.level=FINE.

Last updated: October 2026

16.2 Java Logging API Basics: Logger, Levels, Handlers, and Configuration

Oracle also expects candidates to "understand the basics of Java Logging API." The standard API is java.util.logging in the java.logging module, so a modular application needs requires java.logging;. The exam-level skills are obtaining a logger, choosing levels, predicting which messages print, and reading a basic configuration.


Obtaining and Using a Logger

import java.util.logging.Level;
import java.util.logging.Logger;

public class OrderService {
    private static final Logger LOG = Logger.getLogger(OrderService.class.getName());

    void place(int id, int tries) {
        LOG.info("Placing order " + id);
        LOG.log(Level.WARNING, "Order {0} failed after {1} tries", new Object[] {id, tries});
        LOG.fine(() -> "Expensive debug text " + id);   // Supplier: built only if FINE is enabled
        try {
            throw new IllegalStateException("payment declined");
        } catch (IllegalStateException e) {
            LOG.log(Level.SEVERE, "Order failed", e);    // prints the message plus the stack trace
        }
    }
}
  • Logger.getLogger(name) returns the logger for a dot-separated name, usually the class name. Calling it again with the same name returns the same instance (log == Logger.getLogger("com.shop.Orders") is true while the logger is in use).
  • Loggers form a hierarchy by name: com.shop.Orders is a child of com.shop, and the root logger has the empty name "".
  • Convenience methods exist for each level (severe, warning, info, config, fine, finer, finest), plus log(Level, String), log(Level, String, Object[]) with {0}-style parameters, and log(Level, String, Throwable).
  • isLoggable(Level) tells you whether a message at that level would pass the logger's level check.

The Seven Standard Levels

LevelintValue()Typical use
SEVERE1000Serious failures
WARNING900Potential problems
INFO800Informational messages (the default threshold)
CONFIG700Configuration details
FINE500Tracing information
FINER400Detailed tracing
FINEST300Most detailed tracing

Level.OFF (Integer.MAX_VALUE) disables logging, and Level.ALL (Integer.MIN_VALUE) enables everything. A message is published when its level value is greater than or equal to the threshold.


Why fine() Prints Nothing by Default

With the JDK's default configuration, the root logger's level is INFO and it has one ConsoleHandler, also at level INFO, that writes to System.err. A new named logger has a null level, so it inherits the effective level INFO from its parent. As a result, info, warning, and severe messages appear, while config, fine, finer, and finest messages do not.

Every message must pass two filters:

  1. The logger's level (its own or the inherited one).
  2. The handler's level for each handler that receives the record.
Logger log = Logger.getLogger("com.shop.Orders");
log.setLevel(Level.FINE);
log.fine("A");                          // still NOT shown: the root ConsoleHandler is INFO

ConsoleHandler handler = new ConsoleHandler();
handler.setLevel(Level.FINE);
log.addHandler(handler);
log.info("B");                          // printed TWICE: by this handler and by the root handler

log.setUseParentHandlers(false);
log.fine("C");                          // printed once, by this handler only

Records travel up the hierarchy to the parent's handlers unless setUseParentHandlers(false) is called. Forgetting that step is the usual cause of duplicate log lines.


Configuring Logging Without Code

The default settings come from the JDK's logging.properties file. You can point the JVM at your own file with a system property:

java -Djava.util.logging.config.file=logging.properties -cp app.jar com.shop.Main
handlers=java.util.logging.ConsoleHandler, java.util.logging.FileHandler
.level=INFO
com.shop.level=FINE
java.util.logging.ConsoleHandler.level=FINE
java.util.logging.FileHandler.pattern=%h/shop%u.log
  • .level sets the root logger level; com.shop.level sets the level for the com.shop logger and its children.
  • Each handler has its own level property, which is the second filter described above.
  • FileHandler writes to files (its pattern can use %h for the user's home directory and %u for a unique number), and ConsoleHandler writes to System.err.

System.Logger: The Platform Logging Facade

Java 9 added System.getLogger(name), which returns a System.Logger with its own levels (ALL, TRACE, DEBUG, INFO, WARNING, ERROR, OFF). By default it is backed by java.util.logging, so System.getLogger("sys").log(System.Logger.Level.INFO, "started") prints through the same console handler. Libraries use it to log without choosing a framework for the application.


How a Log Record Flows

StepComponentWhat can stop the record
1LoggerThe record's level is below the logger's effective level
2Logger Filter (optional)setFilter(...) rejects the record
3Each Handler on this logger, then on each parent (unless setUseParentHandlers(false))The record's level is below that handler's level, or the handler's own filter rejects it
4The handler's FormatterNothing; it only turns the record into text (SimpleFormatter by default, XMLFormatter for FileHandler)

The default SimpleFormatter prints two lines per record: a timestamp with the source class and method, then LEVEL: message. Its layout comes from the java.util.logging.SimpleFormatter.format property; for example, the pattern %4$s: %5$s%n prints only the level and the message, such as INFO: formatted.

Logging Best Practices the Exam Rewards

  • Avoid building text that is never logged. LOG.fine("Order " + id) builds the string even when FINE is disabled; prefer LOG.fine(() -> "Order " + id), a {0} parameter, or an if (LOG.isLoggable(Level.FINE)) guard.
  • Log the exception object, not just its message. LOG.log(Level.SEVERE, "Order failed", e) keeps the stack trace; logging e.getMessage() loses it.
  • Do not log and rethrow at every layer. Logging the same exception in each method as it propagates produces duplicate noise; log it once where it is handled.
  • Never log secrets. Passwords read with Console.readPassword() (see 14.1) or tokens must not reach log files.
  • Use named loggers. Logger.getLogger(MyClass.class.getName()) lets configuration target a package. Logger.getGlobal() returns the logger named global, and Logger.getAnonymousLogger() returns an unregistered logger whose name is null.
Test Your Knowledge

Using the JDK's default logging configuration, which lines appear on the console?

Logger log = Logger.getLogger("com.app.Main");
log.config("one");
log.info("two");
log.fine("three");
log.warning("four");
A

all four lines

B

four only

C

two and four only

D

one, two, and four

Test Your Knowledge

With the default configuration, a developer runs:

Logger log = Logger.getLogger("com.app.Repo");
log.setLevel(Level.FINE);
log.fine("query plan");

Why is nothing printed?

A

Because setLevel takes effect only after the JVM restarts

B

Because fine() requires a Supplier argument instead of a String

C

Because the logger name must match a class that is already loaded

D

Because the root ConsoleHandler still discards records below INFO

Test Your Knowledge

What is printed by the following code under the default logging configuration?

Logger log = Logger.getLogger("com.app.Svc");
ConsoleHandler h = new ConsoleHandler();
log.addHandler(h);
log.info("ready");
A

The ready message twice, from the new handler and the root handler

B

The ready message once, because the new handler replaces the root handler

C

An IllegalStateException, because a logger can have only one handler

D

Nothing, because a new ConsoleHandler starts at level OFF

Sections you finish are checked off in the contents.

Congratulations!

You've completed this section

Continue exploring other exams