May 10, 2025

๐Ÿ›ก️ Understanding the Proxy Design Pattern in Java — Real-World Usage and Examples

When it comes to structural design patterns, the Proxy Pattern stands out as one of the most powerful tools for controlling access to an object, managing performance, and adding extra functionality transparently. From security to lazy loading, the proxy pattern has wide-reaching applications in modern Java development — especially in frameworks like Spring and Hibernate.

In this article, we’ll explore:

  • ✅ What is the Proxy Design Pattern?

  • ๐Ÿ” Real-world analogies

  • ๐Ÿ› ️ Types of Proxies (Virtual, Protection, Remote, etc.)

  • ☕ Java implementation

  • ๐ŸŒฑ How Spring uses proxies

  • ๐Ÿ“Š Advantages and trade-offs


๐Ÿง  What is the Proxy Pattern?

Proxy Pattern provides a surrogate or placeholder for another object to control access to it.

Think of it as a gatekeeper or middleman that decides whether or how to pass a request to the real object.

It belongs to the structural family of design patterns and is especially useful when:

  • Creating the actual object is expensive.

  • You want to control access to the real object.

  • You need to add extra behavior (e.g., logging, security) without modifying the real object.


๐ŸŽฏ Real-world Analogy

Imagine you're trying to meet a high-profile CEO. You can't just walk into their office — you go through their secretary (proxy) first, who checks your purpose, filters calls, and decides whether to forward your request.


๐Ÿงฑ Types of Proxies in Java

Type Purpose
๐Ÿ”„ Virtual Proxy Defers creation of resource-heavy objects until needed
๐Ÿ” Protection Proxy Controls access based on permissions or user roles
๐ŸŒ Remote Proxy Represents objects in a different address space (e.g., RMI)
๐Ÿ“ Logging Proxy Adds behavior like logging, caching, or performance tracking
๐Ÿงช Smart Proxy Adds extra actions (e.g., ref counting, access tracking)

๐Ÿ’ป Java Implementation — Virtual Proxy

Let's build a virtual proxy that delays the creation of a large image until it's needed.

Step 1: Create a Common Interface

public interface Image {
    void display();
}

Step 2: The Real Object

public class RealImage implements Image {
    private String fileName;

    public RealImage(String fileName) {
        this.fileName = fileName;
        loadFromDisk();
    }

    private void loadFromDisk() {
        System.out.println("Loading " + fileName);
    }

    public void display() {
        System.out.println("Displaying " + fileName);
    }
}

Step 3: The Proxy Class

public class ProxyImage implements Image {
    private RealImage realImage;
    private String fileName;

    public ProxyImage(String fileName) {
        this.fileName = fileName;
    }

    public void display() {
        if (realImage == null) {
            realImage = new RealImage(fileName); // expensive
        }
        realImage.display();
    }
}

Step 4: Client Code

public class Main {
    public static void main(String[] args) {
        Image image = new ProxyImage("test.jpg");

        System.out.println("Image created");

        image.display(); // loads and displays
        image.display(); // only displays
    }
}

Output:

Image created
Loading test.jpg
Displaying test.jpg
Displaying test.jpg

๐Ÿงฐ Spring Framework and Proxy Pattern

Spring relies heavily on proxies, especially for:

✅ AOP (Aspect-Oriented Programming)

  • Cross-cutting concerns like logging, transactions, and security are implemented using dynamic proxies (JDK or CGLIB).

✅ @Transactional

  • When you use @Transactional, Spring creates a proxy around the bean and applies transaction boundaries before and after the method call.

Example:

@Transactional
public void transferMoney(Account from, Account to, BigDecimal amount) {
    // Transactional logic
}

Here, Spring uses a proxy to manage the transaction behind the scenes.


๐Ÿ“Š Advantages of the Proxy Pattern

  • ✔️ Controls access to the real object

  • ✔️ Adds functionality without changing original code

  • ✔️ Supports lazy initialization

  • ✔️ Can improve performance and security


⚠️ Drawbacks

  • ❌ Adds extra complexity

  • ❌ Overhead if too many proxies are layered

  • ❌ Harder debugging in heavily proxied environments (especially dynamic proxies)


๐Ÿ“ฆ When to Use the Proxy Pattern

Use Case Recommended?
Lazy-loading large resources ✅ Yes (Virtual Proxy)
Security enforcement ✅ Yes (Protection Proxy)
Access to remote resources ✅ Yes (Remote Proxy)
Adding cross-cutting concerns ✅ Yes (Smart/Logging Proxy, AOP)
Overusing proxies in simple apps ❌ Avoid

๐Ÿงพ Final Thoughts

The Proxy Pattern is a powerful tool in your Java toolbox. Whether you're optimizing performance, enforcing security, or adding dynamic behavior, the proxy allows you to extend behavior transparently without touching the real business logic.

Frameworks like Spring and Hibernate make heavy use of this pattern under the hood — so understanding it is key to writing better, cleaner, and more efficient Java applications.


✨ TL;DR

  • Proxy Pattern = Surrogate for real object

  • Types: Virtual, Protection, Remote, Logging

  • Java and Spring use proxies for lazy loading, AOP, and security

  • Recommended for clean separation and controlled object access


Would you like this blog converted to Markdown, PDF, or a LinkedIn/Medium-optimized format?

๐ŸŒฟ Lazy Loading with Static Inner Class in Java — A Deep Dive Into Smart Initialization

In the world of Java performance optimization, lazy loading is a best practice when you're dealing with heavy objects or expensive computations that may not always be needed. One particularly elegant technique to achieve lazy initialization is the use of a static inner class — often underutilized, but incredibly powerful.

In this comprehensive blog, we’ll explore:

  • What lazy loading means in Java

  • How static inner classes work for lazy loading

  • Detailed comparisons with other lazy initialization techniques

  • When to use which method

  • Whether the static inner class approach is complex or not


๐Ÿ“˜ What Is Lazy Loading?

Lazy loading is a design pattern that defers the initialization of an object until it is actually needed.

Why Use Lazy Loading?

  • ๐Ÿง  Optimized memory usage: Don’t load it if you don’t use it.

  • ๐Ÿš€ Faster application startup: Defer costly operations.

  • ๐Ÿ›  Improved performance in scalable systems: Reduces bottlenecks during application bootstrapping.


๐Ÿงฑ Static Inner Classes for Lazy Initialization

What is a Static Inner Class?

A static inner class is a nested class declared with the static keyword. It does not need an instance of the outer class to be instantiated, and more importantly:

⚠️ A static inner class is not loaded into memory until it is explicitly referenced.

This makes it perfect for on-demand loading.


๐Ÿ›  Example: Singleton with Static Inner Class

public class LazySingleton {

    private LazySingleton() {
        System.out.println("Constructor called");
    }

    // Inner class loaded only when getInstance() is called
    private static class Holder {
        private static final LazySingleton INSTANCE = new LazySingleton();
    }

    public static LazySingleton getInstance() {
        return Holder.INSTANCE;
    }
}

✅ JVM Guarantees:

  • The Holder class is only loaded when getInstance() is called.

  • Class loading in Java is thread-safe, so no synchronization is needed.

  • Lazy, thread-safe, and elegant — without boilerplate.


๐Ÿ” Other Ways to Do Lazy Initialization in Java

Let’s compare the static inner class technique with other common singleton approaches:

1. Eager Initialization

public class EagerSingleton {
    private static final EagerSingleton INSTANCE = new EagerSingleton();
    private EagerSingleton() {}
    public static EagerSingleton getInstance() {
        return INSTANCE;
    }
}

✅ Simple
❌ Not lazy — instance is created even if never used


2. Lazy Initialization (Non-thread-safe)

public class LazySingleton {
    private static LazySingleton instance;
    private LazySingleton() {}
    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

✅ Lazy
❌ Not thread-safe — multiple threads may create multiple instances


3. Lazy Initialization with synchronized Method

public class LazySingleton {
    private static LazySingleton instance;
    private LazySingleton() {}
    public static synchronized LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

✅ Lazy and thread-safe
❌ Performance overhead due to synchronization


4. Double-Checked Locking

public class LazySingleton {
    private static volatile LazySingleton instance;
    private LazySingleton() {}

    public static LazySingleton getInstance() {
        if (instance == null) {
            synchronized (LazySingleton.class) {
                if (instance == null) {
                    instance = new LazySingleton();
                }
            }
        }
        return instance;
    }
}

✅ Lazy
✅ Thread-safe
⚠️ Slightly complex
⚠️ Requires volatile (since Java 1.5) to prevent instruction reordering


5. Static Inner Class (Best of All Worlds)

✅ Lazy
✅ Thread-safe
✅ No synchronization overhead
✅ Cleaner and easier than double-checked locking
✅ JVM handles everything


๐Ÿ“Š Comparative Table

Method Lazy Thread-safe Performance Complexity
Eager Initialization Simple
Non-thread-safe Lazy Init Simple
Synchronized Method Simple
Double-Checked Locking Medium
Static Inner Class Very Simple

๐Ÿค” Is Static Inner Class Complicated?

Not at all! It's one of the simplest and safest lazy initialization patterns in Java:

  • No need for synchronization or volatile

  • No risk of race conditions

  • Easier to read and maintain than double-checked locking

In fact, many developers consider this the go-to method for implementing lazy singletons in modern Java applications.


๐Ÿง  When Should You Use It?

✅ Use static inner class lazy loading when:

  • You want a lazy singleton or lazy-loaded config

  • Thread safety is essential

  • You want cleaner code than double-checked locking

❌ Don’t use if:

  • Your object needs to be initialized early (eager loading is preferred)

  • You have complex dependency cycles that make static initialization risky


✅ Final Recommendation

If you're looking for a safe, performant, and clean way to implement lazy loading in Java — especially for singletons — use the static inner class approach. It's backed by the JVM, requires no locking or concurrency hacks, and works beautifully across all modern Java versions.


๐Ÿ“Œ TL;DR

  • Lazy loading improves performance and resource management.

  • Static inner classes defer loading until needed and are thread-safe by default.

  • Compared to other lazy initialization techniques, this is:

    • ๐Ÿ”’ Safer

    • ⚡ Faster

    • ๐Ÿงผ Cleaner


May 4, 2025

⚙️ Modern Guide to Calculating Fixed Thread Pool Size in Java ๐Ÿš€

⚙️ Modern Guide to Calculating Fixed Thread Pool Size in Java ๐Ÿš€

๐Ÿงต Thread Pools Are Cool — But Are Yours Optimally Sized?

Using a fixed thread pool in Java is common:

ExecutorService executor = Executors.newFixedThreadPool(10);

But is 10 the best number?

Using too many threads leads to context switching and memory pressure.
Using too few? You're leaving performance on the table.

Let’s level up: learn how to calculate the perfect thread pool size using some concurrency theory, practical math, and real examples.


๐Ÿง  Theorem: Amdahl’s Law (for CPU Utilization)

"The speedup of a program using multiple processors is limited by the time needed for sequential operations."

In simpler terms:

  • Not all parts of your code can be parallelized.

  • The more threads you add, the less benefit you get after a point (diminishing returns).

This ties directly into how you size thread pools.


๐Ÿ“ Universal Thread Pool Sizing Formula

๐Ÿ’ก From Java Concurrency in Practice:

Thread pool size = Number of cores * Target CPU utilization * (1 + (Wait time / Compute time))

✅ Where:

Variable Meaning
Cores Number of logical processors (hyperthreaded cores)
CPU utilization 0.0 to 1.0 (usually 0.8 for 80%)
Wait time Time task spends blocked (I/O, DB, etc.)
Compute time Time task spends using CPU

๐ŸŽฏ Real-Life Example (IO-Bound Tasks)

Imagine:

  • You’re writing a REST API.

  • Each request waits for a DB query (800 ms) and processes JSON (200 ms).

  • Your server has 8 logical cores.

  • You want 80% CPU usage.

๐Ÿ“Š Calculation:

int cores = 8;
double utilization = 0.8;
double waitTime = 800;
double computeTime = 200;

int poolSize = (int) (cores * utilization * (1 + (waitTime / computeTime)));
// 8 * 0.8 * (1 + 800/200) = 8 * 0.8 * 5 = 32

✅ Recommended thread pool size: 32 threads


๐Ÿ” CPU-Bound Tasks? Keep It Tight

If your task is pure computation:

Formula:

Optimal size = Cores + 1

Why +1? While one thread waits (GC, context switch), others can work.

Example:

int cores = Runtime.getRuntime().availableProcessors();
int optimalSize = cores + 1;

๐Ÿงช How to Measure Wait vs Compute Time

Use System.nanoTime() to measure portions of your task:

long start = System.nanoTime();
// Simulate DB/API/IO
long wait = System.nanoTime() - start;

start = System.nanoTime();
// Simulate computation
long compute = System.nanoTime() - start;

Use averages to estimate waitTime / computeTime.


๐Ÿ“ฆ Java Code: Dynamic Pool Sizing

public class DynamicThreadPoolCalculator {
    public static int calculateOptimalThreads(int cores, double utilization, long waitMs, long computeMs) {
        return (int) (cores * utilization * (1 + ((double) waitMs / computeMs)));
    }

    public static void main(String[] args) {
        int cores = Runtime.getRuntime().availableProcessors();
        int optimal = calculateOptimalThreads(cores, 0.8, 800, 200);
        System.out.println("Recommended thread pool size: " + optimal);
    }
}

๐Ÿ” Bonus Theorem: Little's Law

Used in queuing theory:
L = ฮป × W

Where:

  • L: average number of items in system

  • ฮป: average arrival rate

  • W: average time in the system

Helps estimate task arrival rate vs service time.


๐Ÿ“ˆ Visual Suggestion (for your blog)

  • Pie Chart: Wait vs Compute time

  • Bar Chart: Thread pool size with different wait/compute ratios

  • Heatmap: CPU usage across core count and thread pool sizes


✅ Summary Table

Task Type Sizing Formula
CPU-Bound Cores + 1
IO-Bound Cores * Utilization * (1 + Wait / Compute)
Adaptive Pool Use ThreadPoolExecutor with scaling logic

๐Ÿง  Pro Tips

  • Start with a small pool → monitor → tune

  • Use JVisualVM, JFR, or Micrometer to observe real-time metrics.

  • Combine with bounded queue size to avoid OOM under load.


๐Ÿ“Œ Conclusion

Instead of guessing thread pool size, apply concurrency principles, measure, and then let math guide your architecture.

Would you like this converted to a Markdown blog file or ready-to-publish HTML template?

๐Ÿงฉ Java Collections Cheat Sheet: List vs Set vs Map — And All Their Variants

Java’s Collection Framework is powerful — but choosing the right data structure can be confusing. This blog covers all major types: List, Set, Map, and their key implementations like ArrayList, HashSet, HashMap, etc. Let’s break them down.


๐Ÿ“ฆ 1. Collection Hierarchy Overview

               Collection
              /    |     \
           List   Set    Queue
                    \
                    Map (not a Collection, but part of framework)

๐Ÿ”ข List — Ordered, Duplicates Allowed

✅ Common Implementations:

Type Preserves Order Allows Duplicates Thread-Safe Random Access Best For
ArrayList ✅ Yes ✅ Yes ❌ No ✅ Fast (O(1)) Fast access, rare insert/delete
LinkedList ✅ Yes ✅ Yes ❌ No ❌ Slow (O(n)) Frequent insertions/deletions
Vector ✅ Yes ✅ Yes ✅ Yes ✅ Fast (O(1)) Thread-safe, legacy code

๐Ÿšซ Set — No Duplicates Allowed

✅ Common Implementations:

Type Preserves Order Sorted Allows Duplicates Thread-Safe Best For
HashSet ❌ No ❌ No ❌ No ❌ No Fast insert/check, unordered
LinkedHashSet ✅ Yes ❌ No ❌ No ❌ No Insertion order preserved
TreeSet ✅ Yes (sorted) ✅ Yes ❌ No ❌ No Sorted unique elements

๐Ÿ”Ž TreeSet uses a Red-Black Tree (log(n) operations).


๐Ÿ—บ️ Map — Key-Value Pairs (Keys Unique)

✅ Common Implementations:

Type Order Sorted Allows Nulls Thread-Safe Best For
HashMap ❌ No ❌ No ✅ One null key, many null values ❌ No Fast lookup by key (O(1) avg)
LinkedHashMap ✅ Insertion order ❌ No ✅ Same as HashMap ❌ No Ordered key-value pairs
TreeMap ✅ Sorted by keys ✅ Yes ❌ No null keys ❌ No Sorted map, navigation methods
Hashtable ❌ No ❌ No ❌ No null key/value ✅ Yes Legacy thread-safe map
ConcurrentHashMap ❌ No ❌ No ❌ No null key/value ✅ High performance Concurrent access

๐Ÿ’ก HashMap vs TreeMap vs LinkedHashMap

Feature HashMap TreeMap LinkedHashMap
Lookup Time O(1) average O(log n) O(1)
Key Order None Sorted (natural or comparator) Insertion order
Null Keys ✅ One allowed ❌ Not allowed ✅ One allowed
Use When Fast access Sorted keys needed Maintain order

๐Ÿง  When to Use What?

Use Case Recommended Class
Fast search by key HashMap
Preserve insertion order (Map) LinkedHashMap
Maintain sorted key-value pairs TreeMap
Unique values only HashSet
Sorted unique values TreeSet
Ordered unique values LinkedHashSet
Thread-safe Map (modern) ConcurrentHashMap
Fast element access ArrayList
Many insertions/deletions LinkedList

๐Ÿ” Thread-Safety Tips

  • Prefer ConcurrentHashMap over Hashtable.

  • For other collections, use:

    List<String> syncList = Collections.synchronizedList(new ArrayList<>());
    Set<String> syncSet = Collections.synchronizedSet(new HashSet<>());
    

⚙️ Bonus: How Growth Happens

Structure Growth Factor Notes
ArrayList 1.5x Internal array resized when full
Vector 2x Legacy; slower due to synchronization
HashMap ~2x Doubles capacity when load factor > 0.75

✅ Final Thoughts

Understanding the right collection for your use case can boost both performance and readability. Bookmark this post for quick reference and code like a pro!


Would you like this turned into a Markdown blog post, PDF, or HTML page for publishing?