Look at the code below where the main() loads a class whose name is specified by first argument. The second argument tells whether the class should be resolved or not. It also contains 5 test classes: A1, A2 and their dependencies: B, B1, B2. We will try to identify how early Bs are needed if I want to use As.

class Main {
  public static void main(String... args) throws Exception {
   Class c = Class.forName(args[0], Boolean.valueOf(args[1]), Main.class.getClassLoader());

abstract class B { public abstract void bar(); }

class B1 extends B { public void bar(){} }

class B2 extends B { public void bar(){} }

class A1 {
  void foo(boolean cond) {
   B b = cond ? new B1() : new B2();

class A2 {
  void foo(boolean cond) {
   B1 b = new B1();

After compilation, we shall delete all the B classes so that we can identify when B1.class and B2.class are required while loading A1 or A2.

$ javac Main.java

$ rm B[12].class

$ ls
A1.class  A2.class  A.class  B.class  Main.class  Main.java

$ java Main A1 false

$ java Main A2 false

$ java Main A2 true

$ java Main A1 true
Exception in thread "main" java.lang.NoClassDefFoundError: B1
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:247)
    at Main.main(Main.java:3)
Caused by: java.lang.ClassNotFoundException: B1
    at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
    ... 3 more

$ java -Xverify:none Main A1 true

This tells us that the byte code verification of A1.class caused some of the referencing classes (B1 in this case) to be loaded. Why did it then not fail when we tried to load A2? I turned to my long time friend and colleague Sundarajan, who works in Java SE group, to discuss this. He explained that since LHS and RHS used different types in the following line:

  B b = cond ? new B1() : new B2();

so, the byte code verifier had to load the types involved to ensure assignment compatibility.

Most developers don't care about this, but a few that do typically are writing some kind of lazily initialized systems where they probably don't want some dependencies to be loaded until the dependent service is actually invoked. Of course, frameworks like OSGi helps a long way in developing such systems. Even those developers have to pay attention to such details to get the desired lazy behavior. Some may say that reflection could avoid the problem, but that's not the point being made here.

I understand this behavior of bytecode verifier is probably implementation specific and can differ across implementations including implementation versions. For the record, my Java version is 1.6.0_21.

Actually I came across this in a much more coimplex scnerio while trying to investigate why certain modules in GlassFish are getting loaded at startup time contrary to my expectation, but basic test case remains same.