Memra

Logging, design patterns & debugging

SLF4J over System.out, Singleton/Factory/Strategy in brief, and reading a stack trace to the root cause.

Logging: prefer SLF4J over System.out

System.out.println is fine for learning; it is a liability in production. A logging framework lets you:

- Control verbosity per level (TRACE, DEBUG, INFO, WARN, ERROR) without changing code. - Route output to files or a log aggregator — by config, not redeployment. - Include context automatically (timestamp, thread name, class name).

SLF4J is the de-facto standard facade; Logback is its most common implementation:

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

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

    public void process(Order order) {
        log.info("Processing order id={}", order.getId());
        log.debug("Order details: {}", order);  // only if DEBUG enabled
    }
}

The {} placeholder is lazy — the argument is only converted to String if the message will be emitted. Writing log.debug("Order: " + order) builds the string regardless; log.debug("Order: {}", order) does not.

Three everyday design patterns

Singleton — exactly one instance for the lifetime of the application. Modern Java prefers a single-element enum:

public enum Config {
    INSTANCE;
    private final String apiKey = System.getenv("API_KEY");
    public String getApiKey() { return apiKey; }
}

Use Singleton when the instance is expensive, stateful, and legitimately shared. Avoid it for stateless utilities.

Factory — centralises object creation and hides which concrete type is returned:

Shape shape = ShapeFactory.create("circle");  // returns a Circle

Strategy — injects a behaviour at runtime via an interface, rather than baking it in with if/else. This is how Comparator, Runnable, and most functional interfaces in the JDK work:

@FunctionalInterface
interface Sorter { void sort(int[] data); }

class Report {
    private final Sorter sorter;
    Report(Sorter sorter) { this.sorter = sorter; }
    void generate(int[] data) { sorter.sort(data); }
}
Report r = new Report(Arrays::sort);

Strategy makes code testable (inject a test double) and open for extension without modification.

Reading a stack trace

When an exception is thrown, Java prints a stack trace to standard error. Read it top to bottom:

Exception in thread "main" java.lang.NullPointerException
    at com.example.OrderService.process(OrderService.java:42)
    at com.example.Main.main(Main.java:15)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
  1. Line 1: exception type and message — often the complete diagnosis.
  2. First at line in your own package — the exact line that threw.
  3. Frames below show what called it — useful when the throw site was called with bad arguments.

Skip JDK frames (java.base/...) on your first pass. Focus on lines in your own package.

NORMAL ~/memra/learn/java-from-zero/logging-patterns-debugging utf-8 LF