1. Java Core Language Link to heading

1.1 Interface Default & Private Methods Link to heading

Backward Compatibility: The default method feature was introduced in Java 8 to allow developers to safely append new methods to existing interfaces without breaking legacy code or modifying implementing classes.

Private Methods (Java 9): Introduced explicitly to eliminate redundant, duplicated logic inside multiple default methods by encapsulating shared routines locally inside the interface body.

public interface DBLogging {
    String MONGO_DB_NAME = "ABC_Mongo_Datastore";
    String NEO4J_DB_NAME = "ABC_Neo4J_Datastore";
    String CASSANDRA_DB_NAME = "ABC_Cassandra_Datastore";

    default void logInfo(String message) { log(message, "INFO"); }
    default void logWarn(String message) { log(message, "WARN"); }
    default void logError(String message) { log(message, "ERROR"); }
    default void logFatal(String message) { log(message, "FATAL"); }

    private void log(String message, String msgPrefix) {
        // Step 1: Connect to DataStore
        // Step 2: Log Message with Prefix and styles etc.
        // Step 3: Close the DataStore connection
    }
}

1.2 Method Overriding vs. Method Hiding Link to heading

Runtime Polymorphism: Static methods cannot be overridden in Java because method overriding relies entirely on runtime dynamic binding, whereas static methods utilize static compile-time binding.

Method Hiding: If a subclass declares a static method with an identical signature to a static method in its parent class, the subclass version hides the parent version.

class Base {
    public static void staticDisplay() {
        System.out.println("Static method from Base");
    }
    public void print() {
        System.out.println("Non-static method from Base");
    }
}

class Derived extends Base {
    public static void staticDisplay() {
        System.out.println("Static method from Derived");
    }
    @Override
    public void print() {
        System.out.println("Non-static method from Derived");
    }
}

public class Test {
    public static void main(String args[]) {
        Base obj1 = new Derived();

        obj1.staticDisplay(); // Output: Static method from Base
        obj1.print();         // Output: Non-static method from Derived
    }
}

Static Methods (obj1.staticDisplay()): Calls the method from the Base class. Static methods are not polymorphic — they use compile-time (static) binding. Because the reference variable obj1 is declared as type Base, the compiler binds the call to Base.staticDisplay(), ignoring the actual object type at runtime.

Non-Static Methods (obj1.print()): Calls the method from the Derived class. Instance methods use runtime (dynamic) binding. Java looks at the actual object in memory (new Derived()) rather than the reference type.

Quick Tip: Because static methods belong to the class rather than an instance, calling obj1.staticDisplay() is misleading. Write Base.staticDisplay() instead.

1.3 Inner Classes Link to heading

Static Inner Classes Link to heading

Can declare static members and be instantiated without creating an instance of the outer class. They do not hold an implicit reference to an enclosing instance.

public class OuterStatic {
    private static String staticOuterField = "Static Outer Field";
    private String instanceOuterField = "Instance Outer Field";

    public static class StaticNested {
        public static String staticInnerField = "Static Inner Field";

        public void display() {
            System.out.println(staticOuterField);
            // COMPILER ERROR: Cannot access instance members of the outer class directly
            // System.out.println(instanceOuterField);
        }
    }
}

Instantiation: You do not need an instance of OuterStatic to create StaticNested.

OuterStatic.StaticNested nested = new OuterStatic.StaticNested();
nested.display();
System.out.println(OuterStatic.StaticNested.staticInnerField);

Non-Static Inner Classes Link to heading

A non-static inner class is bound to a specific instance of the enclosing outer class. It captures an implicit pointer (Outer.this) to that instance.

public class OuterNonStatic {
    private String instanceOuterField = "Instance Outer Field";

    public class InnerClass {
        // Cannot define static fields unless they are final constants
        public static final int CONSTANT_FIELD = 42;

        // Cannot define static methods
        // public static void staticMethod() {}

        public void display() {
            System.out.println("Accessing: " + instanceOuterField);
        }
    }
}

Instantiation: You must create an instance of OuterNonStatic first.

OuterNonStatic outer = new OuterNonStatic();
OuterNonStatic.InnerClass inner = outer.new InnerClass();
inner.display();

Quick Memory Check:

Feature Static Inner Class Non-Static Inner Class
Instantiation Syntax new Outer.StaticInner() outerInstance.new Inner()
Implicit Outer Reference? No Yes (risk of memory leaks if lifecycles differ)
Static Members Allowed? Yes Only static final constants
Direct Access to Outer Private Instance Fields? No Yes

1.4 Lambda Expressions & Functional Interfaces Link to heading

Core concepts:

  • Lambda: anonymous function — (params) -> expression
  • Functional Interface: an interface with exactly one abstract method
  • Method Reference: ClassName::methodName

Functional Interface Matrix Link to heading

Interface Abstract Method Purpose
Predicate<T> boolean test(T t) Evaluates a condition
Function<T,R> R apply(T t) Transforms input to output
Consumer<T> void accept(T t) Performs side-effect operations
Supplier<T> T get() Lazily generates typed data

Handling Checked Exceptions in Lambdas Link to heading

Lambdas cannot throw checked exceptions directly from functional interface methods that don’t declare them.

// Option 1: Wrap in try-catch inside the lambda
list.forEach(item -> {
    try {
        process(item); // throws IOException
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
});

// Option 2: Create a wrapper utility
@FunctionalInterface
interface ThrowingConsumer<T> {
    void accept(T t) throws Exception;
}

static <T> Consumer<T> wrap(ThrowingConsumer<T> consumer) {
    return t -> {
        try { consumer.accept(t); }
        catch (Exception e) { throw new RuntimeException(e); }
    };
}

list.forEach(wrap(item -> process(item)));

1.5 Exception Architecture Link to heading

Type Description
Exception Conditions a well-written application should catch and recover from (e.g., IOException)
Error Serious fatal problems an application should not try to handle (e.g., OutOfMemoryError, StackOverflowError)
RuntimeException Unchecked exceptions indicating programming errors (e.g., NullPointerException). No throws clause needed.

1.6 Custom Annotations Link to heading

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogExecute {
    String name() default "";
}

Usage:

public class Employee {
    @LogExecute(name = "hello")
    public void print() {
        System.out.println("Hello");
    }
}

Processing at runtime:

Method method = Employee.class.getMethod("print");
if (method.isAnnotationPresent(LogExecute.class)) {
    LogExecute annotation = method.getAnnotation(LogExecute.class);
    System.out.println("Logging for: " + annotation.name());
}

@Retention policies:

  • SOURCE: Discarded after compilation (e.g., @Override)
  • CLASS: In bytecode but not at runtime (default)
  • RUNTIME: Available via reflection at runtime

@Target element types: TYPE, METHOD, FIELD, CONSTRUCTOR, PARAMETER, etc.

1.7 Reflection API Link to heading

Reflection allows inspecting and manipulating classes, methods, and fields at runtime.

Class<?> clazz = Class.forName("com.example.Employee");

// Get all methods
Method[] methods = clazz.getDeclaredMethods();

// Invoke a method dynamically
Method method = clazz.getMethod("getName");
Object instance = clazz.getDeclaredConstructor().newInstance();
Object result = method.invoke(instance);

// Access private field
Field field = clazz.getDeclaredField("salary");
field.setAccessible(true);
field.set(instance, 50000);

Use cases: Frameworks (Spring, Hibernate, Jackson), testing tools, serialization, proxies.

Drawbacks: Performance overhead, breaks encapsulation, bypasses compile-time type checking.

1.8 Serializable vs Externalizable Link to heading

Aspect Serializable Externalizable
Methods Required None (marker interface) writeExternal(), readExternal()
Serialization Control JVM default (all non-transient fields) Full manual control
Performance Slower (reflection-based) Faster (explicit control)
Constructor Called on Deserialization No Yes (no-arg constructor required)
// Serializable (marker interface)
public class Employee implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private transient String password; // excluded from serialization
}

// Externalizable (explicit control)
public class Employee implements Externalizable {
    private String name;
    private int age;

    public Employee() {} // Required!

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(name);
        out.writeInt(age);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        name = (String) in.readObject();
        age = in.readInt();
    }
}

1.9 I/O Streams vs NIO Link to heading

Aspect Classic I/O (java.io) NIO (java.nio)
Model Stream-oriented (byte/char) Buffer & Channel oriented
Blocking Blocking Non-blocking capable
Selectors No Yes (Selector for multiplexing)
Key classes InputStream, FileReader ByteBuffer, FileChannel, Selector
// NIO non-blocking server (simplified)
Selector selector = Selector.open();
ServerSocketChannel server = ServerSocketChannel.open();
server.configureBlocking(false);
server.register(selector, SelectionKey.OP_ACCEPT);

while (true) {
    selector.select(); // blocks until at least one channel is ready
    Set<SelectionKey> keys = selector.selectedKeys();
    // process ready channels...
}

1.10 String Memory & Passwords Link to heading

String Pool: String literals and interned strings are cached in the String Pool, enabling quick reference comparisons via the == operator.

Security & Passwords: Storing passwords as char[] arrays is more secure than using immutable strings. Strings cannot be zeroed out after use — they linger in memory until GC reclaims them. A character array can be explicitly cleared or overwritten immediately after use.


2. Java 11+ Features Link to heading

2.1 Distribution & Ecosystem Link to heading

Support Model: No free long-term support (LTS) updates are provided for production instances using the Oracle JDK 11 platform.

2.2 Modern Core String APIs Link to heading

Includes new convenience string processing abstractions: .lines(), .isBlank(), .strip(), .stripLeading(), .stripTrailing(), and .repeat().

String multilineString = "Baeldung helps \n \n developers \n explore Java.";
List<String> lines = multilineString.lines()
    .filter(line -> !line.isBlank())
    .map(String::strip)
    .collect(Collectors.toList());

// ["Baeldung helps", "developers", "explore Java."]

2.3 File I/O & Native Conversions Link to heading

File Operations: Introduces single-step text operations: Files.readString(Path) and Files.writeString(Path, CharSequence).

Collection Conversions: Simplifies streaming outputs directly into typed arrays using constructor references via .toArray().

List<String> sampleList = Arrays.asList("Java", "Kotlin");
String[] sampleArray = sampleList.toArray(String[]::new);

2.4 Local Variable Syntax for Lambdas Link to heading

Using var inside a lambda parameter list allows direct support for modifiers like @Nonnull or @Nullable.

Rules: If var is declared on a lambda parameter, it must be applied uniformly to all parameters, and parentheses are strictly mandatory.

List<String> sampleList = Arrays.asList("Java", "Kotlin");
String resultString = sampleList.stream()
    .map((@Nonnull var x) -> x.toUpperCase())
    .collect(Collectors.joining(", "));

2.5 HTTP Client Architecture Link to heading

Feature-rich, asynchronous HttpClient replacing older options like Apache HttpClient or Spring’s RestTemplate.

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;

public class HttpClientExample {

    private static final HttpClient httpClient = HttpClient.newBuilder()
            .version(HttpClient.Version.HTTP_2)
            .connectTimeout(Duration.ofSeconds(10))
            .build();

    public static void main(String[] args) {
        String url = "https://jsonplaceholder.typicode.com/posts/1";

        HttpRequest request = HttpRequest.newBuilder()
                .GET()
                .uri(URI.create(url))
                .header("Accept", "application/json")
                .build();

        System.out.println("Sending asynchronous GET request...");

        CompletableFuture<HttpResponse<String>> responseFuture = httpClient.sendAsync(
                request,
                HttpResponse.BodyHandlers.ofString()
        );

        responseFuture.thenAccept(response -> {
            int statusCode = response.statusCode();
            String responseBody = response.body();
            System.out.println("\nStatus Code: " + statusCode);
            System.out.println("Response Body:\n" + responseBody);
        }).exceptionally(ex -> {
            System.err.println("An error occurred: " + ex.getMessage());
            return null;
        });

        responseFuture.join();
    }
}

Best Practices:

  • Reuse the Client: Create a single HttpClient instance and reuse it.
  • Synchronous Alternative: Use .send() instead of .sendAsync() for blocking requests:
    HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
    
  • Body Handlers: HttpResponse.BodyHandlers.ofString() for text, HttpResponse.BodyHandlers.ofFile(Paths.get("file.json")) for file downloads.

3. Generics & Type System Link to heading

3.1 Advantages of Generics Link to heading

Generics provide compile-time type validation, remove the need for explicit manual type casts, and allow developers to build reusable, type-safe algorithms across varied collections.

3.2 Raw Types vs. Parameterized Types Link to heading

Raw Type: A generic class reference declared with its type arguments omitted (e.g., List list = new ArrayList();).

Parameterized Type: Explicitly assigns concrete parameters (e.g., List<String> list = new ArrayList<>();). Raw assignments generate an unchecked conversion warning.

3.3 Generic Bounds Constraints Link to heading

  • Single bounds: <U extends Number>
  • Multiple Bounds Rule: <T extends BaseClass & InterfaceA & InterfaceB>. If a class type forms part of the constraint, it must be listed first.

3.4 Wildcard Variance & PECS Link to heading

Upper Bound Wildcard (? extends T): Safely permits reading elements out of generic collections (Producer).

Lower Bound Wildcard (? super T): Safely permits writing elements into generic collections (Consumer).

PECS — Producer Extends, Consumer Super Link to heading

Context What a “Producer” does What a “Consumer” does
Kafka / Systems Writes data (pushes to a topic) Reads data (pulls from a topic)
PECS / Java Generics Reads data (collection produces items) Writes data (collection consumes items)

So, if you are writing a Kafka producer in Java, the Producer.send() method might internally use ? extends Serializer because it needs to read the configuration out of it.

3.5 Type Erasure Link to heading

At compile time, generic type parameters are erased and replaced with Object (or bounded type). Generic type info is not available at runtime.

List<String> strings = new ArrayList<>();
List<Integer> ints = new ArrayList<>();
// At runtime: both are just List
System.out.println(strings.getClass() == ints.getClass()); // true

Implications:

  • Cannot do new T(), new T[], or instanceof T
  • Cannot have static generic fields
  • Method overloading with only generic type differences doesn’t work after erasure

Bounded type parameters:

<T extends Comparable<T>>   // upper bound
<? super Integer>            // lower bound (wildcard)
<? extends Number>           // upper bound (wildcard)

3.6 Identity Resolution: instanceof vs. getClass() Link to heading

instanceof: Evaluates whether an object reference matches the target class type or any of its downstream subclasses.

getClass() == ...: Executes an exact type identity test, validating class matching without permitting subclass substitution.


4. Collections Framework Link to heading

4.1 ArrayList vs. LinkedList Link to heading

Aspect ArrayList LinkedList
Internal Structure Resizable array Doubly linked list
Random Access O(1) O(n)
Insert/Delete at ends O(n) (amortized O(1) for add at tail) O(1)
Memory Overhead Less (only array) More (node objects)

Choose LinkedList for workflows reliant on dynamic element manipulation. Choose ArrayList for O(1) random access.

4.2 Fail-Fast vs. Fail-Safe Iterators Link to heading

Fail-Fast: Throws ConcurrentModificationException instantly if structural variations happen mid-traversal (e.g., ArrayList, HashMap).

Fail-Safe: Operates on a snapshot/clone of the underlying array, allowing modification mid-traversal (e.g., CopyOnWriteArrayList).

4.3 Synchronizing an ArrayList Link to heading

// Method 1: Collections Wrapper
List<String> namesList = Collections.synchronizedList(new ArrayList<>());
// Note: Manual looping blocks still require explicit synchronized block.

// Method 2: Copy-On-Write
List<String> cowList = new CopyOnWriteArrayList<>();
// Iterators are thread-safe; each modification creates a new array clone.

4.4 Arrays.equals() vs. Arrays.deepEquals() Link to heading

Arrays.equals(): Compares arrays containing flat elements. Does not evaluate nested inner arrays.

Arrays.deepEquals(): Validates arrays recursively for structural equivalences across all nested dimensions.

int[][] a1 = {{10, 20}, {40, 50}};
int[][] a3 = {{10, 20}, {40, 50}};

Arrays.equals(a1, a3);     // false (top-level reference pointers)
Arrays.deepEquals(a1, a3); // true (evaluates all inner dimensions)

4.5 HashMap Concurrency Hazards Link to heading

A standard java.util.HashMap is built for single-threaded use. Multi-threaded usage introduces critical risks:

  • Race Conditions on Write: Multiple threads calling .put() concurrently can overwrite each other’s nodes.
  • Hash-Collision Node Corruptions: Simultaneous updates to a linked-list bucket can corrupt next pointers.
  • Infinite Looping (Pre-Java 8): Head-insertion during resize could create cyclic loops. Java 8’s tail-insertion mitigates this but still leaves the collection vulnerable to data corruption.

4.6 ConcurrentHashMap Internals Link to heading

Java 7: Divided into fixed Segments (extending ReentrantLock). Threads only locked the specific segment they were writing to.

Java 8+: Lock-Free & Node-Level Locking.

  • Reads (.get()): Entirely lock-free, using volatile variable reads.
  • Writes (.put()): Uses CAS (Compare-And-Swap) for empty buckets; synchronized on the first node of the specific bucket for collisions.

4.7 IdentityHashMap vs. WeakHashMap vs. HashMap Link to heading

Map Type Key Equality GC Interaction Use Cases
HashMap .equals() + .hashCode() Strong References General-purpose
IdentityHashMap == reference equality + System.identityHashCode() Strong References Graph transformations, object topology cloning
WeakHashMap .equals() + .hashCode() Weak References (auto-evicted when key has no strong refs) Metadata management, runtime lookups
// IdentityHashMap
String a = new String("key");
String b = new String("key");
Map<String, Integer> map = new IdentityHashMap<>();
map.put(a, 1);
map.put(b, 2); // Different key — a != b (reference comparison)
// map.size() == 2

// WeakHashMap
Map<Object, String> map = new WeakHashMap<>();
Object key = new Object();
map.put(key, "value");
key = null; // No strong reference → eligible for GC
System.gc();
// map may be empty after GC

4.8 Thread-Safe Collections Comparison Link to heading

Collection Thread-Safe? Notes
ArrayList No Use Collections.synchronizedList() or CopyOnWriteArrayList
Vector Yes (legacy) All methods synchronized; poor throughput
HashMap No Use ConcurrentHashMap
ConcurrentHashMap Yes CAS + synchronized bins (Java 8+)
CopyOnWriteArrayList Yes All writes create a new copy; ideal for read-heavy workloads

5. Memory Model, Garbage Collection & ClassLoaders Link to heading

5.1 Java Memory Model (JMM) Link to heading

The JMM defines how the JVM handles memory communication between hardware and application threads, establishing when actions by one thread become visible to another.

Concept Explanation
Visibility A write to a variable may not be immediately visible to another thread unless a happens-before relationship exists
Atomicity An operation is atomic if it completes in a single step
Volatile Guarantees visibility (not atomicity). A write to a volatile variable happens-before every subsequent read
Happens-Before All memory writes before a happens-before action are visible to code that comes after
Synchronization synchronized establishes happens-before between unlock and subsequent lock on the same monitor

Instruction Reordering Link to heading

To optimize performance, both the compiler (JIT) and CPU can change instruction execution order, provided the program output remains identical in single-threaded context. The JMM defines Happens-Before rules to prevent reordering across thread boundaries.

The volatile Modifier Link to heading

  • CPU Cache Coherency: volatile forces all writes to flush to Main Memory and reads to pull from Main Memory.
  • Instruction Reordering Inhibition: Establishes a strict Happens-Before guarantee.
  • Atomicity Limitation: volatile only guarantees visibility, not atomicity. Compound operations like count++ still require locks or AtomicInteger.
private volatile boolean running = true;

// Thread 1 writes:
running = false;

// Thread 2 reads — guaranteed to see 'false' due to volatile
while (running) { /* ... */ }

Happens-Before Relationships Link to heading

  • Monitor Lock Rule: Unlock happens-before every subsequent lock on the same monitor.
  • Volatile Variable Rule: Write happens-before every subsequent read of the same field.
  • Thread Start Rule: Thread.start() happens-before any action in the started thread.
  • Thread Termination Rule: Actions in a thread happen-before .join() or .isAlive() detection.

5.2 ThreadLocal Mechanics Link to heading

ThreadLocal<T> provides variables where each thread holds an isolated copy, eliminating the need for synchronization for thread-specific state.

  • Internal Implementation: Every Thread maintains a ThreadLocalMap with WeakReference keys pointing to ThreadLocal instances.
  • Memory Leak Hazard: ThreadLocalMap uses weak references for keys but strong references for values. If the ThreadLocal key is GC’d, the value remains pinned by the thread’s strong reference path.
  • Mitigation: Always call .remove() inside a finally block when the thread finishes its work.
public class UserRequestContext {
    private static final ThreadLocal<String> userSecurityToken = new ThreadLocal<>();

    public static void setToken(String token) {
        userSecurityToken.set(token);
    }

    public static String getToken() {
        return userSecurityToken.get();
    }

    public static void clear() {
        userSecurityToken.remove();
    }
}

5.3 Garbage Collection: Generational Architecture Link to heading

The JVM divides the heap based on the Weak Generational Hypothesis (most objects die young).

  • Young Generation: New object allocation zone.
    • Eden Space: Where objects are initially created.
    • Survivor Spaces (S0 / S1): Retain objects surviving Minor GC cycles.
  • Old (Tenured) Generation: Long-lived objects promoted after surviving GC cycles (-XX:MaxTenuringThreshold).
  • Metaspace (Java 8+): Class metadata (replaced PermGen). Scales dynamically using native memory.

GC Algorithms Link to heading

GC Description
Serial GC (-XX:+UseSerialGC) Single-threaded; stop-the-world; suitable for small client apps
Parallel GC (-XX:+UseParallelGC) Multi-threaded; default in Java 8 for high throughput
G1 GC (-XX:+UseG1GC) Region-based; balances throughput and pause time; default Java 9+
ZGC / Shenandoah Low-latency; sub-millisecond pauses; concurrent

Key terms:

  • Stop-The-World (STW): All application threads paused during GC.
  • Minor GC: Collects Young Generation.
  • Major/Full GC: Collects Old Generation (and optionally Young).
  • GC Roots: Starting points for reachability — local variables, static fields, JNI references.

5.4 ClassLoader Mechanics Link to heading

Core Principles Link to heading

  1. Delegation: Loading queries are delegated up the chain before files are scanned.
  2. Visibility: Child ClassLoaders can view parent-loaded classes, but not vice versa.
  3. Uniqueness: A class resolved by a parent loader will not be re-loaded by a child.

Three Built-in Class Loaders Link to heading

Class Loader Loads
Bootstrap Core Java classes (java.lang.*) from JDK modules
Extension (Platform) JDK extension modules
Application (System) Classes on the application’s classpath

Parent Delegation Model Link to heading

Bootstrap CL
    ↑
Extension/Platform CL
    ↑
Application/System CL
    ↑
Custom Class Loader

Class Initialization Rules Link to heading

A class T triggers its static blocks and initialization only before:

  • An instance of T is instantiated.
  • A static method in T is invoked.
  • A mutable static field in T is accessed.

Exclusion: Accessing a compile-time constant (public static final int) does not trigger initialization. Initializing an outer class does not initialize its static inner classes.

Custom Class Loader Use Cases Link to heading

  • Hot-reloading (application servers)
  • Loading classes from non-standard sources (database, network)
  • Isolation (OSGi, plugin systems)

6. Concurrency & Threading Link to heading

6.1 Thread Alternation (Even / Odd) Link to heading

Option A: ReentrantLock & Flag State Link to heading

package com.example.demo;
import lombok.AllArgsConstructor;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class DemoApplication {
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Enable enable = new Enable(true);

        OddEvenThread odd = new OddEvenThread(1, lock, true, enable);
        OddEvenThread even = new OddEvenThread(2, lock, false, enable);

        new Thread(odd).start();
        new Thread(even).start();
    }
}

@AllArgsConstructor class Enable { boolean enable; }

@AllArgsConstructor
class OddEvenThread implements Runnable {
    private int start;
    private Lock lock;
    boolean enableVal;
    Enable enable;

    @Override
    public void run() {
        while (start < 100) {
            lock.lock();
            if (enableVal == enable.enable) {
                System.out.println(start);
                start += 2;
                enable.enable = !enable.enable;
            }
            lock.unlock();
        }
    }
}

Option B: Semaphore Switching Link to heading

public class Printer {
    Semaphore odd = new Semaphore(1);
    Semaphore even = new Semaphore(0);

    public void printEven(int num) throws InterruptedException {
        even.acquire();
        System.out.println(num);
        odd.release();
    }

    public void printOdd(int num) throws InterruptedException {
        odd.acquire();
        System.out.println(num);
        even.release();
    }
}

Option C: Object Monitors (wait / notify) Link to heading

class Printer {
    private boolean isOddTurn;

    public Printer(boolean startWithOdd) {
        this.isOddTurn = startWithOdd;
    }

    public synchronized void printEven(int num) throws InterruptedException {
        while (isOddTurn) { wait(); }
        System.out.println(Thread.currentThread().getName() + ": " + num);
        isOddTurn = true;
        notify();
    }

    public synchronized void printOdd(int num) throws InterruptedException {
        while (!isOddTurn) { wait(); }
        System.out.println(Thread.currentThread().getName() + ": " + num);
        isOddTurn = false;
        notify();
    }
}

public class Main {
    public static void main(String[] args) {
        Printer printer = new Printer(true);
        int maxNumber = 10;

        Thread oddThread = new Thread(() -> {
            for (int i = 1; i <= maxNumber; i += 2) {
                try { printer.printOdd(i); }
                catch (InterruptedException e) { Thread.currentThread().interrupt(); }
            }
        }, "Odd-Thread");

        Thread evenThread = new Thread(() -> {
            for (int i = 2; i <= maxNumber; i += 2) {
                try { printer.printEven(i); }
                catch (InterruptedException e) { Thread.currentThread().interrupt(); }
            }
        }, "Even-Thread");

        oddThread.start();
        evenThread.start();
    }
}

6.2 ReentrantLock Concurrency Mechanics Link to heading

Semantics: Explicit mutual exclusion lock with identical semantics to synchronized but with enhanced control.

Reentrancy: A thread that holds the lock can re-enter it instantly. Trackable via .isHeldByCurrentThread() and .getHoldCount().

Fairness Policy: Optional parameter. When true, the JVM allocates the lock to the longest-waiting thread. Eliminates starvation but reduces throughput.

Best Practice: Always follow .lock() with try-finally:

class X {
    private final ReentrantLock lock = new ReentrantLock();
    public void m() {
        lock.lock();
        try {
            // Method body execution
        } finally {
            lock.unlock();
        }
    }
}

6.3 Producer-Consumer Link to heading

Using wait() / notify() Link to heading

public class SharedBuffer {
    private final Queue<Integer> queue = new LinkedList<>();
    private final int CAPACITY = 5;

    public synchronized void produce(int item) throws InterruptedException {
        while (queue.size() == CAPACITY) { wait(); }
        queue.add(item);
        System.out.println("Produced: " + item);
        notifyAll();
    }

    public synchronized int consume() throws InterruptedException {
        while (queue.isEmpty()) { wait(); }
        int item = queue.poll();
        System.out.println("Consumed: " + item);
        notifyAll();
        return item;
    }
}

Key Rules:

  • wait(), notify(), notifyAll() must be called within a synchronized block.
  • Always use while (not if) to guard against spurious wakeups.
  • Prefer notifyAll() over notify() unless certain only one thread needs waking.

Using BlockingQueue Link to heading

BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(5);
// Producer:
queue.put(item);    // blocks if full
// Consumer:
int item = queue.take(); // blocks if empty

6.4 Custom Unbounded Blocking Queue Link to heading

import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class CustomBlockingQueue<E> {
    private final Queue<E> queue = new LinkedList<>();
    private final Lock lock = new ReentrantLock();
    private final Condition isNotEmpty = lock.newCondition();

    public void enqueue(E element) {
        lock.lock();
        try {
            queue.add(element);
            isNotEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    public E dequeue() throws InterruptedException {
        lock.lock();
        try {
            while (queue.isEmpty()) { isNotEmpty.await(); }
            return queue.poll();
        } finally {
            lock.unlock();
        }
    }
}

6.5 Custom Bounded Blocking Queue Link to heading

import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class BoundedBlockingQueue<E> {
    private final Queue<E> queue = new LinkedList<>();
    private final int capacity;
    private final Lock lock = new ReentrantLock();
    private final Condition isNotEmpty = lock.newCondition();
    private final Condition isNotFull = lock.newCondition();

    public BoundedBlockingQueue(int capacity) {
        this.capacity = capacity;
    }

    public void enqueue(E element) throws InterruptedException {
        lock.lock();
        try {
            while (queue.size() == capacity) { isNotFull.await(); }
            queue.add(element);
            isNotEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    public E dequeue() throws InterruptedException {
        lock.lock();
        try {
            while (queue.isEmpty()) { isNotEmpty.await(); }
            E element = queue.poll();
            isNotFull.signal();
            return element;
        } finally {
            lock.unlock();
        }
    }
}

6.6 Custom CountDownLatch Link to heading

public class CustomCountDownLatch {
    private int count;

    public CustomCountDownLatch(int count) {
        if (count < 0) {
            throw new IllegalArgumentException("Count cannot be negative.");
        }
        this.count = count;
    }

    public synchronized void await() throws InterruptedException {
        while (count > 0) { wait(); }
    }

    public synchronized void countDown() {
        if (count > 0) {
            count--;
            if (count == 0) { notifyAll(); }
        }
    }

    public synchronized int getCount() {
        return count;
    }
}

6.7 Custom ThreadPool Link to heading

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

public class CustomThreadPool {
    private final BoundedBlockingQueue<Runnable> taskQueue;
    private final List<WorkerThread> workers;
    private boolean isShutdown = false;

    public CustomThreadPool(int threadCount, int maxTasks) {
        this.taskQueue = new BoundedBlockingQueue<>(maxTasks);
        this.workers = new ArrayList<>(threadCount);

        for (int i = 0; i < threadCount; i++) {
            WorkerThread worker = new WorkerThread();
            workers.add(worker);
            worker.start();
        }
    }

    public void execute(Runnable task) throws InterruptedException {
        if (this.isShutdown) {
            throw new IllegalStateException("ThreadPool has been shut down.");
        }
        taskQueue.enqueue(task);
    }

    public void shutdown() {
        this.isShutdown = true;
        for (WorkerThread worker : workers) {
            worker.interrupt();
        }
    }

    private class WorkerThread extends Thread {
        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    Runnable task = taskQueue.dequeue();
                    task.run();
                } catch (InterruptedException e) {
                    break;
                }
            }
        }
    }
}

6.8 Custom Thread-Safe LRU Cache Link to heading

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

public class LRUCache {
    private final int capacity;
    private final Map<Integer, Node> map;
    private final Node head;
    private final Node tail;

    private static class Node {
        int key;
        int value;
        Node prev;
        Node next;
        Node(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }

    public LRUCache(int capacity) {
        this.capacity = capacity;
        this.map = new HashMap<>();
        this.head = new Node(0, 0);
        this.tail = new Node(0, 0);
        head.next = tail;
        tail.prev = head;
    }

    public synchronized int get(int key) {
        Node node = map.get(key);
        if (node == null) return -1;
        moveToHead(node);
        return node.value;
    }

    public synchronized void put(int key, int value) {
        Node node = map.get(key);
        if (node != null) {
            node.value = value;
            moveToHead(node);
        } else {
            if (map.size() >= capacity) {
                map.remove(tail.prev.key);
                removeNode(tail.prev);
            }
            Node newNode = new Node(key, value);
            map.put(key, newNode);
            addNode(newNode);
        }
    }

    private void addNode(Node node) {
        node.next = head.next;
        node.next.prev = node;
        node.prev = head;
        head.next = node;
    }

    private void removeNode(Node node) {
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }

    private void moveToHead(Node node) {
        removeNode(node);
        addNode(node);
    }
}

6.9 CompletableFuture Link to heading

Enables non-blocking, asynchronous programming with chaining.

// Basic async computation
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    return fetchDataFromDB(); // runs in ForkJoinPool thread
});

// Chain transformations
future.thenApply(data -> transform(data))         // sync, same thread
      .thenApplyAsync(data -> transform(data))    // async, new thread
      .thenAccept(result -> System.out.println(result));

// Combine multiple futures
CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<String> combined = f1.thenCombine(f2, (a, b) -> a + " " + b);

// Wait for all / any
CompletableFuture<Void> all = CompletableFuture.allOf(f1, f2);
all.join();
CompletableFuture<Object> first = CompletableFuture.anyOf(f1, f2);

// Exception handling
future.exceptionally(ex -> {
    log.error("Error: ", ex);
    return "fallback";
});

future.handle((result, ex) -> {
    if (ex != null) return "error";
    return result;
});
Method Description
thenApply Transform result (like map)
thenCompose Chain async steps (like flatMap)
thenCombine Combine two independent futures
allOf Wait for all futures to complete
anyOf Complete when the first future completes
exceptionally Handle exceptions with fallback
handle Handle both result and exception

6.10 Streams & Fork-Join Framework Link to heading

Sequential vs. Parallel Streams Link to heading

Aspect Sequential Parallel
Execution Single thread Multiple threads (ForkJoinPool.commonPool())
Order Preserved Not guaranteed (use forEachOrdered if needed)
Use case Small data, ordered ops CPU-bound, large independent data
// Sequential
list.stream().filter(n -> n > 5).map(n -> n * 2).collect(toList());

// Parallel
list.parallelStream().filter(n -> n > 5).map(n -> n * 2).collect(toList());

map() vs. flatMap() Link to heading

map(): One-to-one mapping — each input maps to a single transformed value.

flatMap(): One-to-many mapping — converts each element into a stream of child values, then flattens into a single combined stream.

List<List<String>> dynamicNestedList = Arrays.asList(
    Arrays.asList("A", "B"),
    Arrays.asList("C", "D")
);

// flatMap() flattens internal structures: Stream<String> -> ["A", "B", "C", "D"]
List<String> flatResult = dynamicNestedList.stream()
    .flatMap(Collection::stream)
    .collect(Collectors.toList());

Fork-Join Framework Link to heading

Uses work-stealing: idle threads steal tasks from busy threads’ deques. Based on ForkJoinTask (RecursiveAction — no return, RecursiveTask<V> — returns value).

class SumTask extends RecursiveTask<Long> {
    private final int[] arr;
    private final int start, end;
    static final int THRESHOLD = 1000;

    protected Long compute() {
        if (end - start <= THRESHOLD) {
            long sum = 0;
            for (int i = start; i < end; i++) sum += arr[i];
            return sum;
        }
        int mid = (start + end) / 2;
        SumTask left = new SumTask(arr, start, mid);
        SumTask right = new SumTask(arr, mid, end);
        left.fork();
        return right.compute() + left.join();
    }
}

When NOT to use parallel streams:

  • Small data sets (overhead > gain)
  • Operations with side effects or shared mutable state
  • Ordered pipelines (findFirst, limit)
  • I/O-bound operations

7. Design Principles Link to heading

7.1 SOLID Link to heading

S — Single Responsibility Principle (SRP) Link to heading

A class should have only one reason to change.

// Violation: One class handles both user data AND email sending
class User {
    void saveToDatabase() { ... }
    void sendWelcomeEmail() { ... }
}

// Fix: Separate concerns
class User { void saveToDatabase() { ... } }
class EmailService { void sendWelcomeEmail(User user) { ... } }

O — Open/Closed Principle (OCP) Link to heading

Open for extension, closed for modification.

interface Shape { double area(); }
class Circle implements Shape { ... }
class Rectangle implements Shape { ... }
// Adding Triangle does NOT modify existing code
class Triangle implements Shape { ... }

L — Liskov Substitution Principle (LSP) Link to heading

Subtypes must be substitutable for their base types without altering program correctness.

// Violation: Square extends Rectangle but breaks the contract
class Rectangle { void setWidth(int w); void setHeight(int h); }
class Square extends Rectangle {
    // Must set both — breaks caller assumptions
}

I — Interface Segregation Principle (ISP) Link to heading

No client should be forced to implement methods it does not use.

// Violation
interface Animal { void eat(); void fly(); void swim(); }

// Fix
interface Eatable { void eat(); }
interface Flyable { void fly(); }
interface Swimmable { void swim(); }
class Duck implements Eatable, Flyable, Swimmable { ... }
class Dog implements Eatable, Swimmable { ... }

D — Dependency Inversion Principle (DIP) Link to heading

High-level modules should not depend on low-level modules. Both should depend on abstractions.

// Violation
class OrderService {
    private MySQLDatabase db = new MySQLDatabase();
}

// Fix
interface Database { void save(Order o); }
class OrderService {
    private final Database db;
    OrderService(Database db) { this.db = db; }
}

7.2 DRY — Don’t Repeat Yourself Link to heading

Every piece of knowledge must have a single, unambiguous, authoritative representation.

// Violation: tax calc repeated inline
double priceWithTax1 = price * 1.18;
double priceWithTax2 = total * 1.18;

// Fix
double applyTax(double amount) { return amount * 1.18; }

7.3 KISS — Keep It Simple, Stupid Link to heading

Simplicity should be the key design goal.

// Over-engineered
AbstractVisitorFactoryStrategyBuilder.getInstance()
    .withStrategy(new DefaultStrategy())
    .build()
    .process(data);

// KISS
process(data);

7.4 Immutable Class Generation Link to heading

An immutable object’s internal state remains locked post-instantiation.

Rules for Total Immutability:

  1. Mark the class as final to prevent subclassing.
  2. Declare all fields as private final.
  3. Provide no setter methods.
  4. Deep Defensive Copying: For mutable object types (e.g., Date, Collection), clone during initialization and in getters.
import java.util.Date;

public final class ImmutableEmployee {
    private final String name;
    private final int id;
    private final Date joiningDate;

    public ImmutableEmployee(String name, int id, Date joiningDate) {
        this.name = name;
        this.id = id;
        this.joiningDate = new Date(joiningDate.getTime());
    }

    public String getName() { return name; }
    public int getId() { return id; }

    public Date getJoiningDate() {
        return new Date(joiningDate.getTime());
    }
}

8. Spring Core Link to heading

8.1 IoC & Dependency Injection Link to heading

IoC Container: The Spring container creates, configures, and manages beans.

Dependency Injection types:

// 1. Constructor Injection (preferred — immutable, testable)
@Service
public class OrderService {
    private final PaymentService paymentService;

    @Autowired // optional in modern Spring if single constructor
    public OrderService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }
}

// 2. Setter Injection (optional dependencies)
@Autowired
public void setEmailService(EmailService emailService) { ... }

// 3. Field Injection (discouraged — hard to test)
@Autowired
private PaymentService paymentService;

8.2 Bean Scopes & Lifecycle Link to heading

Bean Scopes:

Scope Description
singleton (Default) One instance per Spring container
prototype New instance every time requested
request One per HTTP request (web-aware)
session One per HTTP session (web-aware)
application One per ServletContext

Bean Lifecycle:

  1. Instantiate
  2. Populate properties (DI)
  3. BeanNameAware.setBeanName()
  4. BeanFactoryAware.setBeanFactory()
  5. ApplicationContextAware.setApplicationContext()
  6. @PostConstruct
  7. InitializingBean.afterPropertiesSet()
  8. Bean is ready for use
  9. @PreDestroy / DisposableBean.destroy() (on shutdown)

Component Scanning: @SpringBootApplication includes @ComponentScan for the current package. Spring auto-detects @Component, @Service, @Repository, @Controller.

8.3 Aspect-Oriented Programming (AOP) Link to heading

Spring AOP decouples cross-cutting concerns (auditing, logging, security) from business logic using runtime proxies.

Key Terms:

Term Definition
Aspect The module encapsulating the cross-cutting concern
Join Point A point in program execution (method execution in Spring AOP)
Pointcut Expression that selects join points
Advice Action taken at a join point
Weaving Linking aspects with application objects

Advice Types:

Type Description
@Before Executes prior to the target method invocation
@After Executes following method termination (regardless of outcome)
@AfterReturning Executes only after successful completion
@AfterThrowing Triggers exclusively if the method throws an exception
@Around Wraps the target method; can modify parameters, bypass execution, or alter return value
@Aspect
@Component
public class LoggingAspect {

    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore(JoinPoint jp) {
        System.out.println("Before: " + jp.getSignature().getName());
    }

    @After("execution(* com.example.service.*.*(..))")
    public void logAfter(JoinPoint jp) { ... }

    @AfterReturning(pointcut = "execution(* com.example.*.*(..))", returning = "result")
    public void logReturn(Object result) { ... }

    @AfterThrowing(pointcut = "execution(* com.example.*.*(..))", throwing = "ex")
    public void logException(Exception ex) { ... }

    @Around("execution(* com.example.service.*.*(..))")
    public Object logAround(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = pjp.proceed();
        long elapsed = System.currentTimeMillis() - start;
        System.out.println("Execution time: " + elapsed + "ms");
        return result;
    }
}

8.4 JDK Dynamic Proxies vs. CGLIB Link to heading

Feature JDK Dynamic Proxies CGLIB Proxies
Structural Requirement Target class must implement at least one interface Can proxy concrete classes via subclassing
Generation Strategy java.lang.reflect.Proxy (built into JDK) Bytecode manipulation to generate subclass
Method Constraints Only proxies interface methods Cannot proxy final or private methods
Spring Boot Default Spring Boot 1.x (for interface beans) Spring Boot 2.x+ for all beans

The Self-Invocation Trap Link to heading

A common pitfall: when method A() calls method B() via this.B() within the same class, the call bypasses the proxy wrapper, ignoring any AOP/transactional configuration on B().

Solution:

  1. Extract B() into a separate Spring service component.
  2. Inject the proxy via @Autowired or lazy self-injection.
@Service
public class OrderService {

    @Transactional
    public void checkoutOrder(Order order) {
        saveOrderDetails(order);

        // SELF-INVOCATION TRAP: Bypasses the proxy wrapper!
        // REQUIRES_NEW on chargePayment() will be ignored.
        chargePayment(order);
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void chargePayment(Order order) {
        // Payment logic
    }
}

Custom JDK Dynamic Proxy Implementation Link to heading

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

interface ProcessingService {
    void executeBusinessLogic() throws InterruptedException;
}

class ProcessingServiceImpl implements ProcessingService {
    @Override
    public void executeBusinessLogic() throws InterruptedException {
        Thread.sleep(250);
    }
}

class ProfilingInvocationHandler implements InvocationHandler {
    private final Object targetInstance;

    public ProfilingInvocationHandler(Object targetInstance) {
        this.targetInstance = targetInstance;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long startTime = System.nanoTime();
        Object result = method.invoke(targetInstance, args);
        long endTime = System.nanoTime();
        System.out.println("Execution of " + method.getName() + " took: " + (endTime - startTime) + " ns");
        return result;
    }
}

public class ProxyDemo {
    public static void main(String[] args) throws InterruptedException {
        ProcessingService realService = new ProcessingServiceImpl();

        ProcessingService proxyInstance = (ProcessingService) Proxy.newProxyInstance(
            ProcessingService.class.getClassLoader(),
            new Class<?>[]{ProcessingService.class},
            new ProfilingInvocationHandler(realService)
        );

        proxyInstance.executeBusinessLogic();
    }
}

9. Spring Web Architecture Link to heading

9.1 MVC Request Flow Link to heading

The DispatcherServlet functions as the centralized Front Controller.

Client HTTP Request
        ↓
DispatcherServlet (Front Controller)
        ↓
HandlerMapping → finds the correct @Controller method
        ↓
HandlerInterceptor.preHandle()
        ↓
@Controller method executes (calls @Service → @Repository)
        ↓
HandlerInterceptor.postHandle()
        ↓
ViewResolver (or @ResponseBody → HttpMessageConverter → JSON)
        ↓
HandlerInterceptor.afterCompletion()
        ↓
HTTP Response to Client

Detailed Step-by-Step:

  1. Inbound Request Interception: Client sends HTTP request to DispatcherServlet.
  2. Handler Mapping Selection: DispatcherServlet consults HandlerMapping to identify the controller matching the URL.
  3. Interceptor Pre-Filtering: Request passes through HandlerInterceptor.preHandle(). If any returns false, execution terminates.
  4. Adapter Execution: HandlerAdapter decodes routing variables and triggers the controller method.
  5. Business Logic: Controller executes services and returns data or ModelAndView.
  6. View Resolution: ViewResolver converts view names to physical pages; HttpMessageConverter serializes to JSON for APIs.
  7. Post-Processing: Request moves back through interceptor pipeline: postHandle()afterCompletion().
@RestController
@RequestMapping("/employees")
public class EmployeeController {

    @Autowired
    private EmployeeService service;

    @GetMapping
    public List<Employee> getAllEmployees() {
        return service.findAll();
    }

    @GetMapping("/{id}")
    public ResponseEntity<Employee> getEmployeeById(@PathVariable Long id) {
        return service.findById(id)
            .map(ResponseEntity::ok)
            .orElse(ResponseEntity.notFound().build());
    }
}

9.2 Filters vs. Interceptors vs. AOP Link to heading

Feature Servlet Filters Spring Handler Interceptors AOP
System Layer Servlet Container (Tomcat) Spring MVC framework JVM proxy method execution
Context Access Via DelegatingFilterProxy or no direct access Full access to handler Full access via JoinPoint
Execution Trigger Before DispatcherServlet After front controller, before controller Around wrapped methods
Typical Use Cases Security filtering, compression, encoding Session auth, token validation, locale Logging, auditing, profiling, transactions

9.3 HandlerInterceptor Configuration Link to heading

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

public class SecurityInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String securityToken = request.getHeader("X-Security-Token");
        if (securityToken == null || !securityToken.equals("VALID_SYSTEM_TOKEN")) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return false;
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        // Post-processing
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // Cleanup
    }
}

Interceptor registration:

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LocaleChangeInterceptor());
        registry.addInterceptor(new ThemeChangeInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/admin/**");
        registry.addInterceptor(new SecurityInterceptor())
                .addPathPatterns("/secure/**");
    }
}

9.4 Exception Management Link to heading

Localized (@ExceptionHandler): Defined inside a single controller. Catches exceptions from that controller only.

Global (@ControllerAdvice): Centralized global exception hub across all controllers.

@ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(value = { IllegalArgumentException.class, IllegalStateException.class })
    protected ResponseEntity<Object> handleConflict(RuntimeException ex, WebRequest request) {
        String bodyOfResponse = "Invalid request parameters.";
        return handleExceptionInternal(ex, bodyOfResponse, new HttpHeaders(), HttpStatus.CONFLICT, request);
    }
}

Declarative (@ResponseStatus): Placed on custom exception classes to auto-map to HTTP status codes.

@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "Resource does not exist.")
public class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException(String message) {
        super(message);
    }
}

9.5 Spring Security Link to heading

Security Filter Chain Flow Link to heading

HTTP Request
    ↓
SecurityContextHolderFilter (load SecurityContext)
    ↓
UsernamePasswordAuthenticationFilter (for form login)
    ↓
AuthenticationManager (ProviderManager)
    ↓
AuthenticationProvider (e.g., DaoAuthenticationProvider)
    ↓
UserDetailsService.loadUserByUsername()
    ↓
PasswordEncoder.matches()
    ↓
SecurityContext populated with Authentication object
    ↓
Controller

JWT Authentication Link to heading

// 1. Login → generate JWT
SecretKey key = Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8));
String token = Jwts.builder()
    .subject(username)
    .expiration(new Date(System.currentTimeMillis() + 86400000))
    .signWith(key)
    .compact();

// 2. Validate JWT in filter
Claims claims = Jwts.parser()
    .verifyWith(key)
    .build()
    .parseSignedClaims(token)
    .getPayload();
String username = claims.getSubject();

JWT Structure: Header.Payload.Signature (Base64URL encoded)

// Header
{ "alg": "HS256", "typ": "JWT" }

// Payload
{
  "sub": "user123",
  "name": "John",
  "roles": ["ADMIN"],
  "iat": 1720000000,
  "exp": 1720086400
}

OAuth2 Flows Link to heading

Flow Use Case
Client Credentials Machine-to-machine (no user). Client authenticates with client_id + client_secret.
Authorization Code User-facing web apps. User logs in on auth server; code exchanged for token.
Auth Code + PKCE Mobile/SPA apps. Code verifier/challenge prevents code interception.

PKCE Flow:

1. Client generates random code_verifier
2. Client computes code_challenge = SHA256(code_verifier)
3. Client sends auth request with code_challenge
4. User authenticates → auth server returns code
5. Client exchanges code + code_verifier for token
6. Auth server verifies SHA256(code_verifier) == code_challenge

10. Data Access & Transactions Link to heading

10.1 Transaction Propagation Behaviors Link to heading

Behavior Description
REQUIRED (Default) Supports current transaction; creates new one if none exists
REQUIRES_NEW Suspends current transaction; creates independent one. Inner rollback doesn’t affect outer.
NESTED Uses database Savepoint. Inner failure rolls back to savepoint without affecting outer.
MANDATORY Supports current transaction; throws if none exists
NEVER Executes non-transactionally; throws if transaction exists

10.2 Transaction Isolation Levels Link to heading

Problems that isolation levels prevent:

Problem Description
Dirty Read Read uncommitted data from another transaction
Non-Repeatable Read Same row yields different values (another TX committed between reads)
Phantom Read Re-running a query returns different rows (another TX inserted/deleted)
Isolation Level Dirty Read Non-Repeatable Read Phantom Read
READ UNCOMMITTED Possible Possible Possible
READ COMMITTED Prevented Possible Possible
REPEATABLE READ Prevented Prevented Possible
SERIALIZABLE Prevented Prevented Prevented
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void performOperation() { ... }

10.3 ACID Properties Link to heading

Property Description
Atomicity All operations succeed or all fail (no partial updates)
Consistency DB transitions from one valid state to another; constraints always satisfied
Isolation Concurrent transactions do not interfere with each other
Durability Committed transactions survive system failures

10.4 Spring Data JPA Link to heading

N+1 Query Problem Link to heading

// N+1: For each Order, a separate query fetches items
List<Order> orders = orderRepository.findAll(); // 1 query
orders.forEach(o -> o.getItems().size()); // N queries

Fix 1: JOIN FETCH

@Query("SELECT o FROM Order o JOIN FETCH o.items")
List<Order> findAllWithItems();

Fix 2: @EntityGraph

@EntityGraph(attributePaths = {"items"})
List<Order> findAll();

Fix 3: @BatchSize

@BatchSize(size = 20)
@OneToMany(fetch = FetchType.LAZY)
List<OrderItem> items;

Lazy vs. Eager Fetching Link to heading

Lazy Eager
When loaded On first access When parent entity is loaded
Default for @OneToMany Lazy
Default for @ManyToOne Eager
Risk LazyInitializationException outside session N+1 if not needed
@OneToMany(fetch = FetchType.LAZY)
@ManyToOne(fetch = FetchType.EAGER)

// LazyInitializationException fix: ensure access within transaction
@Transactional
public OrderDTO getOrder(Long id) {
    Order order = orderRepo.findById(id).get();
    order.getItems().size(); // safe — still in transaction
    return mapper.toDTO(order);
}

Native Queries Link to heading

@Query(value = "SELECT * FROM employees WHERE department = :dept", nativeQuery = true)
List<Employee> findByDepartment(@Param("dept") String department);

10.5 Database Indexing Link to heading

An index is a data structure (typically B-tree) for fast row lookup without full table scans.

Type Description
Clustered Index Rows stored in index order. One per table (usually PK).
Non-Clustered Index Separate structure pointing to rows. Multiple per table.
Unique Index Ensures all values are unique
Composite Index Index on multiple columns. Order matters — leftmost prefix rule.
Covering Index All query columns in the index — no row lookup needed.

When indexes help: WHERE, JOIN ON, ORDER BY, GROUP BY. When indexes hurt: Frequent INSERT/UPDATE/DELETE — index maintenance overhead.


11. REST & Web Services Link to heading

11.1 REST Principles Link to heading

REST is based on 6 constraints:

Constraint Meaning
Client-Server UI and data storage are separated
Stateless Each request contains all necessary information; no server session
Cacheable Responses must define whether they are cacheable
Uniform Interface Resources identified by URIs; standard HTTP methods
Layered System Client cannot tell if it’s connected directly to the server
Code on Demand (Optional) Server can send executable code

11.2 HTTP Methods & Status Codes Link to heading

Method Purpose Idempotent
GET Read resource Yes
POST Create resource No
PUT Replace resource Yes
PATCH Partial update No
DELETE Delete resource Yes
Status Code Meaning
200 OK Successful GET/PUT
201 Created Successful POST
204 No Content DELETE successful
400 Bad Request Invalid input
401 Unauthorized Not authenticated
403 Forbidden Authenticated but not authorized
404 Not Found Resource doesn’t exist
500 Internal Server Error Server-side error

11.3 REST API with Spring Boot Link to heading

@RestController
@RequestMapping("/api/v1/employees")
public class EmployeeController {

    @GetMapping
    public List<Employee> getAll() { return service.findAll(); }

    @GetMapping("/{id}")
    public ResponseEntity<Employee> getById(@PathVariable Long id) {
        return ResponseEntity.ok(service.findById(id));
    }

    @PostMapping
    public ResponseEntity<Employee> create(@RequestBody @Valid Employee emp) {
        Employee saved = service.save(emp);
        URI location = URI.create("/api/v1/employees/" + saved.getId());
        return ResponseEntity.created(location).body(saved);
    }

    @PutMapping("/{id}")
    public Employee update(@PathVariable Long id, @RequestBody Employee emp) {
        return service.update(id, emp);
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> delete(@PathVariable Long id) {
        service.delete(id);
        return ResponseEntity.noContent().build();
    }
}

Path Variables vs. Query Parameters:

  • @PathVariable: Part of URI path — /employees/42
  • @RequestParam: Appended after ?/employees?dept=IT&page=0

11.4 Swagger / OpenAPI Link to heading

@Configuration
public class SwaggerConfig {
    @Bean
    public OpenAPI customOpenAPI() {
        return new OpenAPI()
            .info(new Info().title("Employee API").version("1.0"));
    }
}
@Operation(summary = "Get employee by ID")
@ApiResponse(responseCode = "200", description = "Employee found")
@ApiResponse(responseCode = "404", description = "Not found")
@GetMapping("/{id}")
public Employee getById(@PathVariable Long id) { ... }

Access at: http://localhost:8080/swagger-ui/index.html

11.5 REST vs. SOAP & WSDL Link to heading

Aspect REST SOAP
Protocol HTTP HTTP, SMTP, TCP
Format JSON, XML, any XML only
Contract OpenAPI (optional) WSDL (mandatory)
State Stateless Can be stateful
Performance Lightweight, faster Heavier (XML envelope)
Use case Web/mobile APIs Enterprise, banking, legacy

WSDL (Web Services Description Language): The contract file for SOAP services defining Types, Messages, PortType, Binding, and Service.


12. Microservices Architecture Link to heading

12.1 Strangler Fig Pattern Link to heading

Gradually replaces a monolith by routing specific functionality to new microservices.

                     ┌─────────────────────┐
Client Request ──→   │    API Gateway /     │
                     │    Proxy / Router    │
                     └──────┬──────────────┘
                            │
             ┌──────────────┼──────────────┐
             ↓                             ↓
    ┌─────────────────┐        ┌──────────────────────┐
    │  Legacy Monolith │        │  New Microservice(s)  │
    └─────────────────┘        └──────────────────────┘

Steps:

  1. Identify a bounded context to extract.
  2. Build the new microservice.
  3. Route new traffic to the microservice via a proxy.
  4. Retire that functionality from the monolith.
  5. Repeat until the monolith is gone.

12.2 API Gateway Link to heading

A single entry point for all client requests handling:

  • Routing, Authentication/Authorization, Rate Limiting, Load Balancing
  • SSL Termination, Request/Response transformation

Popular implementations: Spring Cloud Gateway, Netflix Zuul, Kong, AWS API Gateway.

# Spring Cloud Gateway example
spring:
  cloud:
    gateway:
      routes:
        - id: employee-service
          uri: lb://EMPLOYEE-SERVICE
          predicates:
            - Path=/api/employees/**
          filters:
            - StripPrefix=1

12.3 Service Registry — Netflix Eureka Link to heading

Microservices register themselves and discover other services dynamically.

// Eureka Server
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApp { public static void main(String[] args) { SpringApplication.run(...); } }

// Eureka Client (in each microservice)
@SpringBootApplication
@EnableEurekaClient
public class EmployeeServiceApp { ... }
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
spring:
  application:
    name: employee-service

12.4 Saga Pattern Link to heading

When a business transaction spans multiple microservices, ACID is not feasible. The Saga pattern breaks it into local transactions, each publishing an event to trigger the next.

Choreography (event-driven):

OrderService → publishes OrderCreated
    → InventoryService → publishes InventoryReserved
        → PaymentService → publishes PaymentProcessed

Orchestration (central coordinator):

SagaOrchestrator → calls InventoryService.reserve()
                 → calls PaymentService.charge()
                 → calls ShippingService.ship()
                 → on failure → calls PaymentService.refund() (compensating)

Compensating Transactions: When a step fails, previously completed steps are undone via compensating (rollback) actions.

12.5 Circuit Breaker — Resilience4j Link to heading

Prevents cascading failures by stopping calls to a failing downstream service.

States:

CLOSED → (failure rate exceeds threshold) → OPEN → (wait expires) → HALF_OPEN
              ↑                                                       |
              └───────────────────────────────────────────────────────┘
                            (success in HALF_OPEN → CLOSED)
resilience4j:
  circuitbreaker:
    instances:
      inventoryService:
        slidingWindowSize: 10
        failureRateThreshold: 50
        waitDurationInOpenState: 10s
        permittedNumberOfCallsInHalfOpenState: 3
@CircuitBreaker(name = "inventoryService", fallbackMethod = "fallback")
public String callInventory(String productId) {
    return inventoryClient.check(productId);
}

public String fallback(String productId, Exception ex) {
    return "Product temporarily unavailable";
}

13. Messaging & Event Streaming Link to heading

13.1 Apache Kafka Link to heading

Kafka is a distributed, partitioned, replicated commit log (publish-subscribe system).

Core Concepts:

Concept Description
Topic Logical category of messages (analogous to a DB table)
Partition Topics split into partitions for parallelism; messages within a partition are ordered
Offset Unique sequential ID of a message within a partition
Producer Writes messages to topics
Consumer Reads messages from topics
Consumer Group Consumers sharing work — each partition consumed by one consumer in the group
Broker A Kafka server
Zookeeper / KRaft Cluster coordination

Reply Template Pattern (Request-Response over Kafka) Link to heading

// Producer (requester)
@Autowired ReplyingKafkaTemplate<String, Request, Response> replyingTemplate;

RequestReplyFuture<String, Request, Response> future =
    replyingTemplate.sendAndReceive(new ProducerRecord<>("request-topic", request));

Response response = future.get(10, TimeUnit.SECONDS).value();

// Consumer (responder)
@KafkaListener(topics = "request-topic")
@SendTo("response-topic")
public Response handleRequest(Request request) {
    return process(request);
}

13.2 ActiveMQ & JMS Link to heading

JMS (Java Message Service) is a Java API for message-oriented middleware.

Message types: TextMessage, ObjectMessage, BytesMessage, MapMessage, StreamMessage

Destinations: Queue (point-to-point) vs. Topic (publish-subscribe)

// Sending with Spring JMS
@Autowired JmsTemplate jmsTemplate;
jmsTemplate.convertAndSend("payment-queue", paymentRequest);

// Receiving
@JmsListener(destination = "payment-queue")
public void handlePayment(PaymentRequest request) {
    process(request);
}

ActiveMQ is a JMS-compliant message broker used for NPCI integrations requiring reliable, ordered delivery.

13.3 RabbitMQ Link to heading

Uses the AMQP protocol. Routes messages via Exchanges to Queues.

Exchange types: Direct (exact routing key), Fanout (broadcast), Topic (pattern match), Headers (by headers)

13.4 Messaging Comparison Link to heading

Kafka RabbitMQ ActiveMQ
Protocol Custom (TCP) AMQP JMS/AMQP/STOMP
Model Log (replay-able) Queue (consumed and deleted) Queue or Topic
Throughput Very high High Medium
Message retention Configurable (time/size) Until consumed Until consumed
Use case Event streaming, analytics Task queues, RPC Enterprise JMS, legacy

13.5 Request-Response Messaging Pattern Link to heading

1. Producer sends request message with unique correlationId and replyTo queue
2. Consumer processes and sends response to replyTo queue with same correlationId
3. Producer listens on replyTo queue, matches response by correlationId

This is how Kafka’s ReplyingKafkaTemplate and JMS’s QueueRequestor work.


14. DevOps & CI/CD Link to heading

14.1 CI/CD Pipeline Link to heading

Push to branch
      ↓
Checkout & Build (mvn compile / gradle build)
      ↓
Unit Tests (mvn test)
      ↓
Static Code Analysis (SonarQube)
      ↓
Security Scan (OWASP Dependency Check, SAST)
      ↓
Build Docker Image (docker build)
      ↓
Push to Container Registry
      ↓
Deploy to Staging
      ↓
Integration/E2E Tests
      ↓
Manual Approval Gate
      ↓
Deploy to Production
# .github/workflows/ci.yml (simplified)
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Build with Maven
        run: mvn -B package --file pom.xml
      - name: SonarQube Scan
        run: mvn sonar:sonar -Dsonar.host.url=$SONAR_URL
      - name: OWASP Dependency Check
        run: mvn org.owasp:dependency-check-maven:check
      - name: Build & Push Docker
        run: |
          docker build -t myapp:${{ github.sha }} .
          docker push myregistry/myapp:${{ github.sha }}

14.2 Docker & Containerization Link to heading

# Multi-stage Dockerfile for Spring Boot
FROM maven:3.9-eclipse-temurin-17 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn package -DskipTests

FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY --from=build /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

Key concepts:

  • Image: Immutable blueprint (layers)
  • Container: Running instance of an image
  • Volume: Persistent storage mounted into container
  • Network: Containers communicate via Docker networks
  • Compose: Multi-container orchestration for local dev

14.3 SonarQube & Security Scanning Link to heading

SonarQube: Static analysis for code smells, bugs, security hotspots, duplication, test coverage gaps.

OWASP Dependency Check: Scans dependencies against the National Vulnerability Database (NVD) for known CVEs.

SAST (Static Application Security Testing): Analyzes source code for vulnerabilities without running the application.

OWASP Top 10 (relevant to Java):

  1. Injection (SQL, LDAP, JNDI)
  2. Broken Authentication
  3. Sensitive Data Exposure
  4. XML External Entities (XXE)
  5. Broken Access Control
  6. Security Misconfiguration
  7. Cross-Site Scripting (XSS)
  8. Insecure Deserialization
  9. Using Components with Known Vulnerabilities
  10. Insufficient Logging & Monitoring

15. Testing Link to heading

15.1 TDD — Red-Green-Refactor Link to heading

  1. Red: Write a failing test.
  2. Green: Write the minimum code to make it pass.
  3. Refactor: Clean up while keeping tests green.
// Step 1: RED — write failing test
@Test
void shouldReturnTaxInclusivePrice() {
    PriceCalculator calc = new PriceCalculator();
    assertEquals(118.0, calc.priceWithTax(100.0), 0.001);
}

// Step 2: GREEN — minimal implementation
public class PriceCalculator {
    public double priceWithTax(double amount) {
        return amount * 1.18;
    }
}

// Step 3: REFACTOR — extract constant
public class PriceCalculator {
    private static final double TAX_RATE = 0.18;
    public double priceWithTax(double amount) {
        return amount * (1 + TAX_RATE);
    }
}

15.2 BDD — Behavior-Driven Development Link to heading

BDD extends TDD using Given-When-Then syntax in human-readable format.

Feature: Price Calculation
  Scenario: Calculate price with tax
    Given a product price of 100.0
    When I calculate the tax-inclusive price
    Then the result should be 118.0

Popular tools: Cucumber (Java), JBehave.

Key difference from TDD:

  • TDD focuses on how the code works (unit level).
  • BDD focuses on what the system should do (behavior, feature level).

15.3 JUnit & Mockito Link to heading

@ExtendWith(MockitoExtension.class)
class EmployeeServiceTest {

    @Mock
    EmployeeRepository repository;

    @InjectMocks
    EmployeeService service;

    @Test
    void shouldReturnEmployeeById() {
        Employee emp = new Employee(1L, "John");
        when(repository.findById(1L)).thenReturn(Optional.of(emp));

        Employee result = service.findById(1L);

        assertEquals("John", result.getName());
        verify(repository, times(1)).findById(1L);
    }

    @Test
    void shouldThrowWhenEmployeeNotFound() {
        when(repository.findById(99L)).thenReturn(Optional.empty());
        assertThrows(EmployeeNotFoundException.class, () -> service.findById(99L));
    }
}

Mockito key methods:

Method Purpose
when(...).thenReturn(...) Stub method to return value
when(...).thenThrow(...) Stub method to throw exception
verify(mock).method() Verify interaction
verify(mock, times(n)) Verify call count
verify(mock, never()) Verify method was never called
@Captor ArgumentCaptor Capture arguments passed to mock
doReturn / doThrow For void methods or spies

15.4 Parameterized Tests Link to heading

@ParameterizedTest
@ValueSource(ints = {1, 2, 3, 5, 8, 13})
void shouldBePositive(int number) {
    assertTrue(number > 0);
}

@ParameterizedTest
@CsvSource({
    "100.0, 118.0",
    "200.0, 236.0",
    "0.0,   0.0"
})
void shouldCalculateTax(double input, double expected) {
    assertEquals(expected, calculator.priceWithTax(input), 0.001);
}

@ParameterizedTest
@MethodSource("provideEmployees")
void shouldValidateEmployee(Employee emp, boolean expected) {
    assertEquals(expected, validator.isValid(emp));
}

static Stream<Arguments> provideEmployees() {
    return Stream.of(
        Arguments.of(new Employee("John", 30), true),
        Arguments.of(new Employee("", 30), false)
    );
}

15.5 Testing Pyramid Link to heading

          ╱ E2E Tests ╲       ← Few, slow, expensive
         ╱─────────────╲
        ╱ Integration   ╲     ← Medium number
       ╱─────────────────╲
      ╱   Unit Tests      ╲   ← Many, fast, cheap
     ╱─────────────────────╲
Level Scope Tools Count
Unit Single class/method JUnit, Mockito Many (70%)
Integration Multiple components, DB, messaging Spring Boot Test, Testcontainers Medium (20%)
E2E Full system, UI Selenium, Cypress, Playwright Few (10%)

Key annotations:

@SpringBootTest          // Full application context
@WebMvcTest              // Only MVC layer
@DataJpaTest             // Only JPA layer
@MockBean                // Replace a Spring bean with a mock