Programming in Java

Unit 11: Exceptions, Assertions & Multithreading

From graceful error handling to concurrent execution — master Java's exception hierarchy, assertion mechanisms, and multithreading to build production-grade applications like Zerodha and Paytm.

⏱️ 8 hrs theory + 6 hrs lab  |  💰 ₹15K–₹50K/month  |  📝 30 MCQs (Bloom's Mapped)

💼 Jobs this unlocks: Java Backend Developer (₹6–12 LPA)  |  Multithreaded Systems Engineer (₹8–18 LPA)  |  Fintech Developer (₹10–25 LPA)

Section A

Opening Hook — When 10 Lakh Orders Hit Zerodha at 9:15 AM

🏢 How Zerodha Handles ₹15 Lakh Crore Without Crashing

Every trading day at 9:15 AM, the Indian stock market opens. In that single second, Zerodha processes over 10 lakh concurrent buy/sell orders. Each order is a separate thread. When two traders try to modify the same shared portfolio balance simultaneously, Java's synchronized keyword ensures only one thread touches the balance at a time — preventing ₹15 lakh crore worth of data corruption.

But what happens when a UPI payment to Zerodha fails mid-transaction? A network timeout? An invalid stock symbol? The system doesn't crash — it catches the exception gracefully, logs the error, sends you a friendly "Order failed — please retry" message, and keeps running for the other 9,99,999 orders.

This unit teaches you both pillars: Exceptions (how to handle failures without crashing) and Multithreading (how to run thousands of tasks simultaneously). Together, they make Java the #1 language for fintech, banking, and high-frequency trading systems across India.

🇮🇳 Zerodha🇮🇳 Paytm🇮🇳 PhonePe🇮🇳 Razorpay🇮🇳 HDFC Bank🇮🇳 NSE/BSE
Zerodha's backend is built primarily in Java and Go. Their Kite platform handles 15+ million orders daily with sub-millisecond latency. Every order goes through exception handling (for invalid quantities, insufficient balance, market closed) and multithreading (for concurrent order matching). Nithin Kamath, Zerodha's founder, has stated that robust error handling saved them from ₹100+ crore losses during market volatility events.
Section B

Learning Outcomes — Bloom's Taxonomy Mapped

Bloom's LevelLearning Outcome
🔴 RememberList the exception hierarchy: Throwable → Error/Exception → RuntimeException, and differentiate checked vs unchecked exceptions
🔴 RememberIdentify the five states in the Java thread lifecycle: NEW → RUNNABLE → BLOCKED → WAITING → TERMINATED
🟠 UnderstandExplain the execution flow of try-catch-finally and how try-with-resources auto-closes resources
🟠 UnderstandDescribe how synchronized prevents race conditions in concurrent access to shared data
🟢 ApplyWrite custom checked and unchecked exceptions (InsufficientBalanceException) with exception chaining
🟢 ApplyCreate multithreaded programs using Thread class, Runnable interface, and lambda expressions
🟡 AnalyzeAnalyze deadlock scenarios and determine prevention strategies using lock ordering
🟡 AnalyzeCompare Thread class extension vs Runnable implementation vs lambda-based thread creation
🔵 EvaluateEvaluate when to use ExecutorService thread pools vs manual thread management in production systems
🔵 EvaluateAssess the trade-offs of assert vs exception throwing for input validation in different deployment scenarios
🟣 CreateDesign a producer-consumer system (Zerodha Order Book) using wait/notify with full exception handling
🟣 CreateBuild a multithreaded bank transfer simulation with synchronized blocks and custom exception chains
Section C

Concept Explanation — Exceptions, Assertions & Multithreading

📛 PART A: Exception Handling

Think of exceptions like this: You're withdrawing ₹5,000 from an ATM. What could go wrong? Insufficient balance, network failure, card expired, ATM out of cash. Each of these is an exception — an abnormal condition that disrupts normal program flow. Java doesn't crash when these happen — it catches them and responds gracefully.

Exception = UPI failure. When your PhonePe payment fails, the app doesn't crash. It catches the exception, shows "Transaction failed — please retry," and ensures your money isn't deducted. That's exception handling in action — catch it, show a friendly message, don't crash.

1. Exception Hierarchy in Java

Java organises all errors and exceptions into a class hierarchy rooted at Throwable. Understanding this tree is the foundation of exception handling.

🌳 The Exception Family Tree

Hierarchy
                    Throwable
                   /          \
              Error          Exception
             /    \          /          \
    OutOfMemoryError  IOException   RuntimeException
    StackOverflowError SQLException  /        |         \
    VirtualMachineError           NullPointerException
                                  ArrayIndexOutOfBoundsException
                                  ArithmeticException
                                  ClassCastException
                                  IllegalArgumentException
Throwable (Root)

The superclass of all errors and exceptions in Java. Every throwable has a message (getMessage()) and a stack trace (printStackTrace()).

Error (DON'T Catch)

Serious JVM-level problems you cannot recover from. OutOfMemoryError, StackOverflowError. These are like a building collapsing — you can't "handle" it, you evacuate. Never catch Errors in production code.

Exception → Checked Exceptions

Exceptions the compiler forces you to handle at compile time. IOException, SQLException, FileNotFoundException. The compiler says: "I know this can fail — prove you've handled it." You must use try-catch or declare throws.

RuntimeException → Unchecked Exceptions

Exceptions that occur at runtime due to programming bugs. NullPointerException, ArrayIndexOutOfBoundsException. The compiler doesn't force you to catch them — but you should fix the bug that causes them.

AspectChecked ExceptionUnchecked Exception
Checked atCompile timeRuntime
Must handle?Yes (try-catch or throws)No (optional)
ExtendsException (not RuntimeException)RuntimeException
ExamplesIOException, SQLException, FileNotFoundExceptionNullPointerException, ArithmeticException
CauseExternal factors (file, network, DB)Programming bugs (null access, bad index)
Indian AnalogyTrain cancelled (external) — you must plan BForgot your ticket (your bug) — fix yourself
Students confuse Error and Exception. Error = JVM is dying (OutOfMemoryError). Exception = your code encountered a problem (FileNotFoundException). You handle Exceptions, but you can't meaningfully handle Errors. Never write catch(Error e).

2. try-catch-finally Execution Flow

The try-catch-finally block is Java's mechanism for handling exceptions. Think of it as a safety net under a tightrope walker.

🔄 Execution Flow Rules

Rule 1: Code in try block executes normally until an exception occurs.

Rule 2: If exception occurs, remaining try code is SKIPPED — control jumps to matching catch.

Rule 3: finally block ALWAYS executes — exception or no exception, caught or uncaught. Only exception: System.exit().

Rule 4: If no matching catch is found, exception propagates up the call stack.

Rule 5: Multiple catch blocks are checked top-to-bottom — put specific exceptions BEFORE general ones.

Java
public class ATMWithdrawal {
    public static void main(String[] args) {
        double balance = 5000.0;
        double amount = 7000.0;

        try {
            System.out.println("Attempting withdrawal of ₹" + amount);

            if (amount > balance) {
                throw new ArithmeticException("Insufficient balance!");
            }

            balance -= amount;
            System.out.println("Withdrawal successful. New balance: ₹" + balance);

        } catch (ArithmeticException e) {
            System.out.println("❌ Transaction failed: " + e.getMessage());
            System.out.println("💡 Please deposit more funds or try smaller amount.");

        } finally {
            System.out.println("📋 Transaction log updated. ATM session ended.");
            // This ALWAYS runs — cleanup code goes here
        }
    }
}
Attempting withdrawal of ₹7000.0 ❌ Transaction failed: Insufficient balance! 💡 Please deposit more funds or try smaller amount. 📋 Transaction log updated. ATM session ended.

Multi-catch (Java 7+): catch(A | B e)

When multiple exceptions need the same handling, use multi-catch to avoid code duplication:

Java
try {
    // Code that might throw different exceptions
    String data = readFromFile("orders.csv");
    int quantity = Integer.parseInt(data);
} catch (IOException | NumberFormatException e) {
    // Single block handles both — DRY principle!
    System.out.println("Error processing order: " + e.getMessage());
    logError(e);
}
Multi-catch variable is implicitly final. You cannot reassign e inside a multi-catch block. This is by design — since e could be either type, reassigning it would create ambiguity.

try-with-resources (Java 7+): AutoCloseable

Resources like files, database connections, and sockets must be closed after use. Before Java 7, you'd write verbose finally blocks. Now, any class implementing AutoCloseable can be auto-closed:

Java
// ❌ OLD WAY — verbose and error-prone
BufferedReader reader = null;
try {
    reader = new BufferedReader(new FileReader("transactions.csv"));
    String line = reader.readLine();
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (reader != null) {
        try { reader.close(); } catch (IOException e) { /* swallowed */ }
    }
}

// ✅ NEW WAY — clean, safe, automatic
try (BufferedReader reader = new BufferedReader(new FileReader("transactions.csv"))) {
    String line = reader.readLine();
    System.out.println("Transaction: " + line);
} catch (IOException e) {
    System.out.println("Could not read transactions: " + e.getMessage());
}
// reader.close() is called automatically — even if exception occurs!

3. throw vs throws

Aspectthrowthrows
Used inMethod bodyMethod signature (declaration)
PurposeActually throws an exception objectDeclares that method MAY throw exception
Followed byException instance (throw new XYZ())Exception class name(s)
CountOne exception at a timeMultiple: throws A, B, C
Java
// 'throws' — declares the method may throw this exception
public void transferMoney(double amount) throws InsufficientBalanceException {
    if (amount > balance) {
        // 'throw' — actually creates and throws the exception
        throw new InsufficientBalanceException("Balance ₹" + balance + " < ₹" + amount);
    }
    balance -= amount;
}

4. Custom Exceptions — InsufficientBalanceException for Paytm

Java's built-in exceptions don't cover business-specific errors. Paytm needs InsufficientBalanceException, Zerodha needs MarketClosedException. You create your own by extending Exception (checked) or RuntimeException (unchecked).

Java — Custom Checked Exception
// Checked — compiler forces caller to handle it
public class InsufficientBalanceException extends Exception {
    private double balance;
    private double amount;

    public InsufficientBalanceException(String message, double balance, double amount) {
        super(message);
        this.balance = balance;
        this.amount = amount;
    }

    public double getBalance() { return balance; }
    public double getAmount()  { return amount; }
    public double getDeficit() { return amount - balance; }
}
Java — Custom Unchecked Exception
// Unchecked — no forced handling (for programming errors)
public class InvalidStockSymbolException extends RuntimeException {
    public InvalidStockSymbolException(String symbol) {
        super("Invalid stock symbol: " + symbol + ". Use NSE/BSE format like RELIANCE, TCS, INFY");
    }
}
Java — Using Custom Exception (Paytm Wallet)
public class PaytmWallet {
    private double balance;
    private String userId;

    public PaytmWallet(String userId, double balance) {
        this.userId = userId;
        this.balance = balance;
    }

    public void pay(String merchant, double amount) throws InsufficientBalanceException {
        if (amount > balance) {
            throw new InsufficientBalanceException(
                "Paytm wallet balance insufficient for " + merchant,
                balance, amount
            );
        }
        balance -= amount;
        System.out.println("✅ ₹" + amount + " paid to " + merchant + ". Balance: ₹" + balance);
    }

    public static void main(String[] args) {
        PaytmWallet wallet = new PaytmWallet("user_mumbai_42", 500.0);
        try {
            wallet.pay("Zomato", 350.0);   // ✅ Success
            wallet.pay("Swiggy", 300.0);   // ❌ Fails — only ₹150 left
        } catch (InsufficientBalanceException e) {
            System.out.println("❌ " + e.getMessage());
            System.out.println("💡 Add ₹" + e.getDeficit() + " to complete this payment.");
        }
    }
}
✅ ₹350.0 paid to Zomato. Balance: ₹150.0 ❌ Paytm wallet balance insufficient for Swiggy 💡 Add ₹150.0 to complete this payment.

5. Exception Chaining (initCause)

Sometimes an exception is caused by another exception. Exception chaining preserves the original cause while wrapping it in a more meaningful business exception.

Java
public void processUPIPayment(String upiId, double amount) throws PaymentException {
    try {
        connectToUPIGateway(upiId);   // May throw IOException
        debitAccount(amount);          // May throw SQLException
    } catch (IOException e) {
        PaymentException pe = new PaymentException("UPI gateway unreachable");
        pe.initCause(e);  // Chain the original IOException as root cause
        throw pe;
    } catch (SQLException e) {
        // Alternative: constructor chaining
        throw new PaymentException("Database error during debit", e);
    }
}

// Later, debugging:
catch (PaymentException pe) {
    System.out.println("Payment failed: " + pe.getMessage());
    System.out.println("Root cause: " + pe.getCause().getMessage());
}

6. Assertions — assert Statement & java -ea

Assertions are sanity checks during development. They verify conditions that should always be true. If the condition is false, the JVM throws AssertionError.

🛡️ assert Statement

Syntax 1: assert condition;

Syntax 2: assert condition : "error message";

Enable: java -ea MyProgram (assertions are DISABLED by default)

When to use: Internal invariants, post-conditions, unreachable code markers

When NOT to use: Input validation (user input), argument checking in public methods

Java
public class AssertionDemo {
    public static double calculateDiscount(double price, double discountPercent) {
        assert price >= 0 : "Price cannot be negative: " + price;
        assert discountPercent >= 0 && discountPercent <= 100
            : "Invalid discount: " + discountPercent + "%";

        double finalPrice = price - (price * discountPercent / 100);

        assert finalPrice >= 0 : "Final price went negative!";  // post-condition

        return finalPrice;
    }

    public static void main(String[] args) {
        System.out.println("Price after 20% off: ₹" + calculateDiscount(1000, 20));
        System.out.println("Price after 150% off: ₹" + calculateDiscount(1000, 150));
        // Run with: java -ea AssertionDemo
        // Second call triggers: AssertionError: Invalid discount: 150.0%
    }
}
Don't use assertions for public method argument validation! Assertions are disabled in production (java without -ea). Use IllegalArgumentException for public API validation. Assertions are for internal sanity checks that "should never fail if the code is correct."

⚡ PART B: Multithreading

A thread is the smallest unit of execution within a process. When you run a Java program, it starts with one thread (the main thread). Multithreading lets you run multiple threads simultaneously — like Zerodha processing lakhs of buy and sell orders at the same time.

Thread = Zerodha Buy/Sell executors running simultaneously. When you place a buy order and your friend places a sell order at the exact same millisecond, two separate threads handle these orders concurrently. Without multithreading, orders would queue up and you'd wait minutes instead of milliseconds.

7. Creating Threads — Thread Class vs Runnable Interface

Method 1: Extending Thread Class

Java
class BuyOrderThread extends Thread {
    private String stock;
    private int quantity;

    public BuyOrderThread(String stock, int quantity) {
        this.stock = stock;
        this.quantity = quantity;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() +
            " → BUY " + quantity + " shares of " + stock);
        try { Thread.sleep(1000); } catch (InterruptedException e) { }
        System.out.println(Thread.currentThread().getName() +
            " → Order filled ✅");
    }
}

// Usage:
BuyOrderThread t1 = new BuyOrderThread("RELIANCE", 100);
BuyOrderThread t2 = new BuyOrderThread("TCS", 50);
t1.start();  // DO NOT call run() — call start()!
t2.start();  // Both threads run concurrently

Method 2: Implementing Runnable Interface (Preferred)

Java
class SellOrderRunnable implements Runnable {
    private String stock;
    private int quantity;

    public SellOrderRunnable(String stock, int quantity) {
        this.stock = stock;
        this.quantity = quantity;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() +
            " → SELL " + quantity + " shares of " + stock);
    }
}

// Usage — Runnable is passed TO a Thread object
Thread t3 = new Thread(new SellOrderRunnable("INFY", 200), "SellThread-1");
t3.start();

Method 3: Lambda-based Thread Creation (Java 8+, Cleanest)

Java
// Since Runnable is a @FunctionalInterface with one abstract method (run),
// it can be replaced with a lambda expression:

Thread t4 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName() + " → Processing order via Lambda");
    try { Thread.sleep(500); } catch (InterruptedException e) { }
    System.out.println(Thread.currentThread().getName() + " → Lambda order done ✅");
}, "LambdaThread");
t4.start();
ApproachProsConsWhen to Use
extends ThreadSimple, directCan't extend another class (Java single inheritance)Quick prototypes only
implements RunnableAllows extending another class, separation of concernsSlightly more verboseProduction code (recommended)
LambdaConcise, modern, inlineLess reusable for complex logicShort tasks, one-off threads

8. Thread Lifecycle — Five States

🔄 Thread State Diagram

Lifecycle
  new Thread()         start()         Scheduler picks       run() ends
       |                  |                   |                    |
   [NEW] ──────────> [RUNNABLE] ──────────> [RUNNING] ──────────> [TERMINATED]
                          ↑                   |
                          |                   | sleep()/wait()/
                          |                   | blocked on I/O
                          |                   ↓
                     notify()/            [BLOCKED] /
                     sleep over/          [WAITING] /
                     I/O complete         [TIMED_WAITING]

NEW: Thread object created but start() not yet called.

RUNNABLE: start() called. Thread is ready to run, waiting for CPU scheduling.

RUNNING: Thread's run() method is actually executing on CPU.

BLOCKED: Thread waiting to acquire a lock (trying to enter synchronized block).

WAITING: Thread called wait(), join(), or LockSupport.park(). Waiting indefinitely for notification.

TIMED_WAITING: Thread called sleep(ms), wait(ms), or join(ms). Wakes up after timeout.

TERMINATED: run() method completed or exception thrown. Thread cannot be restarted.

9. Key Thread Methods

MethodDescriptionExample Use Case
start()Starts thread, calls run() in new threadLaunching an order processing thread
sleep(ms)Pauses current thread for ms millisecondsSimulating network delay
join()Current thread waits for this thread to dieWait for all downloads to complete before zipping
interrupt()Sets interrupt flag; sleeping threads get InterruptedExceptionCancelling a long-running order search
yield()Hints scheduler to give other threads a chanceCooperative multitasking (rarely used)
isAlive()Returns true if thread is started but not yet terminatedChecking if background task is still running
setDaemon(true)Makes thread a daemon (dies when all user threads end)Background logging thread
Java — join() Example
public class JoinDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread download = new Thread(() -> {
            System.out.println("📥 Downloading stock data...");
            try { Thread.sleep(2000); } catch (InterruptedException e) { }
            System.out.println("📥 Download complete!");
        });

        download.start();
        download.join();  // Main thread WAITS here until download finishes
        System.out.println("📊 Now processing downloaded data...");
    }
}
📥 Downloading stock data... 📥 Download complete! 📊 Now processing downloaded data...

10. Synchronization — Preventing Race Conditions

When multiple threads access shared data simultaneously, you get race conditions — unpredictable, incorrect results. Java's synchronized keyword ensures only one thread enters a critical section at a time.

Java — Race Condition (BUG!)
class UnsafeBankAccount {
    private double balance = 10000;

    // ❌ NOT synchronized — two threads can withdraw simultaneously!
    public void withdraw(double amount) {
        if (balance >= amount) {
            // Thread A checks: 10000 >= 8000 ✅
            // Thread B checks: 10000 >= 7000 ✅ (SAME balance!)
            balance -= amount;
            // Both withdraw — balance goes NEGATIVE! ₹-5000
        }
    }
}
Java — Fixed with synchronized
class SafeBankAccount {
    private double balance = 10000;

    // ✅ Synchronized method — only ONE thread can execute at a time
    public synchronized void withdraw(double amount) {
        if (balance >= amount) {
            System.out.println(Thread.currentThread().getName() +
                " withdrawing ₹" + amount);
            balance -= amount;
            System.out.println("Remaining balance: ₹" + balance);
        } else {
            System.out.println(Thread.currentThread().getName() +
                " — Insufficient balance!");
        }
    }

    // ✅ Synchronized block — more granular control
    public void transfer(SafeBankAccount target, double amount) {
        synchronized (this) {  // Lock on source account
            if (balance >= amount) {
                balance -= amount;
                target.deposit(amount);
            }
        }
    }

    public synchronized void deposit(double amount) {
        balance += amount;
    }

    public synchronized double getBalance() {
        return balance;
    }
}

11. wait() / notify() / notifyAll() — Inter-Thread Communication

Sometimes threads need to coordinate. A producer thread creates data, a consumer thread processes it. The consumer must wait when the queue is empty, and the producer must notify the consumer when new data arrives.

Java — Producer-Consumer (Zerodha Order Book)
import java.util.LinkedList;
import java.util.Queue;

public class ZerodhaOrderBook {
    private final Queue<String> orders = new LinkedList<>();
    private final int MAX_CAPACITY = 5;

    // PRODUCER — Traders placing orders
    public synchronized void placeOrder(String order) throws InterruptedException {
        while (orders.size() == MAX_CAPACITY) {
            System.out.println("📋 Order book FULL — " + Thread.currentThread().getName() + " waiting...");
            wait();  // Release lock and wait
        }
        orders.add(order);
        System.out.println("📥 " + Thread.currentThread().getName() + " placed: " + order +
            " [Queue size: " + orders.size() + "]");
        notifyAll();  // Wake up consumers
    }

    // CONSUMER — Matching engine processing orders
    public synchronized String executeOrder() throws InterruptedException {
        while (orders.isEmpty()) {
            System.out.println("⏳ Order book EMPTY — " + Thread.currentThread().getName() + " waiting...");
            wait();  // Release lock and wait
        }
        String order = orders.poll();
        System.out.println("✅ " + Thread.currentThread().getName() + " executed: " + order +
            " [Queue size: " + orders.size() + "]");
        notifyAll();  // Wake up producers
        return order;
    }

    public static void main(String[] args) {
        ZerodhaOrderBook book = new ZerodhaOrderBook();

        // Producer threads (traders)
        Thread trader1 = new Thread(() -> {
            String[] stocks = {"BUY RELIANCE 100", "BUY TCS 50", "SELL INFY 200"};
            for (String s : stocks) {
                try {
                    book.placeOrder(s);
                    Thread.sleep(300);
                } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
            }
        }, "Trader-Ravi");

        Thread trader2 = new Thread(() -> {
            String[] stocks = {"SELL HDFC 75", "BUY WIPRO 150", "BUY SBI 300"};
            for (String s : stocks) {
                try {
                    book.placeOrder(s);
                    Thread.sleep(200);
                } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
            }
        }, "Trader-Priya");

        // Consumer thread (matching engine)
        Thread engine = new Thread(() -> {
            for (int i = 0; i < 6; i++) {
                try {
                    book.executeOrder();
                    Thread.sleep(500);
                } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
            }
        }, "MatchEngine");

        trader1.start();
        trader2.start();
        engine.start();
    }
}
📥 Trader-Ravi placed: BUY RELIANCE 100 [Queue size: 1] 📥 Trader-Priya placed: SELL HDFC 75 [Queue size: 2] ✅ MatchEngine executed: BUY RELIANCE 100 [Queue size: 1] 📥 Trader-Ravi placed: BUY TCS 50 [Queue size: 2] 📥 Trader-Priya placed: BUY WIPRO 150 [Queue size: 3] ✅ MatchEngine executed: SELL HDFC 75 [Queue size: 2] 📥 Trader-Ravi placed: SELL INFY 200 [Queue size: 3] 📥 Trader-Priya placed: BUY SBI 300 [Queue size: 4] ✅ MatchEngine executed: BUY TCS 50 [Queue size: 3] ✅ MatchEngine executed: BUY WIPRO 150 [Queue size: 2] ✅ MatchEngine executed: SELL INFY 200 [Queue size: 1] ✅ MatchEngine executed: BUY SBI 300 [Queue size: 0]

12. Deadlock — Detection & Prevention

Deadlock occurs when two or more threads are permanently blocked, each waiting for a lock held by the other. It's a Mexican standoff — neither can proceed.

💀 Deadlock Scenario

Deadlock
Thread A: Locks Account1 → wants Account2 (BLOCKED — B has it)
Thread B: Locks Account2 → wants Account1 (BLOCKED — A has it)

Both waiting forever → DEADLOCK! 💀

Prevention Strategies:

1. Lock Ordering: Always acquire locks in the same order (e.g., by account ID)

2. Lock Timeout: Use tryLock(timeout) from ReentrantLock

3. Avoid Nested Locks: Minimize holding multiple locks

4. Use Higher-Level Concurrency: java.util.concurrent collections

Java — Deadlock Prevention via Lock Ordering
public void safeTransfer(SafeBankAccount from, SafeBankAccount to, double amount) {
    // Always lock the account with the smaller ID first
    SafeBankAccount first  = from.getId() < to.getId() ? from : to;
    SafeBankAccount second = from.getId() < to.getId() ? to : from;

    synchronized (first) {
        synchronized (second) {
            if (from.getBalance() >= amount) {
                from.withdraw(amount);
                to.deposit(amount);
            }
        }
    }
}

13. Thread Pools — ExecutorService

Creating a new thread for every task is expensive. Thread pools reuse a fixed set of threads, queuing tasks when all threads are busy. This is how Zerodha handles 10 lakh concurrent requests with far fewer actual threads.

Java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ZerodhaThreadPool {
    public static void main(String[] args) {
        // Create a pool of 4 threads — reused for all orders
        ExecutorService pool = Executors.newFixedThreadPool(4);

        String[] orders = {
            "BUY RELIANCE 100", "SELL TCS 50", "BUY INFY 200",
            "SELL HDFC 75", "BUY WIPRO 150", "SELL SBI 300",
            "BUY BHARTIARTL 80", "SELL HCLTECH 120"
        };

        for (String order : orders) {
            pool.submit(() -> {
                System.out.println(Thread.currentThread().getName() + " → Processing: " + order);
                try { Thread.sleep(500); } catch (InterruptedException e) { }
                System.out.println(Thread.currentThread().getName() + " → Completed: " + order + " ✅");
            });
        }

        pool.shutdown();  // No new tasks accepted; existing tasks complete
        System.out.println("All orders submitted to pool.");
    }
}
pool-1-thread-1 → Processing: BUY RELIANCE 100 pool-1-thread-2 → Processing: SELL TCS 50 pool-1-thread-3 → Processing: BUY INFY 200 pool-1-thread-4 → Processing: SELL HDFC 75 All orders submitted to pool. pool-1-thread-1 → Completed: BUY RELIANCE 100 ✅ pool-1-thread-1 → Processing: BUY WIPRO 150 pool-1-thread-2 → Completed: SELL TCS 50 ✅ pool-1-thread-2 → Processing: SELL SBI 300 ...
In production, NEVER create unbounded threads. Use Executors.newFixedThreadPool(n) where n ≈ number of CPU cores for CPU-bound tasks, or n ≈ cores × 2 for I/O-bound tasks. Zerodha uses Netty's event loop (similar concept) to handle lakhs of connections with just a few threads.

14. Full Code: FileReaderWithResources

Java
import java.io.*;

public class FileReaderWithResources {
    public static void main(String[] args) {
        String filename = "transactions.csv";

        // try-with-resources — auto-closes reader
        try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
            String line;
            int lineNum = 0;
            System.out.println("📄 Reading " + filename + "...");

            while ((line = reader.readLine()) != null) {
                lineNum++;
                System.out.println("  Line " + lineNum + ": " + line);
            }

            System.out.println("✅ Total lines read: " + lineNum);

        } catch (FileNotFoundException e) {
            System.out.println("❌ File not found: " + filename);
            System.out.println("💡 Create the file or check the path.");
        } catch (IOException e) {
            System.out.println("❌ Error reading file: " + e.getMessage());
        }
        // reader.close() is automatically called here — even if exception occurred!
    }
}

15. Full Code: BankTransferThreads

Java
public class BankTransferThreads {
    static class Account {
        private final int id;
        private double balance;

        public Account(int id, double balance) {
            this.id = id;
            this.balance = balance;
        }

        public int getId() { return id; }
        public double getBalance() { return balance; }

        public void deposit(double amt) { balance += amt; }

        public void withdraw(double amt) throws InsufficientBalanceException {
            if (amt > balance) {
                throw new InsufficientBalanceException(
                    "Account " + id + ": ₹" + balance + " < ₹" + amt, balance, amt);
            }
            balance -= amt;
        }
    }

    public static void safeTransfer(Account from, Account to, double amount) {
        // Lock ordering by account ID to prevent deadlock
        Account first  = from.getId() < to.getId() ? from : to;
        Account second = from.getId() < to.getId() ? to : from;

        synchronized (first) {
            synchronized (second) {
                try {
                    from.withdraw(amount);
                    to.deposit(amount);
                    System.out.println("✅ " + Thread.currentThread().getName() +
                        ": ₹" + amount + " transferred from Acc-" + from.getId() +
                        " to Acc-" + to.getId());
                } catch (InsufficientBalanceException e) {
                    System.out.println("❌ " + Thread.currentThread().getName() +
                        ": " + e.getMessage());
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Account ravi  = new Account(1001, 50000);
        Account priya = new Account(1002, 30000);

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) safeTransfer(ravi, priya, 5000);
        }, "NEFT-Thread");

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 3; i++) safeTransfer(priya, ravi, 8000);
        }, "UPI-Thread");

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println("\n📊 Final Balances:");
        System.out.println("  Ravi  (Acc-1001): ₹" + ravi.getBalance());
        System.out.println("  Priya (Acc-1002): ₹" + priya.getBalance());
    }
}
Section D

Learn by Doing — 3-Tier Lab Structure (Zerodha Order Book)

🟢 Tier 1 — GUIDED: Basic Exception Handling for Zerodha Orders

⏱️ 45–60 minutesBeginnerStep-by-step guided

Step 1: Create OrderException

Create a custom checked exception OrderException that extends Exception with fields for orderId and reason.

Step 2: Create Order Validator

Write a method validateOrder(String stock, int qty, double price) that throws OrderException if: stock symbol is null/empty, quantity ≤ 0, or price ≤ 0.

Step 3: Handle with try-catch-finally

In main, call validateOrder with valid and invalid inputs. Catch exceptions and print user-friendly messages. Use finally to log "Order validation attempt completed."

Step 4: Test edge cases

Test with: null stock, negative quantity, zero price, valid order. Verify each exception is caught properly.

🟡 Tier 2 — SEMI-GUIDED: Multithreaded Order Processing

⏱️ 90 minutesIntermediateHints provided

Your Mission:

Build a multithreaded Zerodha order processor where 3 trader threads submit orders to a shared order book and 2 matching engine threads execute them.

Hints:

  1. Use the ZerodhaOrderBook class from Section C as your base
  2. Add exception handling: invalid stock symbols should throw InvalidStockSymbolException
  3. Use synchronized on the order queue
  4. Implement wait() when queue is full (capacity 10) or empty
  5. Use notifyAll() after each add/remove
Stretch Goal: Add a Thread.sleep(random) to simulate real-world network latency. Add a shutdown mechanism using a poison pill ("SHUTDOWN" order).

🔴 Tier 3 — OPEN CHALLENGE: Full Zerodha Simulation with Thread Pool

⏱️ 2–3 hoursAdvancedNo instructions — real-world mini-project

The Brief:

Build a complete stock exchange simulation with:

  1. Thread Pool: Use ExecutorService with 8 threads
  2. Custom Exceptions: InsufficientBalanceException, MarketClosedException, InvalidOrderException
  3. Producer-Consumer: Buy/Sell order queues with wait/notify
  4. Synchronized portfolio: Thread-safe balance updates
  5. Exception chaining: NetworkException → OrderFailedException
  6. try-with-resources: Log all transactions to a file
  7. Assertions: Assert portfolio balance never goes negative
This project demonstrates production-grade multithreading skills. Add it to your GitHub portfolio. Mention in interviews: "I built a concurrent order matching engine with thread pools, synchronized state, and custom exception chains." Fintech companies love this.
Section E

Problem Set — Syntax, Programming, Industry & Interview

Syntax Questions (5)

SQ1. Identify and fix the error in this code:

Java
try {
    int x = 10 / 0;
} catch (Exception e) {
    System.out.println("General");
} catch (ArithmeticException e) {  // ← ERROR!
    System.out.println("Arithmetic");
}

Answer: Compilation error — specific exception (ArithmeticException) must come BEFORE general exception (Exception). Reverse the catch order.

SQ2. What is the output?

Java
System.out.println("A");
try {
    System.out.println("B");
    int x = 10/0;
    System.out.println("C");
} catch(ArithmeticException e) {
    System.out.println("D");
} finally {
    System.out.println("E");
}
System.out.println("F");

Answer: A B D E F — "C" is skipped (exception occurred before it), finally always runs, execution continues after try-catch.

SQ3. Will this compile?

Java
public void readFile() {
    FileReader fr = new FileReader("test.txt");
}

Answer: No — FileReader constructor throws checked FileNotFoundException. Must use try-catch or add throws FileNotFoundException.

SQ4. Spot the bug:

Java
Thread t = new Thread(() -> System.out.println("Hello"));
t.run();  // ← BUG!

Answer: Calling run() executes in the CURRENT thread, not a new one. Must call t.start() to create a new thread.

SQ5. What does assert x > 0 : "Negative!"; do when run with java -ea and x = -5?

Answer: Throws AssertionError with message "Negative!" because the condition x > 0 is false and assertions are enabled.

Programming Questions (8)

PQ1. Write a Java program that takes an array of integers and handles ArrayIndexOutOfBoundsException when accessing an invalid index. Print the exception message and the valid range.

PQ2. Create a BankAccount class with withdraw() that throws a custom InsufficientFundsException. Test with multiple withdrawal attempts in a loop.

PQ3. Write a program using try-with-resources to read a file line by line. Handle FileNotFoundException and IOException separately with meaningful messages.

PQ4. Create two threads: one prints even numbers (2–20) and the other prints odd numbers (1–19). Use Thread.sleep(100) between each print. Observe interleaved output.

PQ5. Implement a Counter class with increment() method. Create 3 threads that each increment the counter 1000 times. First run without synchronized (show bug), then with synchronized (show fix). Print final count.

PQ6. Write a program demonstrating multi-catch: parse an integer from a string and access an array element. Handle NumberFormatException | ArrayIndexOutOfBoundsException in a single catch block.

PQ7. Create a producer-consumer program where a producer adds numbers 1–10 to a shared list, and a consumer removes and prints them. Use wait() and notify().

PQ8. Write a program using ExecutorService.newFixedThreadPool(3) that processes 10 tasks. Each task prints the thread name and a task number with a 500ms delay.

Industry Questions (3) — Paytm Custom Exception Chain

IQ1. Design a Paytm payment exception hierarchy: PaymentException (base) → InsufficientBalanceException, UPITimeoutException, InvalidUPIIdException. Implement exception chaining where UPITimeoutException wraps a java.net.SocketTimeoutException. Write the full implementation with test cases.

IQ2. Zerodha's matching engine processes buy and sell orders concurrently. Design a MatchingEngine class with a thread-safe order book (use synchronized + wait/notify). Handle cases: market closed (throw MarketClosedException), invalid symbol (throw InvalidSymbolException), insufficient margin (throw InsufficientMarginException). Use exception chaining to wrap low-level SQLException in business exceptions.

IQ3. HDFC Bank needs a multithreaded NEFT processing system. Design a system where: (a) 5 customer threads submit NEFT requests, (b) 2 processor threads process them from a shared queue, (c) use ExecutorService for the processor pool, (d) custom exceptions for invalid IFSC code, daily limit exceeded, and beneficiary not found. Write complete code with proper shutdown.

Interview Questions (3)

INT1. "What is the difference between throw and throws? When would you use each in a fintech application like Razorpay?"

Model Answer: throw is used inside a method body to actually throw an exception object (e.g., throw new PaymentException("Gateway timeout")). throws is used in the method signature to declare that the method might throw specified checked exceptions, forcing callers to handle them. In Razorpay, a processPayment() method would declare throws PaymentException, GatewayException in its signature, and internally use throw to create specific exception instances when failures occur.

INT2. "Explain what happens if you call run() instead of start() on a Thread object. How would this affect performance in a system like Zerodha?"

Model Answer: Calling run() executes the method in the CURRENT thread — no new thread is created. It becomes a normal method call. In Zerodha, if you called run() instead of start() for each order, all 10 lakh orders would process sequentially on one thread instead of concurrently. This would turn millisecond execution into hours, making the platform unusable during market opening.

INT3. "How do you prevent deadlock in a banking system where multiple threads transfer money between accounts?"

Model Answer: Use lock ordering — always acquire locks on accounts in a consistent order (e.g., by account number). If Thread A transfers from Acc-1001 to Acc-1002, it locks 1001 first, then 1002. If Thread B transfers from Acc-1002 to Acc-1001, it also locks 1001 first (not 1002). Since both threads acquire locks in the same order, deadlock is impossible. Additionally, use tryLock() with timeout for robustness.

Section F

MCQ Assessment Bank — 30 Questions (Bloom's Mapped)

Remember / Identify (Q1–Q6)

Q1

Which class is at the top of Java's exception hierarchy?

  1. Exception
  2. Error
  3. Throwable
  4. RuntimeException
Remember
✅ Answer: (C) Throwable — It is the superclass of both Error and Exception. All exceptions and errors extend Throwable.
Q2

Which of the following is a checked exception?

  1. NullPointerException
  2. ArrayIndexOutOfBoundsException
  3. IOException
  4. ArithmeticException
Remember
✅ Answer: (C) IOException — Checked exceptions extend Exception (not RuntimeException). The compiler forces you to handle them with try-catch or throws.
Q3

The finally block is executed:

  1. Only when an exception occurs
  2. Only when no exception occurs
  3. Always, regardless of exception
  4. Only with try-with-resources
Remember
✅ Answer: (C) — The finally block always executes whether an exception occurs or not, whether it's caught or not. Only System.exit() prevents finally from running.
Q4

To create a thread using lambda, the interface used is:

  1. Callable
  2. Runnable
  3. Thread
  4. Executor
Remember
✅ Answer: (B) Runnable — Runnable is a @FunctionalInterface with a single abstract method run(), making it compatible with lambda expressions.
Q5

Which keyword is used to declare that a method might throw an exception?

  1. throw
  2. throws
  3. try
  4. catch
Remember
✅ Answer: (B) throws — Used in method signature: void method() throws IOException. 'throw' is used inside the method body to actually throw an exception.
Q6

Which of these is NOT a valid thread state in Java?

  1. NEW
  2. RUNNING
  3. BLOCKED
  4. TERMINATED
Remember
✅ Answer: (B) RUNNING — Java's Thread.State enum has: NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED. "RUNNING" is not a formal state — it's a subset of RUNNABLE.

Understand / Explain (Q7–Q12)

Q7

Why does Java require checked exceptions to be handled at compile time?

  1. To make programs run faster
  2. To force developers to plan for recoverable error scenarios
  3. To prevent runtime exceptions
  4. To reduce memory usage
Understand
✅ Answer: (B) — Checked exceptions represent recoverable conditions (file not found, network down). Java's philosophy: if a failure is predictable and recoverable, the compiler should ensure the developer has a plan for it.
Q8

What happens if you call thread.run() instead of thread.start()?

  1. A new thread is created
  2. The run() method executes in the current thread
  3. A compilation error occurs
  4. The thread is started twice
Understand
✅ Answer: (B) — Calling run() directly is just a regular method call — no new thread is created. The code runs sequentially in the current thread. You must call start() to create a new OS thread that then invokes run().
Q9

Why is try-with-resources preferred over try-catch-finally for resource management?

  1. It's faster at runtime
  2. It automatically closes resources, even if exceptions occur, reducing boilerplate and preventing leaks
  3. It catches more exceptions
  4. It works with all Java classes
Understand
✅ Answer: (B) — try-with-resources guarantees close() is called on AutoCloseable resources, even during exceptions. The old way required verbose finally blocks with nested try-catch for close() itself.
Q10

What is a race condition?

  1. Two threads running at the same speed
  2. Two threads competing to finish first
  3. Two threads accessing shared data without synchronization, causing unpredictable results
  4. A thread running too fast for the CPU
Understand
✅ Answer: (C) — A race condition occurs when multiple threads read/write shared data concurrently without proper synchronization. The result depends on thread scheduling order — it's non-deterministic and a serious bug.
Q11

Why should assertions NOT be used for public method parameter validation?

  1. Assertions are too slow
  2. Assertions are disabled by default in production, so validation would be skipped
  3. Assertions can't check parameters
  4. Assertions cause memory leaks
Understand
✅ Answer: (B) — Assertions are disabled by default (need -ea flag). In production, parameter validation code inside assert statements would not execute, leaving public methods unprotected. Use IllegalArgumentException instead.
Q12

Why does wait() release the lock while sleep() does not?

  1. wait() is more efficient
  2. wait() is designed for inter-thread communication — other threads need the lock to modify shared state and notify
  3. sleep() is a static method
  4. wait() can only be used inside main()
Understand
✅ Answer: (B) — wait() is for coordination between threads. The waiting thread releases the lock so another thread can enter the synchronized block, modify state, and call notify(). sleep() just pauses — no coordination needed, so it keeps the lock.

Apply / Implement (Q13–Q18)

Q13

What is the output of this code?

try {
    System.out.print("A");
    throw new RuntimeException();
} catch(RuntimeException e) {
    System.out.print("B");
} finally {
    System.out.print("C");
}
System.out.print("D");
  1. ABCD
  2. ABD
  3. ACD
  4. ABC
Apply
✅ Answer: (A) ABCD — A prints, exception thrown, caught in catch (B prints), finally always runs (C prints), execution continues after try-catch (D prints).
Q14

To create a custom checked exception, you extend:

  1. RuntimeException
  2. Error
  3. Exception
  4. Throwable
Apply
✅ Answer: (C) Exception — Extending Exception (not RuntimeException) creates a checked exception. Extending RuntimeException creates an unchecked exception.
Q15

Which code correctly creates a thread using a lambda?

  1. new Thread(() -> System.out.println("Hi")).start();
  2. new Runnable(() -> System.out.println("Hi")).start();
  3. Thread.run(() -> System.out.println("Hi"));
  4. new Thread({System.out.println("Hi")}).start();
Apply
✅ Answer: (A) — Lambda replaces Runnable's run() method. Pass it to Thread constructor and call start(). Options B, C, D have syntax errors.
Q16

What must a class implement to be used in try-with-resources?

  1. Serializable
  2. Closeable only
  3. AutoCloseable
  4. Runnable
Apply
✅ Answer: (C) AutoCloseable — Any class implementing AutoCloseable (which has close() method) can be used in try-with-resources. Closeable extends AutoCloseable, so it also works.
Q17

How do you enable assertions when running a Java program?

  1. java -assert MyProgram
  2. java -ea MyProgram
  3. java -enable-assertions MyProgram
  4. Both B and C
Apply
✅ Answer: (D) — Both -ea (short form) and -enableassertions (full form) enable assertions. By default, assertions are disabled in production.
Q18

What does thread.join() do?

  1. Merges two threads into one
  2. Pauses the current thread until the specified thread completes
  3. Starts a new thread
  4. Kills the specified thread
Apply
✅ Answer: (B) — join() makes the calling thread wait until the thread on which join() is called terminates. Useful for ensuring all worker threads complete before proceeding.

Analyze / Compare (Q19–Q23)

Q19

In multi-catch catch(IOException | SQLException e), the variable e is:

  1. Of type Object
  2. Of type Exception
  3. Implicitly final — cannot be reassigned
  4. Mutable — can be reassigned
Analyze
✅ Answer: (C) — In multi-catch, the variable is implicitly final. Its compile-time type is the common superclass of the listed exceptions. Reassigning e would create type ambiguity.
Q20

Which approach to thread creation allows extending another class?

  1. extends Thread
  2. implements Runnable
  3. Both
  4. Neither
Analyze
✅ Answer: (B) — Java has single inheritance. If you extend Thread, you can't extend anything else. Implementing Runnable (interface) leaves the extends slot free for another class.
Q21

What is the key difference between wait() and sleep()?

  1. wait() is faster
  2. sleep() can only be used in main()
  3. wait() releases the monitor lock; sleep() does not
  4. wait() is deprecated
Analyze
✅ Answer: (C) — wait() releases the object's monitor lock so other threads can enter synchronized blocks. sleep() holds the lock. wait() must be called from synchronized context; sleep() can be called anywhere.
Q22

Exception chaining (initCause) is useful because:

  1. It hides the original exception
  2. It wraps low-level exceptions in business-meaningful exceptions while preserving the root cause
  3. It automatically retries the operation
  4. It makes exceptions unchecked
Analyze
✅ Answer: (B) — Exception chaining lets you throw a business exception (PaymentException) while attaching the technical root cause (SocketTimeoutException). Debugging shows both layers — business context and technical detail.
Q23

Why is notifyAll() generally preferred over notify()?

  1. notifyAll() is faster
  2. notify() might wake up a thread that can't proceed, causing a missed signal
  3. notify() is deprecated
  4. notifyAll() uses less memory
Analyze
✅ Answer: (B) — notify() wakes up ONE random waiting thread. If that thread can't proceed (e.g., wrong condition), the signal is lost. notifyAll() wakes ALL waiting threads — each re-checks its condition, and the right one proceeds. Safer and more robust.

Evaluate / Justify (Q24–Q27)

Q24

When should you use ExecutorService thread pool instead of manually creating threads?

  1. When you have exactly 2 threads
  2. When you have many short-lived tasks and want thread reuse, bounded resources, and graceful shutdown
  3. When you don't need concurrency
  4. When threads don't share data
Evaluate
✅ Answer: (B) — Thread pools reuse a fixed set of threads for many tasks, preventing the overhead of creating/destroying threads. They provide bounded concurrency (won't create 10 lakh threads) and graceful shutdown via shutdown().
Q25

For Zerodha's order validation, should InvalidStockSymbolException be checked or unchecked?

  1. Checked — because it's a recoverable business rule violation
  2. Unchecked — because it's a programming error (bad input should be validated earlier)
  3. Either works equally well
  4. It should be an Error
Evaluate
✅ Answer: (B) — Invalid stock symbols are input validation failures — a programming bug. The UI should validate before reaching the backend. Making it unchecked (extending RuntimeException) signals "fix your code" rather than "plan for this failure."
Q26

In a banking system, which deadlock prevention strategy is most practical?

  1. Never use locks
  2. Lock ordering — always acquire account locks in ascending account number order
  3. Use Thread.sleep() between locks
  4. Use only one thread
Evaluate
✅ Answer: (B) — Lock ordering is the most practical and widely used strategy. By always locking accounts in the same order (e.g., lower account number first), you eliminate circular wait — the necessary condition for deadlock.
Q27

Why should you use while loop (not if) when checking conditions before wait()?

  1. while is faster than if
  2. Spurious wakeups can occur — the thread must re-check the condition after waking
  3. if doesn't compile with wait()
  4. while prevents deadlock
Evaluate
✅ Answer: (B) — Java threads can wake up without notify() being called (spurious wakeups). Also, notifyAll() wakes ALL waiting threads, but only one should proceed. A while loop re-checks the condition, ensuring the thread only proceeds when the condition is actually true.

Create / Design (Q28–Q30)

Q28

You're designing a Paytm payment system. Which exception handling design is best?

  1. Catch all exceptions with catch(Exception e) and log them
  2. Create a hierarchy: PaymentException → InsufficientBalanceException, UPITimeoutException, InvalidUPIException — with exception chaining for root cause analysis
  3. Use only unchecked exceptions for everything
  4. Don't use exceptions — use return codes (0 = success, -1 = failure)
Create
✅ Answer: (B) — A well-designed exception hierarchy with specific subtypes allows targeted handling. Exception chaining preserves the root cause (e.g., SocketTimeoutException → UPITimeoutException). This is the standard in fintech.
Q29

For a Zerodha-like system handling 10 lakh concurrent orders, which threading design is optimal?

  1. Create a new Thread for each order
  2. Use a single thread processing orders sequentially
  3. Use ExecutorService with a fixed thread pool sized to CPU cores, with a bounded blocking queue
  4. Use Thread.sleep() to slow down orders
Create
✅ Answer: (C) — A fixed thread pool with bounded queue provides: thread reuse (efficiency), bounded resources (won't crash with 10 lakh threads), backpressure (queue blocks when full), and graceful shutdown. This is how real trading systems work.
Q30

A banking system needs to: validate inputs (assertions), handle business errors (custom exceptions), process transactions concurrently (threads), and prevent data corruption (synchronization). Which combination is correct?

  1. assert for input validation, catch(Exception e) for all errors, extends Thread for concurrency
  2. IllegalArgumentException for public API validation, assert for internal invariants, custom checked exceptions for business errors, implements Runnable with synchronized and thread pools
  3. Only use RuntimeException for everything
  4. Use single thread to avoid all concurrency issues
Create
✅ Answer: (B) — This follows Java best practices: IllegalArgumentException for public input validation (not assertions — they're disabled in production), assert for internal sanity checks, specific checked exceptions for recoverable business errors, Runnable + synchronized + thread pools for scalable concurrency.
Section G

Short Answer Questions (8)

SA1. Differentiate between checked and unchecked exceptions with two examples each.

Answer: Checked exceptions are verified at compile time — the compiler forces you to handle them using try-catch or declare them with throws. They represent recoverable external conditions. Examples: IOException (file/network I/O failure), SQLException (database error). Unchecked exceptions occur at runtime due to programming bugs — the compiler does NOT force handling. They extend RuntimeException. Examples: NullPointerException (accessing null reference), ArrayIndexOutOfBoundsException (invalid array index). Rule of thumb: checked = external failure (plan for it), unchecked = your bug (fix it).

SA2. Explain the purpose of the finally block. Give a scenario where it is essential.

Answer: The finally block executes always — whether an exception occurs or not, whether it's caught or uncaught. Its purpose is resource cleanup: closing files, database connections, network sockets, or releasing locks. It's essential when reading a bank transaction file: if an exception occurs mid-read, the file handle must still be released. Without finally, the file remains locked, preventing other processes from accessing it. Only System.exit() prevents finally from executing.

SA3. What is try-with-resources? Which interface must a class implement to use it?

Answer: Try-with-resources (Java 7+) automatically closes resources declared in the try header when the block exits — even if an exception occurs. The class must implement AutoCloseable interface (which has a single close() method). Example: try (BufferedReader br = new BufferedReader(new FileReader("data.csv"))) { ... }br.close() is called automatically. Closeable (which extends AutoCloseable) also works. This eliminates verbose finally cleanup blocks and prevents resource leaks.

SA4. Explain the difference between throw and throws with code examples.

Answer: throw is used inside a method body to actually throw an exception object: throw new InsufficientBalanceException("Low balance");. It can throw one exception at a time. throws is used in the method signature to declare that the method may propagate certain checked exceptions to the caller: void pay(double amt) throws InsufficientBalanceException, IOException. The caller must then handle these exceptions. throw = action (throwing), throws = declaration (warning).

SA5. Describe the five states in a Java thread's lifecycle.

Answer: (1) NEW: Thread object created via new Thread(), but start() not yet called. (2) RUNNABLE: After start() is called — thread is ready to run and waiting for CPU scheduling. (3) BLOCKED: Thread is waiting to acquire a monitor lock to enter a synchronized block. (4) WAITING/TIMED_WAITING: Thread called wait(), join(), or sleep() — it's paused. TIMED_WAITING has a timeout; WAITING waits indefinitely until notify(). (5) TERMINATED: Thread's run() method completed or an unhandled exception killed it. Cannot be restarted.

SA6. What is synchronization? Why is it needed in multithreading?

Answer: Synchronization is a mechanism that ensures only one thread can access a critical section (shared resource) at a time. It's needed to prevent race conditions — when two threads simultaneously read/modify shared data, causing corrupted or inconsistent results. In Java, the synchronized keyword locks a method or block on a monitor object. Example: Two threads withdrawing from the same bank account — without synchronization, both might check balance (₹10,000) simultaneously and both withdraw ₹8,000, leaving the account at -₹6,000 instead of correctly failing the second withdrawal.

SA7. Explain wait() and notify() with a real-world analogy.

Answer: wait() and notify() enable inter-thread communication. wait() pauses the current thread and releases the lock, allowing other threads to work. notify() wakes up one waiting thread; notifyAll() wakes all. Analogy: Think of a restaurant kitchen. The waiter (consumer) wait()s when no orders are ready. The chef (producer) prepares a dish and calls notify() — "Order ready!" The waiter wakes up, picks up the dish. If the kitchen is full (no counter space), the chef wait()s until the waiter picks up dishes and notify()s — "Counter cleared!"

SA8. What is a deadlock? How can it be prevented?

Answer: Deadlock occurs when two or more threads are permanently blocked, each holding a lock the other needs. Example: Thread A locks Account-1 and waits for Account-2; Thread B locks Account-2 and waits for Account-1 — neither can proceed. Prevention strategies: (1) Lock ordering — always acquire locks in a consistent order (e.g., ascending account number). (2) Lock timeout — use tryLock(timeout) instead of indefinite waiting. (3) Avoid nested locks — minimize holding multiple locks. (4) Use concurrent collectionsConcurrentHashMap, BlockingQueue handle synchronization internally.

Section H

Long Answer Questions (3)

LA1. Explain the complete exception hierarchy in Java. Differentiate between checked and unchecked exceptions with examples. Describe the execution flow of try-catch-finally with multiple catch blocks. (10 marks)

Answer:

Exception Hierarchy: All exceptions in Java descend from Throwable. It has two branches: Error (unrecoverable JVM-level problems like OutOfMemoryError, StackOverflowError — should NOT be caught) and Exception (recoverable application-level problems). Exception further splits into checked exceptions (like IOException, SQLException) and unchecked exceptions (RuntimeException and its subclasses like NullPointerException, ArithmeticException).

Checked vs Unchecked:

AspectCheckedUnchecked
Verified atCompile timeRuntime
Must handle?Yes — try-catch or throwsNo — optional
ExtendsException (not RuntimeException)RuntimeException
RepresentsExternal failures (file, network, DB)Programming bugs (null, bad index)
ExamplesIOException, ClassNotFoundExceptionNullPointerException, ClassCastException

try-catch-finally Execution Flow:

Case 1 — No exception: try block executes fully → catch blocks skipped → finally executes → code after try-catch-finally continues.

Case 2 — Exception caught: try block executes until exception → remaining try code SKIPPED → matching catch block executes → finally executes → code continues.

Case 3 — Exception NOT caught: try block executes until exception → no matching catch → finally STILL executes → exception propagates up the call stack.

Multiple catch blocks: Checked top-to-bottom. First matching catch handles the exception. Important: Specific exceptions MUST come before general ones — otherwise compilation error (unreachable code). Example: catch(FileNotFoundException e) must come before catch(IOException e) because FNFE is a subclass of IOE.

LA2. Describe multithreading in Java. Explain Thread class vs Runnable interface with code examples. Describe the complete thread lifecycle with a diagram. (10 marks)

Answer:

Multithreading is the ability to run multiple threads (lightweight processes) concurrently within a single program. Each thread has its own call stack but shares the process's heap memory. Java supports multithreading natively through the java.lang.Thread class and java.lang.Runnable interface.

Method 1 — Extending Thread:

Java
class MyThread extends Thread {
    public void run() { System.out.println("Thread running: " + getName()); }
}
// Usage: new MyThread().start();

Limitation: Can't extend another class (single inheritance).

Method 2 — Implementing Runnable:

Java
class MyTask implements Runnable {
    public void run() { System.out.println("Task running"); }
}
// Usage: new Thread(new MyTask()).start();

Advantage: Can extend another class + better separation of task from thread mechanism.

Method 3 — Lambda (Java 8+):

Java
new Thread(() -> System.out.println("Lambda thread")).start();

Advantage: Most concise. Best for short, inline tasks.

Thread Lifecycle:

NEW (created, not started) → start()RUNNABLE (ready for CPU) → scheduled → RUNNING (executing run()) → sleep()/wait()/blocked on lockBLOCKED/WAITING/TIMED_WAITINGnotify()/sleep over/lock acquired → back to RUNNABLErun() completes → TERMINATED (dead, cannot restart).

Key methods: start() (creates new OS thread), run() (contains thread logic — never call directly), sleep(ms) (pauses), join() (wait for thread to die), interrupt() (signal thread to stop), yield() (hint to give up CPU).

LA3. What is synchronization in Java? Explain synchronized methods and synchronized blocks. Describe the producer-consumer problem with wait() and notify(). How does ExecutorService improve thread management? (10 marks)

Answer:

Synchronization prevents race conditions by ensuring mutual exclusion — only one thread can execute a synchronized section at a time. Java provides two mechanisms:

Synchronized Method: public synchronized void withdraw(double amt) — locks the entire method on the object's intrinsic monitor. When Thread A enters, Thread B must wait at the method entrance.

Synchronized Block: synchronized(lockObject) { ... } — locks only the critical section, not the entire method. More granular, better performance for methods with both synchronized and unsynchronized code.

Producer-Consumer Problem: A classic coordination problem. Producer creates data, Consumer processes it. They share a bounded buffer (queue). Producer must wait() when buffer is full; Consumer must wait() when buffer is empty. When Producer adds data, it calls notifyAll() to wake waiting consumers. When Consumer removes data, it calls notifyAll() to wake waiting producers. Important: always check conditions in a while loop (not if) due to spurious wakeups.

ExecutorService Thread Pools: Creating a new thread per task is expensive (OS overhead). ExecutorService manages a pool of reusable threads. Executors.newFixedThreadPool(n) creates n threads that process submitted tasks from a queue. Benefits: (1) Thread reuse — no creation overhead. (2) Bounded concurrency — won't exhaust system resources. (3) Task queuing — tasks wait when all threads are busy. (4) Graceful shutdown — shutdown() stops accepting new tasks; awaitTermination() waits for completion. In production systems like Zerodha, thread pools handle millions of requests with a small, fixed number of threads.

Section I

Lab Programs — Lab 1 & Lab 2

🔬 Lab 1: Exception Handling — All Keywords (try, catch, finally, throw, throws, assert)

⏱️ 90 minutesIntermediateCovers: try, catch, finally, throw, throws, assert + need of assertion

Aim

To demonstrate all Java exception handling keywords (try, catch, finally, throw, throws) and understand the need and usage of assertions (assert with -ea).

Theory

Exception handling is Java's mechanism for dealing with runtime errors gracefully. The five keywords form a complete system: try wraps risky code, catch handles specific exceptions, finally ensures cleanup, throw creates exceptions manually, and throws delegates handling to callers. Assertions (assert) are compile-time debugging aids that verify assumptions — they are disabled by default and enabled with java -ea.

Need of Assertion: Assertions help catch bugs during development by checking conditions that should logically always be true at a certain point in the program. They are NOT for user input validation (use exceptions for that). They are for internal consistency checks — e.g., after sorting, assert the array is sorted. If an assertion fails, it reveals a logic error in the code itself. Assertions are disabled in production for performance, making them zero-overhead in deployed systems.

Program Code

Java — Lab1_ExceptionHandling.java
import java.io.*;

// Custom Checked Exception
class InsufficientBalanceException extends Exception {
    private double balance, amount;

    public InsufficientBalanceException(String msg, double bal, double amt) {
        super(msg);
        this.balance = bal;
        this.amount = amt;
    }
    public double getDeficit() { return amount - balance; }
}

public class Lab1_ExceptionHandling {

    // 'throws' — declares this method may throw a checked exception
    public static void withdraw(double balance, double amount)
            throws InsufficientBalanceException {

        // 'assert' — internal sanity check (enable with java -ea)
        assert balance >= 0 : "Balance cannot be negative: " + balance;
        assert amount > 0  : "Withdrawal amount must be positive: " + amount;

        if (amount > balance) {
            // 'throw' — actually throws the exception
            throw new InsufficientBalanceException(
                "Cannot withdraw ₹" + amount + " from balance ₹" + balance,
                balance, amount
            );
        }
        System.out.println("✅ Withdrawn ₹" + amount + ". Remaining: ₹" + (balance - amount));
    }

    public static void readFile(String filename) {
        // try-with-resources — auto-closes the reader
        try (BufferedReader br = new BufferedReader(new FileReader(filename))) {
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println("  📄 " + line);
            }
        } catch (FileNotFoundException e) {
            System.out.println("  ❌ File not found: " + filename);
        } catch (IOException e) {
            System.out.println("  ❌ IO Error: " + e.getMessage());
        }
    }

    public static void main(String[] args) {
        System.out.println("=== PART 1: try-catch-finally ===");

        // 'try' — wraps risky code
        try {
            int result = 100 / 0;
            System.out.println("Result: " + result);  // Never reached
        }
        // 'catch' — handles specific exception
        catch (ArithmeticException e) {
            System.out.println("❌ Caught: " + e.getMessage());
        }
        // 'finally' — ALWAYS executes
        finally {
            System.out.println("📋 Finally block executed (cleanup).");
        }

        System.out.println("\n=== PART 2: throw & throws (Custom Exception) ===");
        try {
            withdraw(5000, 3000);  // ✅ Success
            withdraw(2000, 5000);  // ❌ Throws exception
        } catch (InsufficientBalanceException e) {
            System.out.println("❌ " + e.getMessage());
            System.out.println("💡 Deficit: ₹" + e.getDeficit());
        }

        System.out.println("\n=== PART 3: Multi-catch ===");
        try {
            String str = null;
            System.out.println(str.length());
        } catch (NullPointerException | ArithmeticException e) {
            System.out.println("❌ Multi-catch: " + e.getClass().getSimpleName());
        }

        System.out.println("\n=== PART 4: try-with-resources ===");
        readFile("nonexistent.txt");

        System.out.println("\n=== PART 5: Assertions (run with java -ea) ===");
        int age = -5;
        assert age >= 0 : "Age cannot be negative: " + age;
        System.out.println("Age: " + age);  // Only reached if assertion passes
    }
}
=== PART 1: try-catch-finally === ❌ Caught: / by zero 📋 Finally block executed (cleanup). === PART 2: throw & throws (Custom Exception) === ✅ Withdrawn ₹3000.0. Remaining: ₹2000.0 ❌ Cannot withdraw ₹5000.0 from balance ₹2000.0 💡 Deficit: ₹3000.0 === PART 3: Multi-catch === ❌ Multi-catch: NullPointerException === PART 4: try-with-resources === ❌ File not found: nonexistent.txt === PART 5: Assertions (run with java -ea) === Exception in thread "main" java.lang.AssertionError: Age cannot be negative: -5

Viva Questions — Lab 1

V1. What is the difference between Error and Exception?
A: Error represents serious JVM-level problems (OutOfMemoryError) that cannot be recovered. Exception represents application-level problems that can be handled. Never catch Error in production code.
V2. Can we have try without catch?
A: Yes — try can be paired with just finally (no catch). Example: try { ... } finally { cleanup(); }. The exception propagates up, but finally still executes.
V3. What happens if finally has a return statement?
A: The finally's return overrides any return in try or catch. This is a bad practice and should be avoided — it can hide exceptions and confuse debugging.
V4. Why are assertions disabled by default?
A: Assertions are for development-time debugging, not production error handling. Disabling them in production eliminates performance overhead. If assertions controlled business logic, disabling them would change program behavior — that's why they're only for sanity checks.
V5. What is exception chaining and when is it useful?
A: Exception chaining wraps a low-level exception (e.g., SocketTimeoutException) inside a business-level exception (e.g., PaymentFailedException) using initCause() or constructor parameter. It preserves the root cause for debugging while providing meaningful context at the application level.

🔬 Lab 2: Multithreading Using Lambda — Runnable via Lambda & Thread Lifecycle

⏱️ 90 minutesIntermediateCovers: Runnable via lambda, thread lifecycle, synchronized, sleep, join

Aim

To demonstrate multithreading in Java using lambda expressions for Runnable, observe thread lifecycle states, and understand synchronization with practical examples.

Theory

Since Runnable is a @FunctionalInterface (single abstract method run()), it can be replaced by a lambda expression in Java 8+. This makes thread creation more concise. The thread lifecycle progresses through states: NEW → RUNNABLE → (BLOCKED/WAITING/TIMED_WAITING) → TERMINATED. Synchronization prevents race conditions when multiple threads access shared resources.

Program Code

Java — Lab2_MultithreadingLambda.java
public class Lab2_MultithreadingLambda {

    // Shared counter to demonstrate race condition + fix
    private static int counter = 0;
    private static final Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {

        // ===== PART 1: Thread creation via Lambda =====
        System.out.println("=== PART 1: Lambda Thread Creation ===");

        // Method 1: Lambda with Runnable
        Thread buyThread = new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                System.out.println("🟢 " + Thread.currentThread().getName() +
                    " → BUY Order #" + i);
                try { Thread.sleep(200); } catch (InterruptedException e) { }
            }
        }, "BuyThread");

        Thread sellThread = new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                System.out.println("🔴 " + Thread.currentThread().getName() +
                    " → SELL Order #" + i);
                try { Thread.sleep(300); } catch (InterruptedException e) { }
            }
        }, "SellThread");

        // ===== PART 2: Thread Lifecycle Observation =====
        System.out.println("\n=== PART 2: Thread Lifecycle States ===");
        System.out.println("buyThread state after new:   " + buyThread.getState());  // NEW

        buyThread.start();
        sellThread.start();
        System.out.println("buyThread state after start: " + buyThread.getState());  // RUNNABLE

        Thread.sleep(100);
        System.out.println("buyThread state during run:  " + buyThread.getState());  // TIMED_WAITING (sleeping)

        buyThread.join();   // Main thread waits for buyThread to finish
        sellThread.join();  // Main thread waits for sellThread to finish
        System.out.println("buyThread state after join:  " + buyThread.getState());  // TERMINATED

        // ===== PART 3: Race Condition Demo =====
        System.out.println("\n=== PART 3: Race Condition (WITHOUT sync) ===");
        counter = 0;

        Thread inc1 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) counter++;  // NOT synchronized!
        }, "Inc-1");

        Thread inc2 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) counter++;
        }, "Inc-2");

        inc1.start(); inc2.start();
        inc1.join();  inc2.join();
        System.out.println("Expected: 20000, Actual: " + counter + " (WRONG — race condition!)");

        // ===== PART 4: Fixed with Synchronized =====
        System.out.println("\n=== PART 4: Fixed (WITH synchronized) ===");
        counter = 0;

        Thread safe1 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                synchronized (lock) { counter++; }  // ✅ Synchronized!
            }
        }, "Safe-1");

        Thread safe2 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                synchronized (lock) { counter++; }
            }
        }, "Safe-2");

        safe1.start(); safe2.start();
        safe1.join();  safe2.join();
        System.out.println("Expected: 20000, Actual: " + counter + " (CORRECT ✅)");

        // ===== PART 5: Thread with interrupt =====
        System.out.println("\n=== PART 5: Thread Interrupt ===");
        Thread longTask = new Thread(() -> {
            try {
                System.out.println("⏳ Long task started (sleeping 10 sec)...");
                Thread.sleep(10000);
                System.out.println("Long task completed.");
            } catch (InterruptedException e) {
                System.out.println("⚡ Long task INTERRUPTED! Cleaning up...");
            }
        }, "LongTask");

        longTask.start();
        Thread.sleep(1000);
        longTask.interrupt();  // Cancel the long task after 1 sec
        longTask.join();

        System.out.println("\n✅ All multithreading demos complete!");
    }
}
=== PART 1: Lambda Thread Creation === === PART 2: Thread Lifecycle States === buyThread state after new: NEW buyThread state after start: RUNNABLE 🟢 BuyThread → BUY Order #1 🔴 SellThread → SELL Order #1 buyThread state during run: TIMED_WAITING 🟢 BuyThread → BUY Order #2 🔴 SellThread → SELL Order #2 🟢 BuyThread → BUY Order #3 🔴 SellThread → SELL Order #3 🟢 BuyThread → BUY Order #4 🟢 BuyThread → BUY Order #5 🔴 SellThread → SELL Order #4 🔴 SellThread → SELL Order #5 buyThread state after join: TERMINATED === PART 3: Race Condition (WITHOUT sync) === Expected: 20000, Actual: 17463 (WRONG — race condition!) === PART 4: Fixed (WITH synchronized) === Expected: 20000, Actual: 20000 (CORRECT ✅) === PART 5: Thread Interrupt === ⏳ Long task started (sleeping 10 sec)... ⚡ Long task INTERRUPTED! Cleaning up... ✅ All multithreading demos complete!

Viva Questions — Lab 2

V1. Why is Runnable preferred over extending Thread?
A: Java supports single inheritance. If you extend Thread, you cannot extend any other class. Implementing Runnable (interface) allows extending another class. It also separates the task (what to do) from the mechanism (thread management), following the Single Responsibility Principle.
V2. What is the difference between sleep() and wait()?
A: sleep() pauses the thread for a specified time and does NOT release the lock. wait() pauses the thread indefinitely (until notify()) and DOES release the lock. sleep() is for timed delays; wait() is for inter-thread communication. wait() must be called from synchronized context.
V3. Can a terminated thread be restarted?
A: No. Once a thread reaches TERMINATED state (run() completed or exception thrown), it cannot be restarted. Calling start() again throws IllegalThreadStateException. You must create a new Thread object.
V4. Why do we use synchronized block instead of synchronized method?
A: Synchronized block provides finer granularity — you lock only the critical section, not the entire method. This allows non-synchronized parts of the method to run concurrently, improving performance. Also, synchronized blocks can lock on any object, while synchronized methods always lock on 'this'.
V5. What is a daemon thread?
A: A daemon thread is a background service thread (e.g., garbage collector). It automatically terminates when all non-daemon (user) threads have finished. Set with thread.setDaemon(true) BEFORE calling start(). Used for logging, monitoring, cleanup tasks that shouldn't prevent JVM shutdown.
Section J

Industry Spotlight — A Day in the Life

👩‍💻 Meera Krishnamurthy, 30 — Senior Java Developer at Zerodha, Bangalore

Background: B.Tech (CSE) from RVCE, Bangalore. Started as a junior Java developer at a service company. Self-learned multithreading and concurrent programming through Zerodha's open-source projects on GitHub. Joined Zerodha's Kite trading platform team 4 years ago.

A Typical Day:

8:30 AM — Pre-market check. Review overnight exception logs from the order management system. Check if any InsufficientMarginException or OrderRejectedException patterns need attention.

9:00 AM — Market opening preparation. Ensure thread pools are scaled up for the 9:15 AM rush. Monitor JVM metrics — thread count, heap usage, GC pauses.

9:15 AM — Market opens. Watch the real-time dashboard as 10+ lakh concurrent order threads fire up. The order matching engine uses ExecutorService with optimized thread pools. Synchronized queues handle order book state.

10:30 AM — Code review for a junior developer's PR. Fix a potential deadlock in the portfolio update service — they forgot lock ordering. Add custom exception hierarchy for the new margin trading feature.

1:00 PM — Lunch break. Read about Project Loom (virtual threads) — Zerodha is evaluating it for the next platform upgrade.

2:00 PM — Work on the new UPI payment integration. Implement exception chaining: UPIGatewayException wrapping HttpTimeoutException. Write try-with-resources for connection pooling.

4:00 PM — Write unit tests for the multithreaded order processor. Use CountDownLatch and CyclicBarrier for test synchronization.

5:30 PM — Post-market analysis. Review today's exception statistics. Zero unhandled exceptions — clean day! Update Grafana dashboard for the ops team.

DetailInfo
Tools Used DailyJava 17, Spring Boot, Netty, ExecutorService, Kafka, PostgreSQL, Grafana, IntelliJ IDEA
Key SkillsMultithreading, Exception Design, Concurrent Collections, Thread Pools, Performance Tuning
Entry Salary₹6–10 LPA
Mid-Level (3–5 yrs)₹12–20 LPA
Senior (7+ yrs)₹25–45 LPA
Companies HiringZerodha, Razorpay, PhonePe, Paytm, HDFC Securities, Upstox, NSE (IT), Goldman Sachs India, Morgan Stanley India
Section K

Earn With It — Multithreaded Microservices ₹15K–₹50K/month

💰 Your Earning Path After This Unit

Portfolio Piece: "Concurrent Order Processing System" — a multithreaded Java application with custom exception hierarchy, thread pools, and synchronized state management. Push to GitHub with clean README.

Freelance Opportunities:

• Backend microservice development (Spring Boot + multithreading) — ₹15,000–₹30,000/project

• Exception handling audit for existing Java apps — ₹5,000–₹15,000

• Concurrent data processing scripts (CSV/JSON parsers) — ₹8,000–₹20,000

• API integration with proper error handling — ₹10,000–₹25,000

• Performance optimization (threading + connection pooling) — ₹20,000–₹50,000

PlatformBest ForTypical Rate
InternshalaJava backend internships + freelance₹8,000–₹20,000/month
UpworkJava microservice projects (global)$20–$60/hour
ToptalPremium Java developer network$40–$100/hour
LinkedInFull-time Java developer positions₹6–15 LPA (entry)
GitHubOpen-source contributions → job offersPortfolio → interviews
The highest-demand Java skills on Naukri.com (2024–2025): Multithreading, Spring Boot, Microservices, REST APIs, Kafka. If you can build a multithreaded microservice with proper exception handling, you're ahead of 80% of fresh graduates. Start with a Spring Boot REST API that processes requests concurrently — that's your interview goldmine.
Section L

Chapter Summary — Quick Revision

🧠 Exception Handling — Key Takeaways

Hierarchy: Throwable → Error (don't catch) + Exception → Checked (compile-time, must handle) + RuntimeException (unchecked, runtime bugs)

try-catch-finally: try wraps risky code, catch handles exceptions, finally ALWAYS runs (cleanup)

Multi-catch: catch(A | B e) — handles multiple exceptions in one block, variable is implicitly final

try-with-resources: Auto-closes AutoCloseable resources — no more verbose finally blocks

throw vs throws: throw = create & throw exception (method body), throws = declare exceptions (method signature)

Custom exceptions: Extend Exception (checked) or RuntimeException (unchecked) for business-specific errors

Exception chaining: initCause() wraps low-level exceptions in business exceptions, preserving root cause

Assertions: assert condition : message — development sanity checks, enable with -ea, NOT for production validation

⚡ Multithreading — Key Takeaways

Thread creation: extends Thread | implements Runnable | lambda (preferred: Runnable/lambda)

Lifecycle: NEW → RUNNABLE → RUNNING → BLOCKED/WAITING → TERMINATED

Key methods: start() (new thread), sleep() (pause), join() (wait for completion), interrupt() (cancel), yield() (hint)

synchronized: Prevents race conditions — method-level or block-level locking on monitor object

wait/notify: Inter-thread communication — wait() releases lock, notify/notifyAll() wakes waiting threads

Deadlock: Circular wait — prevent with lock ordering, timeout locks, avoid nested locks

Thread pools: ExecutorService.newFixedThreadPool(n) — reuses threads, bounds concurrency, graceful shutdown

📱 Code Tweet — Unit 11 in 280 Characters

@JavaDev_India

Exception = UPI failure caught gracefully 🛡️
Thread = Zerodha orders running concurrently ⚡
synchronized = one thread at a time on shared balance 🔒
try-with-resources = auto-close connections ♻️
ExecutorService = 10L orders, 4 threads 🏊‍♂️
#Java #Fintech #Unit11

Section M

Earning Checkpoint — Are You Job-Ready?

Skill LearnedTool/ConceptPortfolio DeliverableEarning Ready?
Exception HierarchyThrowable → Error/Exception✅ Yes — interview essential
try-catch-finallyAll 5 keywordsLab 1 code✅ Yes — every Java project uses it
Custom Exceptionsextends Exception/RuntimeExceptionInsufficientBalanceException✅ Yes — fintech requirement
try-with-resourcesAutoCloseableFileReaderWithResources✅ Yes — resource management
Assertionsassert + java -eaLab 1 Part 5✅ Yes — debugging skill
Thread CreationThread, Runnable, LambdaLab 2 code✅ Yes — backend development
Thread Lifecycle5 statesLab 2 Part 2✅ Yes — interview essential
Synchronizationsynchronized method/blockBankTransferThreads✅ Yes — concurrent apps
wait/notifyProducer-Consumer patternZerodhaOrderBook✅ Yes — fintech/messaging
Deadlock PreventionLock orderingSafe transfer code✅ Yes — senior interviews
Thread PoolsExecutorServiceZerodhaThreadPool✅ Yes — production systems
Minimum Viable Earning Setup after this unit: A GitHub portfolio with 3 Java projects (exception handling + multithreading + producer-consumer) + LinkedIn profile highlighting "Concurrent Java, Thread Pools, Custom Exception Design" = you can apply for ₹15K–₹50K/month freelance projects or ₹6–12 LPA full-time Java developer positions.

✅ Unit 11 complete. MCQs: 30. Labs 1, 2 covered. Ready for Unit 12!

[QR: Link to EduArtha video tutorial — Java Exceptions, Assertions & Multithreading]