Logging
When your app breaks at 2 AM, your logs are the only witness. By the end of this lesson you'll write structured, level-filtered logs with SLF4J — and know exactly what to record and what to never let near a log file.
Learn Logging in our free Java course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick reference.
Part of the free Java course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
You'll get the most from this if you know Exception Handling (you'll log exceptions here) and Maven & Gradle (SLF4J and Logback are added as dependencies). A little Multithreading helps when we reach MDC.
💡 Analogy: Think of a plane's flight recorder (the "black box"). It quietly notes what happened, at what level of importance, with a timestamp — so that after something goes wrong, investigators can replay events and find the cause.
System.out.println() is like shouting in the cockpit — gone the moment it's heard, with no timestamp, no severity, and no way to dial the noise up or down. A logging framework is the black box: every entry has a level (how important), a timestamp , and a source , and you can record more detail in development and less in production — without touching your code.
Every log message has a level that says how important it is. You set a threshold , and anything below it is silently dropped. In production you typically run at INFO , so the chatty TRACE and DEBUG lines cost you nothing. When you need to investigate, you lower the threshold to DEBUG — often for just one package — and the detail reappears, no redeploy required.
The JDK's built-in java.util.logging uses slightly different names ( FINEST / FINE ≈ TRACE/DEBUG, INFO , WARNING ≈ WARN, SEVERE ≈ ERROR), but the idea is identical. The worked example below uses it because it ships with the JDK and runs with zero dependencies.
In real projects you don't call java.util.logging directly. You code against SLF4J (Simple Logging Facade for Java) — an API , a set of method names like log.info(...) that does no logging on its own. Behind it sits an implementation (also called a binding ) that does the real work: Logback or Log4j2 .
Your code → SLF4J API (what you write against, always the same)
SLF4J API → Logback or Log4j2 (the engine you pick at build time)
Swap the engine by changing one dependency — your code never changes.
Why a facade? Because libraries you depend on may use different logging engines. SLF4J lets the whole application — your code and its libraries — funnel through one engine you choose. Add it with two dependencies: slf4j-api (the facade) and a binding such as logback-classic (the engine). Get the logger once per class:
SLF4J messages use placeholders : a literal {' '} in the message that gets filled by the matching argument. log.info("user {' '}", id) prints user 42 . Placeholders are filled left-to-right, so log.info("{' '} ordered {' '} items", name, count) works too.
This isn't just tidier than string concatenation — it's faster . With "user " + id the full string is built every time, even when the line is below the threshold and immediately thrown away. With {' '} , SLF4J checks the level first and only assembles the message if it will actually be written.
Three events, three different levels. Fill in info , warn , or error so each message lands at the right severity. The expected output is in the comments.
Replace the + concatenation with {' '} placeholders. One message needs a single placeholder; the other needs two, filled left-to-right.
You configure Logback in XML , not Java, in a file at src/main/resources/logback.xml . Three pieces matter: an appender (where logs go — console, file, etc.), a pattern (the layout of each line), and the levels (the root threshold plus per-package overrides). A rolling file appender starts a fresh file by size and date so a log can never fill the disk.
In the pattern below, %d is the timestamp, %-5level the padded level, %logger the class, %msg%n your message and a newline. The %X{' '} token pulls a value out of MDC — which the next section explains.
MDC (Mapped Diagnostic Context) is a small key/value map attached to the current thread . You stamp it once at the start of a request, reference the keys in your pattern, and every log line from that request — across every class it touches — carries the same tag.
Analogy: MDC is a name badge every log line from a request automatically wears.
MDC.put("requestId", id) — pin the badge on (start of request)
%X{' '} — show the badge in each log line (in the pattern)
MDC.clear() — take the badge off in a finally block
When 1,000 requests run at once, MDC lets you filter a million interleaved lines down to the single requestId you're chasing. That's the difference between minutes and hours of debugging.
Time to put MDC to work with the support faded — only a comment outline is given. Stamp a requestId , log two messages, and clean up in a finally block so the id can't leak. The expected output is in the comments.
You can now log like a professional: pick the right level, code against the SLF4J facade with Logback underneath, use {' '} placeholders instead of concatenation, configure rotation in logback.xml , trace requests with MDC , and keep secrets out of your logs.
Next up: Secure Coding — preventing common vulnerabilities in Java applications.
Practice quiz
What is SLF4J?
- A logging implementation that writes files
- A database driver
- A facade (API) you code against, with no logging of its own
- A JSON library
Answer: A facade (API) you code against, with no logging of its own. SLF4J is a facade — an API like log.info(...). Implementations such as Logback or Log4j2 do the real work.
Why use log.info("user {}", id) instead of log.info("user " + id)?
- The string is only built if the level is enabled, saving CPU when it's off
- It looks nicer only
- Concatenation is illegal
- Placeholders encrypt the value
Answer: The string is only built if the level is enabled, saving CPU when it's off. With placeholders SLF4J checks the level first and only assembles the message if it will be written.
Which level is the typical production default?
- TRACE
- DEBUG
- ERROR
- INFO
Answer: INFO. INFO is the production default; TRACE and DEBUG are usually off, WARN and ERROR are always on.
How do you log an exception properly with SLF4J?
- log.error(e.getMessage())
- Pass the exception as the last argument with no {} for it
- Add a {} placeholder for the exception
- Catch it and log nothing
Answer: Pass the exception as the last argument with no {} for it. Pass the Throwable last with no placeholder; SLF4J prints the full stack trace. e.getMessage() loses the trace.
Which should you NEVER write to a log?
- Passwords, tokens, or PII
- A user id
- A request id
- A timestamp
Answer: Passwords, tokens, or PII. Logs are copied and widely read; never log secrets or PII. Log an identifier instead of the sensitive value.
What is MDC (Mapped Diagnostic Context)?
- A database cache
- A log file format
- A thread-local key/value map stamped onto every log line
- A compression algorithm
Answer: A thread-local key/value map stamped onto every log line. MDC is a thread-local map; put a requestId once and every log line from that request carries it via %X{requestId}.
Why must you call MDC.clear() in a finally block?
- To flush logs to disk
- Because threads are reused and a leftover value would mislabel the next request
- To close the logger
- To reset the log level
Answer: Because threads are reused and a leftover value would mislabel the next request. Pooled threads are reused; clearing MDC prevents a stale value from being stamped onto the next request's logs.
What relationship do Logback and Log4j2 have to SLF4J?
- They replace SLF4J
- They are faster facades
- They are databases
- They are implementations (bindings) that sit behind the SLF4J API
Answer: They are implementations (bindings) that sit behind the SLF4J API. You code against SLF4J and pick Logback or Log4j2 as the binding; swapping them needs zero code changes.
Which level fits 'a slow database query — unexpected but the app keeps going'?
- TRACE
- WARN
- INFO
- ERROR
Answer: WARN. WARN signals something unexpected but recoverable, like a retry, a deprecated call, or a slow query.
Why prefer a logging framework over System.out.println in production?
- It is shorter to type
- It runs on the GPU
- It adds levels, timestamps, sources, filtering, and can be turned off without code changes
- It encrypts every line
Answer: It adds levels, timestamps, sources, filtering, and can be turned off without code changes. A logger gives each entry a level, timestamp, and source, and lets you change verbosity without editing code.