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. WriteBase.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
HttpClientinstance 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[], orinstanceof T - Cannot have
staticgeneric 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
nextpointers. - 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;synchronizedon 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:
volatileforces 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:
volatileonly guarantees visibility, not atomicity. Compound operations likecount++still require locks orAtomicInteger.
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
Threadmaintains aThreadLocalMapwithWeakReferencekeys pointing toThreadLocalinstances. - Memory Leak Hazard:
ThreadLocalMapuses weak references for keys but strong references for values. If theThreadLocalkey is GC’d, the value remains pinned by the thread’s strong reference path. - Mitigation: Always call
.remove()inside afinallyblock 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
- Delegation: Loading queries are delegated up the chain before files are scanned.
- Visibility: Child ClassLoaders can view parent-loaded classes, but not vice versa.
- 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
Tis instantiated. - A static method in
Tis invoked. - A mutable static field in
Tis 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 asynchronizedblock.- Always use
while(notif) to guard against spurious wakeups. - Prefer
notifyAll()overnotify()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:
- Mark the class as
finalto prevent subclassing. - Declare all fields as
private final. - Provide no setter methods.
- 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:
- Instantiate
- Populate properties (DI)
BeanNameAware.setBeanName()BeanFactoryAware.setBeanFactory()ApplicationContextAware.setApplicationContext()@PostConstructInitializingBean.afterPropertiesSet()- Bean is ready for use
@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:
- Extract
B()into a separate Spring service component. - Inject the proxy via
@Autowiredor 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:
- Inbound Request Interception: Client sends HTTP request to
DispatcherServlet. - Handler Mapping Selection:
DispatcherServletconsultsHandlerMappingto identify the controller matching the URL. - Interceptor Pre-Filtering: Request passes through
HandlerInterceptor.preHandle(). If any returnsfalse, execution terminates. - Adapter Execution:
HandlerAdapterdecodes routing variables and triggers the controller method. - Business Logic: Controller executes services and returns data or
ModelAndView. - View Resolution:
ViewResolverconverts view names to physical pages;HttpMessageConverterserializes to JSON for APIs. - 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:
- Identify a bounded context to extract.
- Build the new microservice.
- Route new traffic to the microservice via a proxy.
- Retire that functionality from the monolith.
- 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):
- Injection (SQL, LDAP, JNDI)
- Broken Authentication
- Sensitive Data Exposure
- XML External Entities (XXE)
- Broken Access Control
- Security Misconfiguration
- Cross-Site Scripting (XSS)
- Insecure Deserialization
- Using Components with Known Vulnerabilities
- Insufficient Logging & Monitoring
15. Testing Link to heading
15.1 TDD — Red-Green-Refactor Link to heading
- Red: Write a failing test.
- Green: Write the minimum code to make it pass.
- 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