Java Core Mechanisms: From Exception Handling to Logging Configuration

Exception Handling

Understanding Exceptions

An exception represents an abnormal condition that arises during program execution. Java groups exceptions into checked and unchecked categories, enabling structured error management.

Custom Exception Classes

Applications often require specific exception types beyond the standard library. Two variants exist:

Unchecked Custom Exception

Extend RuntimeException for exceptions that usually reflect programming errors and do not require explicit handling:

public class AgeValidation {
    public static void main(String[] args) {
        try {
            storeAge(230);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public static void storeAge(int age) {
        if (age > 0 && age < 120) {
            System.out.println("Age recorded: " + age);
        } else {
            throw new InvalidAgeException("Invalid age value: " + age);
        }
    }
}

class InvalidAgeException extends RuntimeException {
    public InvalidAgeException(String reason) {
        super(reason);
    }
}

Checked Custom Exception

Derive from Exception when the caller must be forced to handle or declare the exception:

public class AgeService {
    public static void main(String[] args) {
        try {
            registerAge(350);
        } catch (InvalidAgeException e) {
            System.err.println("Handled: " + e.getMessage());
        }
    }

    public static void registerAge(int age) throws InvalidAgeException {
        if (age > 0 && age < 120) {
            System.out.println("Registration successful: " + age);
        } else {
            throw new InvalidAgeException("Age out of allowed range: " + age);
        }
    }
}

class InvalidAgeException extends Exception {
    public InvalidAgeException(String message) {
        super(message);
    }
}

Practical Exception Handling Patterns

Log and notify the user

import java.text.SimpleDateFormat;
import java.util.Date;

public class DateParser {
    public static void main(String[] args) {
        try {
            parseCustomFormat("2025/03/15 09:30:00");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("Invalid date format provided.");
        }
    }

    public static void parseCustomFormat(String input) throws Exception {
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        Date result = formatter.parse(input);
        System.out.println(result);
    }
}

Attempt recovery

import java.util.Scanner;

public class PriceInput {
    public static void main(String[] args) {
        while (true) {
            try {
                capturePrice();
                break;
            } catch (Exception ex) {
                System.out.println("Please enter a valid numeric price.");
            }
        }
    }

    public static void capturePrice() {
        Scanner scanner = new Scanner(System.in);
        while (true) {
            System.out.print("Enter price: ");
            double value = scanner.nextDouble();
            if (value >= 0) {
                System.out.println("Accepted: " + value);
                break;
            } else {
                System.out.println("Price cannot be negative.");
            }
        }
    }
}

Collections Framework

Single‑collection Hierarchy (Collection Interface)

  • List – ordered, allows duplicates, index‑based access
    • ArrayList
    • LinkedList
  • Set – typically unordered, no duplicates, no index
    • HashSet (unordered, no duplicates)
      • LinkedHashSet (insertion‑ordered, no duplicates)
    • TreeSet (sorted, no duplicates)

All collection types are generic.

Core Collection Methods

These methods are universal across single‑column collections:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;

public class CollectionBasics {
    public static void main(String[] args) {
        Collection<String> items = new ArrayList<>();

        items.add("Alpha");
        items.add("Beta");
        System.out.println(items);

        items.clear();
        System.out.println("Empty? " + items.isEmpty());
        System.out.println("Size: " + items.size());

        items.add("Gamma");
        items.add("Delta");
        items.add("Epsilon");
        System.out.println("Contains Gamma? " + items.contains("Gamma"));

        items.remove("Epsilon");
        System.out.println(items);

        Object[] array = items.toArray();
        System.out.println(Arrays.toString(array));

        String[] typedArray = items.toArray(new String[0]);
        System.out.println(Arrays.toString(typedArray));

        Collection<String> extra = new ArrayList<>();
        extra.add("Zeta");
        extra.add("Eta");
        items.addAll(extra);
        System.out.println(items);
    }
}

Iteration Techniques

Three common approaches work for all Collection subtypes:

  • Iterator
  • Enhanced for‑loop
  • Lambda forEach
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class TraversalDemo {
    public static void main(String[] args) {
        Collection<String> langs = new ArrayList<>();
        langs.add("Kotlin");
        langs.add("Scala");
        langs.add("Rust");
        langs.add("Swift");
        langs.add("Go");

        // Iterator
        Iterator<String> it = langs.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }

        System.out.println("---");

        // Enhanced for
        for (String lang : langs) {
            System.out.println(lang);
        }

        System.out.println("---");

        // Lambda
        langs.forEach(System.out::println);
    }
}

List Implementations

ArrayList

Built on a dynamic array. Fast random access, slow insertions/deletions in the middle. When the internal array fills up, it grows by 50%.

LinkedList

Uses a doubly‑linked list. Efficient for frequent additions/removals at both ends, but slower positional access.

import java.util.LinkedList;
import java.util.List;

public class LinkedListOps {
    public static void main(String[] args) {
        List<String> names = new LinkedList<>();
        names.add("Alice");
        names.add("Bob");
        names.add("Charlie");

        // Iterate using index
        for (int i = 0; i < names.size(); i++) {
            System.out.println(names.get(i));
        }
    }
}

Set Implementations

HashSet

Relies on a hash table (array + linked list + red‑black tree from JDK 8). The default initial capacity is 16 with a load factor of 0.75. Duplicate elements are identified via hashCode() and equals(). For custom objects to be recognised as duplicates by content, both methods must be overridden:

import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

public class UniqueUsers {
    public static void main(String[] args) {
        Set<User> users = new HashSet<>();
        users.add(new User("john", "john@example.com"));
        users.add(new User("john", "john@example.com"));
        System.out.println("Size: " + users.size()); // 1
    }
}

class User {
    private String username;
    private String email;

    public User(String username, String email) {
        this.username = username;
        this.email = email;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof User)) return false;
        User user = (User) o;
        return Objects.equals(username, user.username) &&
               Objects.equals(email, user.email);
    }

    @Override
    public int hashCode() {
        return Objects.hash(username, email);
    }
}

LinkedHashSet

Maintains insertion order through a doubly‑linked list on top of the hash table.

TreeSet

Automatically sorts elements (ascending by default). Sorting can be defined via:

  • A Comparator passed to the constructor
  • Implementing Comparable in the element class
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;

public class SortedProducts {
    public static void main(String[] args) {
        // Comparator example
        Set<Product> catalog = new TreeSet<>(
            Comparator.comparingDouble(Product::getPrice)
        );
        catalog.add(new Product("Phone", 699.99));
        catalog.add(new Product("Tablet", 499.99));
        catalog.add(new Product("Laptop", 1299.99));
        System.out.println(catalog);
    }
}

class Product {
    private String name;
    private double price;

    public Product(String name, double price) {
        this.name = name;
        this.price = price;
    }

    public double getPrice() { return price; }

    @Override
    public String toString() {
        return name + " $" + price;
    }
}

Concurrent Modification

When modifying a collection while iterating, prefer Iterator.remove() or a regular for‑loop with index manipulation. Enhanced for and lambdas cannot handle structural changes safely.

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class SafeRemoval {
    public static void main(String[] args) {
        List<String> tasks = new ArrayList<>();
        tasks.add("taskA");
        tasks.add("taskB");
        tasks.add("taskC");

        Iterator<String> iter = tasks.iterator();
        while (iter.hasNext()) {
            if (iter.next().equals("taskB")) {
                iter.remove();
            }
        }
        System.out.println(tasks);
    }
}

Map (Key‑Value Pairs)

  • HashMap – unordered, keys unique, permits one null key
  • LinkedHashMap – insertion‑ordered
  • TreeMap – sorted by keys

Common Map Operations

import java.util.HashMap;
import java.util.Map;

public class MapDemo {
    public static void main(String[] args) {
        Map<Integer, String> inventory = new HashMap<>();
        inventory.put(101, "Monitor");
        inventory.put(102, "Keyboard");
        inventory.put(103, "Mouse");
        inventory.put(null, "Unknown");

        System.out.println(inventory.size());
        System.out.println(inventory.get(102));
        System.out.println(inventory.remove(103));

        // Key set
        for (Integer code : inventory.keySet()) {
            System.out.print(code + " ");
        }
        System.out.println();

        // Values
        for (String item : inventory.values()) {
            System.out.print(item + " ");
        }
        System.out.println();

        // Entry set
        for (Map.Entry<Integer, String> entry : inventory.entrySet()) {
            System.out.println(entry.getKey() + " -> " + entry.getValue());
        }

        // Lambda
        inventory.forEach((k, v) -> System.out.println(k + ": " + v));
    }
}

Varargs (Variable Arguments)

A method can accept zero or more arguments of a specified type using Type... name. The varargs parameter is treated as an array and must be the last parameter in the method signature.

public class VarargsExample {
    public static void main(String[] args) {
        display(42);
        display(10, 20, 30);
        int[] data = {1, 2, 3, 4};
        display(data);
    }

    public static void display(int... values) {
        System.out.println(java.util.Arrays.toString(values));
    }
}

File Handling and I/O Streams

The File Class

File from java.io represents file and directory paths. It does not read or write file content; it only provides metadata and file system operations.

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;

public class FileInfo {
    public static void main(String[] args) {
        File document = new File("docs/readme.txt");
        System.out.println("Exists: " + document.exists());
        System.out.println("Is file: " + document.isFile());
        System.out.println("Name: " + document.getName());
        System.out.println("Bytes: " + document.length());

        long modified = document.lastModified();
        String dateStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(modified));
        System.out.println("Last modified: " + dateStr);

        System.out.println("Absolute path: " + document.getAbsolutePath());
    }
}
import java.io.File;
import java.io.IOException;

public class FileManipulation {
    public static void main(String[] args) throws IOException {
        File notes = new File("notes.txt");
        if (!notes.exists()) notes.createNewFile();

        File dir = new File("output");
        dir.mkdir();

        File deepDir = new File("data/archive/2025");
        deepDir.mkdirs();

        System.out.println(notes.delete());
    }
}

I/O Streams Essentials

Streams handle raw data or characters.

Byte Streams (FileInputStream / FileOutputStream)

Used for binary data (images, videos, any file).

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class ByteStreamCopy {
    public static void main(String[] args) {
        try (FileInputStream in = new FileInputStream("source.png");
             FileOutputStream out = new FileOutputStream("target.png")) {
            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = in.read(buffer)) != -1) {
                out.write(buffer, 0, bytesRead);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Character Streams (FileReader / FileWriter)

Designed for text files, handling character encoding internally.

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class TextFileCopy {
    public static void main(String[] args) {
        try (FileReader reader = new FileReader("input.txt");
             FileWriter writer = new FileWriter("output.txt")) {
            int ch;
            while ((ch = reader.read()) != -1) {
                writer.write(ch);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Remember to flush or close a FileWriter to persist data.

Buffered Streams

Wrapping unbuffered streams with BufferedInputStream / BufferedOutputStream or BufferedReader / BufferedWriter adds an internal buffer (default 8 KB) that significantly improves performance.

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;

public class BufferedTextExample {
    public static void main(String[] args) {
        try (BufferedReader br = new BufferedReader(new FileReader("story.txt"));
             BufferedWriter bw = new BufferedWriter(new FileWriter("copy_story.txt"))) {
            String line;
            while ((line = br.readLine()) != null) {
                bw.write(line);
                bw.newLine();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Conversion Streams (InputStreamReader / OutputStreamWriter)

Bridge byte streams to character streams with explicit charset control, avoiding encoding mismatches.

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;

public class CharsetAwareReader {
    public static void main(String[] args) {
        try (BufferedReader br = new BufferedReader(
                new InputStreamReader(new FileInputStream("document.txt"), "UTF-8"))) {
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

Print Streams

PrintStream and PrintWriter provide convenient print/println methods and can redirect standard output.

import java.io.PrintStream;

public class OutputRedirect {
    public static void main(String[] args) {
        System.out.println("Console message");
        try (PrintStream fileOut = new PrintStream("log.txt")) {
            System.setOut(fileOut);
            System.out.println("This goes to file");
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

Data Streams

DataOutputStream / DataInputStream preserve primitive data types when writing/reading.

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;

public class DataStreamDemo {
    public static void main(String[] args) {
        try (DataOutputStream dos = new DataOutputStream(new FileOutputStream("values.dat"));
             DataInputStream dis = new DataInputStream(new FileInputStream("values.dat"))) {
            dos.writeInt(1024);
            dos.writeDouble(3.14);
            dos.writeUTF("Hello");

            System.out.println(dis.readInt());
            System.out.println(dis.readDouble());
            System.out.println(dis.readUTF());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Serialization

Objects can be persisted using ObjectOutputStream and restored with ObjectInputStream. The class must implement Serializable.

import java.io.*;

public class SerializationTest {
    public static void main(String[] args) {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("employee.ser"));
             ObjectInputStream ois = new ObjectInputStream(new FileInputStream("employee.ser"))) {
            Employee emp = new Employee("Alice", 30);
            oos.writeObject(emp);

            Employee recovered = (Employee) ois.readObject();
            System.out.println(recovered.getName() + " " + recovered.getAge());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class Employee implements Serializable {
    private String name;
    private int age;

    public Employee(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() { return name; }
    public int getAge() { return age; }
}

Special File Formats

Properties Files

.properties files store key‑value pairs. The Properties class simplifies loading and saving.

import java.io.FileReader;
import java.io.FileWriter;
import java.util.Properties;

public class PropertiesManager {
    public static void main(String[] args) throws Exception {
        Properties config = new Properties();
        config.load(new FileReader("settings.properties"));
        System.out.println(config.getProperty("db.host"));

        config.setProperty("app.timeout", "30");
        config.store(new FileWriter("settings.properties"), "Application Config");
    }
}

XML Processing with Dom4j

XML is widely used for configuration and data exchange. Dom4j offers a straightforward DOM‑based approach.

Parsing

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.util.List;

public class XmlReader {
    public static void main(String[] args) throws Exception {
        SAXReader reader = new SAXReader();
        Document doc = reader.read("users.xml");
        Element root = doc.getRootElement();

        List<Element> userList = root.elements("user");
        for (Element user : userList) {
            String id = user.attributeValue("id");
            String name = user.elementText("name");
            System.out.println(id + ": " + name);
        }
    }
}

Writing

import java.io.BufferedWriter;
import java.io.FileWriter;

public class XmlWriter {
    public static void main(String[] args) {
        try (BufferedWriter bw = new BufferedWriter(new FileWriter("data.xml"))) {
            StringBuilder xml = new StringBuilder();
            xml.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
            xml.append("<books>\n");
            xml.append("  <book id=\"1\">\n");
            xml.append("    <title>Effective Java</title>\n");
            xml.append("    <price>45.0</price>\n");
            xml.append("  </book>\n");
            xml.append("</books>");
            bw.write(xml.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Logging with Logback

Motivation

Logging captures runtime events for debugging and monitoring without modifying source code. Logback, built on SLF4J, is a modern, high‑performance logging framework.

Setup

Include slf4j-api, logback-core, and logback-classic on the classpath. Place logback.xml in the source root.

Quick Start

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

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

    public static void main(String[] args) {
        logger.info("Payment processing started");
        try {
            processTransaction(100, 0);
        } catch (Exception e) {
            logger.error("Transaction failed", e);
        }
        logger.info("Processing completed");
    }

    public static int processTransaction(int amount, int divisor) {
        logger.debug("Amount: {}, Divisor: {}", amount, divisor);
        int result = amount / divisor;
        logger.info("Result: {}", result);
        return result;
    }
}

Configuration Snippet (logback.xml)

<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/application.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>logs/application.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>7</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="INFO">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="FILE" />
    </root>
</configuration>

The global level can be set to TRACE, DEBUG, INFO, WARN, ERROR, or OFF to control verbosity.

Tags: java Exception Handling Collections I/O Streams File Handling

Posted on Wed, 02 Sep 2026 16:20:19 +0000 by mybluehair