Back to Hidden Classes
Q2Question 2 of 2

Hidden classes vs dynamically generated `Proxy` classes, and their behaviour under JPMS module restrictions

Key Concepts
  • A `Proxy` is a named, loader-registered class that implements a list of interfaces and routes calls through an `InvocationHandler`.
  • A hidden class is nameless, loader-unregistered, independently unloadable, and defined from arbitrary bytecode.
  • Proxies are limited to interfaces; hidden classes can implement interfaces or extend classes.
  • Under JPMS, a `Proxy` is placed in a dynamic module and requires all proxied interfaces to be accessible.
  • A hidden class lives in the same runtime package and module as its lookup class, inheriting that module's access.
  • Use `Proxy` for simple interface wrapping without bytecode generation; use hidden classes for framework-level code generation.
  • Hidden classes do not weaken module encapsulation because they share the host module's protection domain.

Definition

A `Proxy` is a named, registered, interface-based class in a dynamic module; a hidden class is nameless, unregistered, unloadable, and lives in its lookup class's own module.
  • Dynamic Proxy – created by Proxy.newProxyInstance(), it is a normal, named, loader-registered class that implements a list of interfaces and dispatches calls to an InvocationHandler.
  • Hidden class – created by defineHiddenClass(), it is nameless, not registered in the loader, independently unloadable, and defined from arbitrary bytecode.
  • Interface limitation – proxies can only implement interfaces, not extend concrete classes; hidden classes can do either.
  • JPMS Proxy – placed in a dynamic module, requires all proxied interfaces to be accessible and exported.
  • JPMS hidden class – defined in the same runtime package and module as the lookup class, inheriting its access and readability.

A Proxy is like National Bank of India creating a new, officially-named stand-in counter listed in the branch directory that follows the bank's visitor rules about which departments it may enter. A hidden class is like a private, unnamed helper created inside a specific manager's office: it is not in the directory, only that manager can use it, and it automatically has the same keys and department membership as the manager who made it.

Understanding the Concepts

What is a dynamic Proxy?

A dynamic Proxy is created with Proxy.newProxyInstance(classLoader, interfaces, handler). The JVM generates a class that implements all the listed interfaces, and every method call is dispatched to handler.invoke(proxy, method, args). The generated class is named (e.g., $Proxy0), registered in the class loader, and lives as long as the loader.

JAVA
package com.bank.service;

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

public class ProxyDemo {

    interface AccountService {
        BigDecimal getBalance(String accountId);
        void credit(String accountId, BigDecimal amount);
    }

    static class RealAccountService implements AccountService {
        @Override
        public BigDecimal getBalance(String accountId) {
            return new BigDecimal("500000");
        }

        @Override
        public void credit(String accountId, BigDecimal amount) {
            System.out.println("Credited " + amount + " to " + accountId);
        }
    }

    public static void main(String[] args) {
        AccountService real = new RealAccountService();

        // Dynamic proxy wrapping the real service
        AccountService proxy = (AccountService) Proxy.newProxyInstance(
                AccountService.class.getClassLoader(),
                new Class<?>[]{ AccountService.class },
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object p, Method method, Object[] a)
                            throws Throwable {
                        System.out.println("[LOG] " + method.getName() + " called");
                        return method.invoke(real, a);
                    }
                }
        );

        System.out.println("Balance: " + proxy.getBalance("SAV-1001"));
        proxy.credit("SAV-1001", new BigDecimal("10000"));
        System.out.println("Proxy class: " + proxy.getClass().getName());
    }
}
TEXT
[LOG] getBalance called
Balance: 500000
[LOG] credit called
Credited 10000 to SAV-1001
Proxy class: jdk.proxy1.$Proxy0

What is a hidden class in comparison?

A hidden class is created from raw bytecode via defineHiddenClass. It is not registered in the loader, has no usable binary name, and can be unloaded independently. It can implement any interface or even extend a class, because you supply arbitrary bytecode.

JAVA
package com.bank.service;

import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;

public class HiddenClassComparison {
    public void createHiddenCalculator() throws Exception {
        byte[] classBytes = generateCalculatorBytes();

        Lookup hidden = MethodHandles.lookup()
                .defineHiddenClass(classBytes, true,
                        Lookup.ClassOption.NESTMATE);

        Class<?> hiddenClass = hidden.lookupClass();
        System.out.println("Hidden: " + hiddenClass.isHidden());
        System.out.println("Name:   " + hiddenClass.getName());

        // Cannot be found by Class.forName
        try {
            Class.forName(hiddenClass.getName());
        } catch (ClassNotFoundException e) {
            System.out.println("Not discoverable by name");
        }
    }

    private byte[] generateCalculatorBytes() {
        return new byte[0]; // placeholder
    }
}

How do Proxy and hidden class compare side by side?

| Feature | Dynamic Proxy | Hidden Class |
|---|---|---|
| Creation | Proxy.newProxyInstance() | defineHiddenClass() |
| Named | Yes ($Proxy0) | No (contains /) |
| Loader-registered | Yes | No |
| Target | Interfaces only | Any class or interface |
| Bytecode generation | Automatic (from interface list) | Manual (supply bytecode) |
| Independent unloading | No (tied to loader) | Yes (default) |
| Private host access | No | Yes (with NESTMATE) |
| JPMS behaviour | Dynamic module | Host's module |

JAVA
package com.bank.service;

import java.math.BigDecimal;

public class ComparisonSummary {
    // Use Proxy: simple interface wrapping
    // AccountService proxy = (AccountService) Proxy.newProxyInstance(...);

    // Use Hidden Class: specialized code generation
    // Lookup hidden = lookup.defineHiddenClass(bytes, true, NESTMATE);
}

How does a Proxy behave under JPMS?

Under the module system, a Proxy for public, exported interfaces is placed in a dynamic module with the right reads edges. For non-public interfaces, all such interfaces must be in the same package and module, and the proxy is defined in that package.

JAVA
package com.bank.service;

import java.lang.reflect.Proxy;
import java.math.BigDecimal;

public class ProxyModuleDemo {

    // Public, exported interface: proxy goes to a dynamic module
    public interface TransferService {
        BigDecimal transfer(String from, String to, BigDecimal amount);
    }

    public static void main(String[] args) {
        TransferService proxy = (TransferService) Proxy.newProxyInstance(
                TransferService.class.getClassLoader(),
                new Class<?>[]{ TransferService.class },
                (p, method, a) -> {
                    System.out.println("Proxy module: " + p.getClass().getModule());
                    return new BigDecimal("50000");
                }
        );

        proxy.transfer("SAV-1001", "SAV-2002", new BigDecimal("50000"));
    }
}

How does a hidden class behave under JPMS?

A hidden class is defined in the same runtime package and module as the lookup class. It inherits exactly that module's readability and access. There is no separate dynamic module, and no requirement to export or open additional packages.

JAVA
package com.bank.service;

import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;

public class HiddenClassModuleDemo {
    public void showModuleInfo() throws Exception {
        byte[] classBytes = new byte[0]; // placeholder

        Lookup hidden = MethodHandles.lookup()
                .defineHiddenClass(classBytes, true);

        Class<?> h = hidden.lookupClass();
        System.out.println("Host module:   " + HiddenClassModuleDemo.class.getModule());
        System.out.println("Hidden module: " + h.getModule());
        System.out.println("Same module:   " + (h.getModule() == HiddenClassModuleDemo.class.getModule()));
    }
}

When should you use Proxy vs hidden class?

Use Proxy when you just need an interface-based wrapper with a single dispatch point and do not want to generate bytecode by hand. Use a hidden class when you are writing framework-level code that generates specialized classes and needs them invisible, unloadable, or able to touch a host's private state.

JAVA
package com.bank.service;

import java.lang.reflect.Proxy;
import java.math.BigDecimal;

public class DecisionGuide {

    interface LoanService {
        BigDecimal calculateEMI(BigDecimal principal, int months);
    }

    // Application code: use Proxy for simple logging wrapper
    public LoanService createLoggingProxy(LoanService real) {
        return (LoanService) Proxy.newProxyInstance(
                LoanService.class.getClassLoader(),
                new Class<?>[]{ LoanService.class },
                (proxy, method, args) -> {
                    System.out.println("[AUDIT] " + method.getName());
                    return method.invoke(real, args);
                }
        );
    }

    // Framework code: use hidden class for specialized generator
    // Lookup hidden = lookup.defineHiddenClass(bytes, true, NESTMATE);
    // hidden.lookupClass().getConstructor().newInstance();
}

What about security and encapsulation?

A hidden class does not weaken module encapsulation. It shares the host module's protection domain and cannot access a package that its host module itself cannot access. A Proxy over interfaces from multiple modules must have all those interfaces accessible, and non-exported interfaces require being in one package.

JAVA
package com.bank.service;

public class SecurityNote {
    // Hidden class in com.bank.service module:
    // - CAN access everything com.bank.service can access
    // - CANNOT access unexported packages of other modules
    // - Protection domain = host's protection domain

    // Proxy over non-exported interface:
    // - All such interfaces must be in same package and module
    // - Proxy defined in that package
}

What should a senior engineer know?

For frameworks migrating off Unsafe or needing fine lifecycle control in modular banking applications, hidden classes avoid the dynamic-module complexity that proxies introduce. Modern JDK machinery (lambda metafactory, method handle proxies) uses hidden classes internally. Application code wiring simple interface interceptors can still use Proxy or ByteBuddy.

Visual Overview

DIAGRAM
┌─────────────────────────────────────────────┐
│   Proxy vs Hidden Class Comparison          │
├─────────────────────────────────────────────┤
│                                             │
│  Proxy.newProxyInstance()                   │
│  ┌─────────────────────────┐                │
│  │ Named: $Proxy0          │                │
│  │ Loader-registered       │                │
│  │ Interface-only          │                │
│  │ InvocationHandler       │                │
│  │ Dynamic module (JPMS)   │                │
│  │ Cannot unload alone     │                │
│  └─────────────────────────┘                │
│                                             │
│  defineHiddenClass()                        │
│  ┌─────────────────────────┐                │
│  │ Nameless (Host/0x...)   │                │
│  │ NOT registered          │                │
│  │ Any bytecode target     │                │
│  │ Direct method calls     │                │
│  │ Host's module (JPMS)    │                │
│  │ Independently unloadable│                │
│  └─────────────────────────┘                │
│                                             │
├─────────────────────────────────────────────┤
│  JPMS Behaviour                             │
│                                             │
│  Proxy ──→ dynamic module ──→ needs exports │
│  Hidden ──→ host's module ──→ inherits access│
└─────────────────────────────────────────────┘

Key Takeaway

A dynamically generated Proxy class is a normal, named, loader-registered class that implements a list of interfaces and routes every call through an invocation handler. It is interface-based, so it cannot proxy a concrete class. A hidden class is nameless, not registered in the loader, independently unloadable, and defined from arbitrary bytecode, so it can implement interfaces or extend classes.

The comparison is straightforward: use Proxy when you need a simple interface wrapper with one dispatch point and do not want to generate bytecode yourself, because it is built in and easy. Use a hidden class when you are writing framework-level code that generates specialized classes and needs them invisible, unloadable, or able to touch a host's private state with NESTMATE. In a banking project at National Bank of India, Rajesh wraps the account service with a logging proxy for simple interception, and Pankaj uses hidden classes for a generated fee calculator that reads a private rate on its host.

Under the module system the two behave differently. A Proxy is placed in a dynamic module that is given the right reads edges, but if any interface is non-public, all such interfaces must sit in the same package and module. A hidden class is simpler: it lives in the same runtime package and module as the lookup class, inheriting that module's readability and access, with no extra exports needed.

The senior point is that a hidden class does not weaken encapsulation because it shares the host module's protection domain and cannot reach a package the host module itself cannot reach. That clean module fit, plus independent unloading and private host access, is why the JDK adopted hidden classes internally.

Remember one sentence: a Proxy is a named, interface-only, loader-registered class in a dynamic module, while a hidden class is nameless, arbitrary-bytecode, independently unloadable, and lives in its host's module.

---

Share
Use ← → arrow keys to navigate