Definition
- Dynamic
Proxy– created byProxy.newProxyInstance(), it is a normal, named, loader-registered class that implements a list of interfaces and dispatches calls to anInvocationHandler. - 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.
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());
}
}
[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.
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 |
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.
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.
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.
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.
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
┌─────────────────────────────────────────────┐ │ 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.
---