A circular dependency in class initialization occurs when initializing one class or enum directly or indirectly needs another type that leads back to the first. The cycle can expose fields before their initializers finish, producing surprising default values such as null rather than the intended value. section 3.9.3 section 3.9.4
Static fields can observe incomplete initialization
If A initializes B before assigning A.WELCOME, and B initializes MSG from A.WELCOME, B may read WELCOME as null. Field declaration order matters; a primitive or String compile-time constant is initialized during class loading instead, before ordinary class initialization begins. section 3.9.3
The more serious failure appears under concurrency. The JVM locks a class while its initializer runs so that it is not executed twice. Two threads that begin opposite sides of a cycle can each hold one class-initialization lock and wait for the other, deadlocking the application. Later accesses to either class can also block. section 3.9.3
Treat initialization-time cycles as defects, especially when types may be reached concurrently:
- Do not make static field initializers cross a dependency cycle; move or restructure the dependency so a type is not consumed before initialization completes. section 3.9.3
- Inspect a stuck process with jstack. Frames in <clinit> identify class initializers; Java 17 and newer can additionally report the class-initialization monitor being awaited. section 3.9.3
- Do not assume enum constants already exist while their enum initializes. An enum constructor can observe a referenced constant from another enum as null. section 3.9.4
Mutually referring enums
Pet.DOG can receive Voice.BARK successfully while Voice.BARK.owner becomes null, because Pet.DOG was not ready when Voice.BARK was constructed. Where a back-reference is necessary, declare it nonfinal and assign it after constructor invocation. section 3.9.4
Enum initialization also makes an enum temporarily incomplete: values() does not work until every constant exists. In Java 20 and older, switching on this inside an enum constructor may implicitly invoke values() and fail with a NullPointerException in the class initializer. section 3.9.4
enum Pet {
DOG(Voice.BARK),
CAT(Voice.MEOW);
final Voice voice;
Pet(Voice voice) { this.voice = voice; }
}
enum Voice {
BARK(Pet.DOG),
MEOW(Pet.CAT);
final Pet owner;
Voice(Pet owner) { this.owner = owner; }
}
This pair of enums isolates the chicken-and-egg cycle: each constant passes a constant from the other enum into its constructor. section 3.9.4




