Java
Java.util.logging
Programming
Code Examples
Software Development

Good examples using java.util.logging

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

java.util.logging, usually shortened to JUL, is Java's built-in logging framework. It is not the most fashionable logging API in modern Java stacks, but it is still widely used in JDK code, smaller applications, and environments where keeping dependencies minimal matters.

Good JUL examples are not just about calling logger.info(...). They show how to choose loggers, levels, handlers, and message patterns in a way that stays maintainable.

A Clean Basic Logger

The normal starting point is a class-level logger:

java
1import java.util.logging.Logger;
2
3public class BillingService {
4    private static final Logger LOGGER =
5            Logger.getLogger(BillingService.class.getName());
6
7    public void charge(String orderId) {
8        LOGGER.info("Charging order " + orderId);
9    }
10}

This is enough for many small applications. The logger name follows the class name, which fits JUL's hierarchy model.

Use Levels Deliberately

JUL supports levels such as SEVERE, WARNING, INFO, CONFIG, FINE, FINER, and FINEST.

A practical example:

java
1import java.util.logging.Level;
2import java.util.logging.Logger;
3
4public class ImportJob {
5    private static final Logger LOGGER =
6            Logger.getLogger(ImportJob.class.getName());
7
8    public void run() {
9        LOGGER.info("Import started");
10        LOGGER.warning("Input file contains deprecated field names");
11        LOGGER.log(Level.SEVERE, "Import failed");
12    }
13}

The point is not to memorize every level. It is to use levels consistently so operators can filter noise from real incidents.

Prefer Parameterized Logging

Instead of concatenating strings eagerly, JUL supports parameterized messages:

java
1import java.util.logging.Logger;
2
3public class UserService {
4    private static final Logger LOGGER =
5            Logger.getLogger(UserService.class.getName());
6
7    public void loadUser(String userId) {
8        LOGGER.log(java.util.logging.Level.INFO,
9                "Loading user {0}", userId);
10    }
11}

This is cleaner for structured message templates and avoids some unnecessary string construction.

If you are on newer Java versions, supplier-based logging is also useful for expensive messages:

java
LOGGER.fine(() -> "Detailed debug info: " + computeExpensiveDebugText());

That message is only constructed if the FINE level is enabled.

Add a File Handler

JUL becomes much more practical once you understand handlers:

java
1import java.io.IOException;
2import java.util.logging.FileHandler;
3import java.util.logging.Logger;
4import java.util.logging.SimpleFormatter;
5
6public class FileLoggingExample {
7    private static final Logger LOGGER =
8            Logger.getLogger(FileLoggingExample.class.getName());
9
10    static {
11        try {
12            FileHandler fileHandler = new FileHandler("app.log", true);
13            fileHandler.setFormatter(new SimpleFormatter());
14            LOGGER.addHandler(fileHandler);
15            LOGGER.setUseParentHandlers(false);
16        } catch (IOException e) {
17            throw new RuntimeException(e);
18        }
19    }
20}

setUseParentHandlers(false) matters here. Without it, the message may go both to the custom file handler and to inherited console handlers.

Handle Exceptions Properly

When logging exceptions, log the throwable object instead of flattening it to a string:

java
1try {
2    throw new IllegalStateException("bad state");
3} catch (Exception ex) {
4    LOGGER.log(java.util.logging.Level.SEVERE,
5            "Request processing failed", ex);
6}

This preserves the stack trace and makes the log useful for debugging.

Configuration Can Be External

JUL also supports external configuration through logging.properties. A small example:

properties
1handlers= java.util.logging.ConsoleHandler
2.level= INFO
3java.util.logging.ConsoleHandler.level = INFO
4java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
5com.example.level = FINE

This is helpful when you want logging behavior to change between environments without recompiling code.

Common Pitfalls

The biggest pitfall is mixing custom handlers with inherited parent handlers and then wondering why each message appears twice.

Another common mistake is using string concatenation everywhere for debug messages, even when the level may be disabled. Parameterized or supplier-based logging is better.

People also log exceptions as plain text and lose the stack trace. If you have the throwable, pass it to LOGGER.log(...).

Finally, JUL works best when logger names and level usage follow a consistent hierarchy. Random logger naming quickly becomes hard to manage.

Summary

  • Use one logger per class with a stable class-based name.
  • Choose log levels intentionally and consistently.
  • Prefer parameterized or supplier-based logging over eager string concatenation.
  • Add handlers deliberately and disable parent handlers when duplication is unwanted.
  • Log exceptions with the throwable object so stack traces are preserved.

Course illustration
Course illustration

All Rights Reserved.