How would you implement a class loader that loads class bytes from a non-standard source such as a database row or an encrypted archive, and which methods of java.lang.ClassLoader do you override?
answer
- findClass = mechanism, loadClass = policy
- defineClass: protected + final, byte[] → Class
- wrong name → NoClassDefFoundError
- registerAsParallelCapable + getClassLoadingLock
- override findResource too, or ServiceLoader breaks
basics
~20 sExtend java.lang.ClassLoader, override findClass(name): fetch the bytes yourself, then call the protected defineClass(name, bytes, 0, len), which asks the VM to parse them into a runtime Class. Leave loadClass alone so parent delegation still works.
solid answer
~50 sSubclass `ClassLoader`, pass a parent into `super(parent)`, and override **`findClass`**. The inherited `loadClass` already implements the policy — check what this loader has already defined, ask the parent, and only then call `findClass` — so overriding just `findClass` gets standard delegation for free. Inside `findClass` you map the binary name (`com.acme.Foo`) to your storage: read the blob, decrypt it, download it, or generate it. Then call `defineClass(name, bytes, 0, bytes.length)`. `defineClass` is `protected final` — it is the only bridge from `byte[]` to `Class`, and it performs format checking, so it can throw `ClassFormatError` or `NoClassDefFoundError` if the name inside the file disagrees with the name you passed. If the bytes don't exist, throw `ClassNotFoundException`. Also override `findResource`/`findResources` if hosted code will read files. Call `registerAsParallelCapable()` in a static initializer if concurrent loads are expected.
code
java · 22 linespublic class BlobClassLoader extends ClassLoader {
static { registerAsParallelCapable(); }
private final ByteSource source; // DB, encrypted jar, network, generator...
public BlobClassLoader(ClassLoader parent, ByteSource source) {
super(parent); // keep normal delegation
this.source = source;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
Class<?> already = findLoadedClass(name);
if (already != null) return already;
byte[] bytes = source.read(name.replace('.', '/') + ".class");
if (bytes == null) throw new ClassNotFoundException(name);
return defineClass(name, bytes, 0, bytes.length);
}
}
}go deeper
Know the shape: extend ClassLoader, override findClass, call defineClass with the bytes, and don't touch loadClass. Being able to write the ten-line skeleton is enough.
Explain why findClass is the hook (loadClass already does findLoadedClass → parent → findClass), the name/format errors defineClass can throw, and that resources need findResource too.
Add the operational details: parallel-capable registration and per-name locks, idempotent definition under races, protection domains and sealing, and the fact that dependencies resolve lazily through this same loader.
Frame it as namespace design — a loader is a namespace boundary with lifetime, visibility and security consequences; decide when a custom loader is justified at all versus modules, separate processes, or Lookup-based definition.
## The two halves of ClassLoader `java.lang.ClassLoader` deliberately separates *policy* from *mechanism*. **Policy** lives in `loadClass(String name, boolean resolve)`. Its inherited implementation does three things, in order: call `findLoadedClass(name)` to see whether *this* loader has already defined that name; if not, delegate to the parent loader (or the built-in bootstrap loader when the parent is `null`); and only if the parent fails with `ClassNotFoundException`, call `findClass(name)`. It then optionally links the result via `resolveClass`. **Mechanism** lives in `findClass(String name)`. The base implementation does nothing but throw `ClassNotFoundException` — it exists purely as your extension point. Overriding `findClass` therefore gives you a loader that participates in the standard hierarchy exactly like the platform's own loaders, while sourcing bytes from anywhere you like. That is why the idiomatic custom loader overrides `findClass` and not `loadClass`. You override `loadClass` only when you deliberately want a *different policy* — most commonly child-first (look locally before the parent) for plugin isolation, and even then you normally special-case `java.*`/`jdk.*` so JDK types still come from above. ## defineClass is the primitive `protected final Class<?> defineClass(String name, byte[] b, int off, int len)` (and its overloads taking a `ProtectionDomain` or a `ByteBuffer`) is the only supported way to turn bytes into a runtime type. It is `final` so nobody can substitute a fake, and `protected` so you can only inject types into a loader you yourself subclass — you cannot push a class into someone else's namespace. What it does: - Parses and format-checks the classfile (magic number, version, constant pool). Bad bytes → `ClassFormatError`; a classfile major version newer than this JVM → `UnsupportedClassVersionError`. - Checks the name. If you pass `"com.acme.Foo"` but the file declares `com/acme/Bar`, you get `NoClassDefFoundError: ... wrong name`. Passing `null` for the name lets the VM take the name from the bytes — handy for generated code. - Records **this loader as the defining loader** of the new class. That is what makes the resulting type distinct from an identically-named type defined by another loader. - Refuses prohibited packages: defining anything in `java.*` throws a `SecurityException`. - Defining the same name twice in the same loader throws `LinkageError: attempted duplicate class definition`. Crucially, `defineClass` does **not** load the class's dependencies. Superclasses and interfaces are resolved during linking (so they *are* pulled in eagerly enough to fail early if missing), but ordinary references in method bodies are resolved lazily, on first use, through this same loader. So a loader that succeeds at define time can still blow up later with `NoClassDefFoundError` for a type it cannot reach. ## Details that separate a toy from a real loader **Resources.** Frameworks routinely call `getResourceAsStream("META-INF/services/...")`. If your loader can supply classes but not resources, service loading, logging config and annotation scanners break. Override `findResource` and `findResources`. **Parallel capability.** Since Java 7 loaders can be *parallel capable*: call `ClassLoader.registerAsParallelCapable()` in a static initializer of your subclass, and lock per class name via `getClassLoadingLock(name)` instead of locking the whole loader. Without registration the VM serializes loads on the loader object, which can deadlock in circular-dependency scenarios and hurts startup on many-core machines. **Idempotency.** Two threads may reach `findClass` for the same name. Guard by re-checking `findLoadedClass` inside the per-name lock, or catch the duplicate-definition `LinkageError` and return the already-defined class. **Packages and sealing.** `definePackage` lets you attach implementation/specification titles and versions and seal a package, so a jar's classes cannot be mixed with another source's. Package-private access is enforced against the *runtime package* — package name plus defining loader — so two loaders never share package-private access even for identical package names. **Protection domains.** The `defineClass` overload taking a `ProtectionDomain` records the code source; useful for auditing and for code that reads `getClass().getProtectionDomain().getCodeSource()`. **Naming.** `findClass` receives a *binary* name with dots; classfile paths use slashes. Converting with `name.replace('.', '/') + ".class"` is the standard step, and forgetting it is the classic first bug. ## Where such loaders are used Everything that needs bytes the classpath cannot describe: application servers loading each deployment separately, plugin/extension containers, OSGi-style module systems, agents that decrypt or verify code before running it, ORM/mocking/AOP libraries that synthesize proxies, and hot-reload tooling that re-defines an application under a fresh loader.
- Why is defineClass declared protected and final on java.lang.ClassLoader?It is the trusted bridge between raw bytes and the VM's type system, so it must not be replaceable — final prevents a subclass from faking the definition step. Protected means you can only define classes into a loader you subclass, never into someone else's loader, which preserves each loader's namespace as its own security and isolation boundary.
- What happens if the name you pass to defineClass differs from the name declared inside the class file?The VM rejects the definition with NoClassDefFoundError whose message says the class file has the wrong name. Passing null as the name makes the VM read the name out of the bytes instead, which is the usual choice for generated classes where you don't want to duplicate the naming logic.
- Does defineClass also load the types the class references?No. It parses and defines only this class. Its superclass and interfaces are resolved as part of linking, and everything else — field types, method signatures used in bodies, referenced classes — is resolved lazily on first use, going through this loader. So a define can succeed and the class still fail later with NoClassDefFoundError.
saying these in an interview costs you the question
- Overriding loadClass by default "because that's the load method", silently discarding parent delegation and re-defining JDK types
- Believing defineClass eagerly pulls in every referenced class, so a successful define means the class is fully usable
- Thinking you can define your own java.lang.String by putting it in a custom loader — prohibited packages throw SecurityException
- Calling defineClass repeatedly for the same name in one loader and expecting the newer bytes to win, instead of LinkageError: duplicate class definition
- Supplying classes but not resources, then wondering why ServiceLoader, logging config and annotation scanning see nothing