CIRCULAR_DEPENDENCY: Apex class A depends on B, which depends on A
Two Apex classes refer to each other in a way that the platform can't compile in isolation. The deploy fails because there's no order in which both classes can compile cleanly. The fix is breaking the cycle with an interface, an abstract class, or a third class that holds the shared logic.
Also seen asCIRCULAR_DEPENDENCY·circular dependency apex·depends on which depends on·Apex class circular reference
Apex compiles classes in dependency order. If A references B and B references A, the compiler can't pick a starting point. This is "circular dependency" and the deploy refuses.
The shape of the bug
// AccountService.cls
public class AccountService {
public static void doStuff(Account a) {
ContactService.helpWith(a);
}
}
// ContactService.cls
public class ContactService {
public static void helpWith(Account a) {
AccountService.helperFor(a); // 💥 cycle
}
}
Apex needs to compile AccountService to know what methods exist, but compiling AccountService needs ContactService, which needs AccountService, which...
Fix 1: extract the shared logic to a third class
// SharedLogic.cls
public class SharedLogic {
public static void doSharedThing(Account a) { ... }
}
// AccountService.cls
public class AccountService {
public static void doStuff(Account a) { SharedLogic.doSharedThing(a); }
}
// ContactService.cls
public class ContactService {
public static void helpWith(Account a) { SharedLogic.doSharedThing(a); }
}
AccountService and ContactService both depend on SharedLogic, but neither depends on the other. No cycle.
Fix 2: introduce an interface
If the relationship is genuinely bidirectional (AccountService and ContactService both need to call into each other), an interface breaks the compile-time cycle:
public interface IAccountHelper {
void doSomething(Account a);
}
public class ContactService {
private IAccountHelper helper;
public ContactService(IAccountHelper h) { this.helper = h; }
public void helpWith(Account a) {
helper.doSomething(a); // depends on the interface, not AccountService
}
}
ContactService now depends on IAccountHelper (a stable interface), and AccountService implements IAccountHelper. Neither class hard-references the other.
Fix 3: lazy resolution
If you can't refactor, dynamic dispatch via Type.forName breaks the static cycle:
public class ContactService {
public static void helpWith(Account a) {
Type accountSvc = Type.forName('AccountService');
// use reflection-style dispatch
}
}
This is ugly and slow; reserve for genuinely-stuck legacy code.
Diagnose with the deploy log
Salesforce's deploy log usually names both classes in the cycle:
Class AccountService cannot be saved because of an unresolved dependency:
ContactService -> AccountService -> ContactService
Read the chain — that's the cycle to break.
A common cause: "manager" classes
A class named XxxManager that touches many other domains often becomes a hub of cycles. Refactor by splitting XxxManager into focused single-responsibility classes; the cycles often vanish.
A subtle case: Trigger ↔ Apex Class
A trigger calls an Apex class. The Apex class queries the same SObject that the trigger is on. Then a workflow or another trigger updates the SObject... not exactly a circular dependency in the compile sense, but a runtime feedback loop. Use a recursion guard (see Maximum stack depth) for the runtime version.
