Course topics

By WebNest Studio Engineering Team

Advanced Java Tutorial

Logging in Java Applications

Every serious Java application needs a way to record what it is doing — not for the developer sitting at a terminal, but for diagnosing problems in production, days or weeks after an incident, when no debugger is attached. System.out.println feels convenient during learning, but it is unsuitable for real applications: it cannot be turned off selectively, it always writes to standard output with no timestamps or severity information, it cannot be routed to a file or a log-aggregation system, and it has no concept of which part of the application produced the message.

A proper logging framework solves all of this: it lets you tag each message with a severity level, filter what gets recorded without changing code, attach context like timestamps and thread names, and send output to multiple destinations (console, rotating files, remote log collectors) at once.

Why it matters

When a production system misbehaves at three in the morning, the logs are the only account of what happened. System.out.println cannot be filtered by severity, switched off or routed to a file without changing code. A logging framework does all of this by configuration, and every professional Java application uses one.

Log Levels

Logging frameworks categorize messages by severity, and each logger has a configured threshold — only messages at or above that threshold are actually recorded, everything below is silently dropped.

  • TRACE — extremely fine-grained detail, rarely enabled outside of deep debugging sessions.
  • DEBUG — diagnostic information useful during development, such as variable values and control-flow decisions.
  • INFO — high-level events confirming the application is working as expected (a service started, a job completed).
  • WARN — something unexpected happened, but the application can continue running.
  • ERROR — a serious problem occurred, typically an operation failed and needs attention.

java.util.logging (JUL)

Java ships with a built-in logging API, java.util.logging, available in every JDK with no extra dependency. You obtain a Logger via Logger.getLogger(ClassName.class.getName()) and call methods like .info(), .warning(), and .severe(). It works, but its configuration API is verbose, its default output format is unpopular with many teams, and its feature set (log rotation, structured output, integrations) lags behind third-party alternatives.

SLF4J and Logback: The Practical Standard

In real-world Java projects, the de facto standard is to code against SLF4J (Simple Logging Facade for Java) — a thin, framework-agnostic logging API — and plug in Logback as the actual implementation that writes the logs. This separation matters: your code and any libraries you use only depend on the SLF4J interface, so you can swap the underlying logging implementation (Logback, Log4j2, or others) without touching application code. SLF4J also supports parameterized messages (logger.info("User {} logged in", userId)), which avoid the cost of string concatenation when a log level is disabled.

Example

java.util.logging vs. SLF4J + Logback for the same event

Java
import java.util.logging.Logger;

public class BuiltInLoggingDemo {
    private static final Logger logger = Logger.getLogger(BuiltInLoggingDemo.class.getName());

    public static void main(String[] args) {
        logger.info("Application starting");
        logger.warning("Cache miss for key: user_42");
    }
}

// --- With SLF4J + Logback (typical dependency: org.slf4j:slf4j-api, ch.qos.logback:logback-classic) ---

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

class OrderService {
    private static final Logger logger = LoggerFactory.getLogger(OrderService.class);

    void processOrder(String orderId) {
        logger.info("Processing order {}", orderId);
        try {
            // ... order logic ...
        } catch (Exception e) {
            logger.error("Failed to process order {}", orderId, e);
        }
    }
}
Output
Sep 21, 2026 10:02:11 AM BuiltInLoggingDemo main
INFO: Application starting
Sep 21, 2026 10:02:11 AM BuiltInLoggingDemo main
WARNING: Cache miss for key: user_42

10:02:11.412 [main] INFO  OrderService - Processing order ORD-1001

Common Mistakes

  • Leaving System.out.println calls scattered through production code with no way to disable or filter them.
  • Logging at ERROR level for routine, expected conditions, which trains teams to ignore alerts ("alert fatigue").
  • Building log messages with string concatenation ("User " + id + " logged in") instead of parameterized messages, doing unnecessary work even when the log level is disabled.
  • Logging sensitive data (passwords, full credit card numbers, personal information) in plain text log files.

Exercise

Using java.util.logging, create a logger whose level is INFO and whose handler writes to standard output in the form LEVEL: message. Log one message at each of the levels FINE, INFO and WARNING. The FINE message must not appear.

Expected output: INFO: started then WARNING: disk almost full

Java · starter code
import java.util.logging.Formatter;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.logging.StreamHandler;

public class LogLevels {
    public static void main(String[] args) {
        Logger logger = Logger.getLogger("app");
        logger.setUseParentHandlers(false); // do not also print through the default handler

        StreamHandler handler = new StreamHandler(System.out, new Formatter() {
            @Override
            public String format(LogRecord record) {
                // TODO: return "LEVEL: message" followed by a line separator
                return "";
            }
        });
        logger.addHandler(handler);

        // TODO: set the logger's level to INFO

        logger.fine("details for developers");
        logger.info("started");
        logger.warning("disk almost full");

        handler.flush();
    }
}
Show hints
  • The record provides getLevel() and getMessage(); end the line with System.lineSeparator().
  • logger.setLevel(Level.INFO) discards anything less severe, such as FINE.
Show solution
Java · solution
import java.util.logging.Formatter;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.logging.StreamHandler;

public class LogLevels {
    public static void main(String[] args) {
        Logger logger = Logger.getLogger("app");
        logger.setUseParentHandlers(false); // do not also print through the default handler

        StreamHandler handler = new StreamHandler(System.out, new Formatter() {
            @Override
            public String format(LogRecord record) {
                return record.getLevel() + ": " + record.getMessage() + System.lineSeparator();
            }
        });
        logger.addHandler(handler);

        logger.setLevel(Level.INFO);

        logger.fine("details for developers");
        logger.info("started");
        logger.warning("disk almost full");

        handler.flush();
    }
}

Quick quiz

1. What is SLF4J?

  • A. A database driver
  • B. A build tool
  • C. A logging facade: an API that application code calls, with the actual logging done by an implementation such as Logback
  • D. A test framework
Show answer

C. The implementation can be changed without touching the code that logs.

2. A logger is set to the level WARN. Which messages are written?

  • A. WARN and ERROR
  • B. All messages
  • C. DEBUG and INFO
  • D. Only WARN
Show answer

A. A logger writes messages at its own level and above.

3. Why write log.debug("User {} logged in", name) instead of joining strings with +?

  • A. It is required by the compiler
  • B. The message is built only if debug logging is enabled, saving work when it is not
  • C. It encrypts the name
  • D. It writes to a different file
Show answer

B. With concatenation the string is built every time, even when the message is then discarded.

Interview questions

What are the standard log levels and when is each used?

Show answer

ERROR: something failed and needs attention. WARN: something unexpected happened, but the application carried on. INFO: significant normal events, such as start-up or a completed job. DEBUG: detail useful while developing or diagnosing. TRACE: very fine detail. Production usually runs at INFO, with DEBUG enabled temporarily for a package under investigation.

What should never be written to logs?

Show answer

Passwords, authentication tokens, API keys, full card numbers and other personal data. Logs are stored for a long time, copied to other systems and read by many people, so anything written there is effectively disclosed. Sensitive values are omitted or masked, and identifiers such as a user id or a request id are logged instead, which is enough to trace what happened.

Key Points to Remember

  • System.out.println lacks severity levels, filtering, timestamps, and configurable output destinations — unsuitable for production.
  • Standard levels from least to most severe: TRACE, DEBUG, INFO, WARN, ERROR.
  • java.util.logging is built into the JDK but is less feature-rich than third-party alternatives.
  • SLF4J (facade) + Logback (implementation) is the practical industry standard for Java logging.

Practice in the playground

Write and run your own code for this topic, or start from the exercise or an example. Runs plain Java 17. Frameworks such as Spring, and databases, are not available here. Your code for each topic is kept in this browser.

Run the examples

Change an input, predict the result, then compare it with the output. Explain why the result changes.

These examples need a JDK project with their libraries on your computer, so they can be copied but not run here. Codelab runs Python, FastAPI backends with SQLite, plain Java 17 and HTML/CSS/JavaScript.

WebNest Studio

Written by

WebNest Studio Engineering Team

Practical lessons from developers working with Java, Python, React, databases and production systems.

Led by Vansh Duggal, Founder, WebNest Studio · Full-stack & software engineering