Apex has four access modifiers — private, protected, public, and global — and they work like Java’s, with one Salesforce-specific addition (global) for managed packages. Pick the most restrictive modifier that still lets the caller see what it needs. Wide-open access is the most common cause of failed AppExchange security reviews.
The four levels
| Modifier | Visibility | Use it for |
|---|---|---|
private | Only inside the same class | Internal helpers, fields you don’t want callers to touch |
protected | Same class + subclasses | Members exposed to your inheritance hierarchy but not the world |
public | Anywhere in the same namespace (org) | Default for application code inside one org |
global | All namespaces, including subscriber orgs | Managed package APIs, @RestResource, @InvocableMethod |
private — the default for fields
Use private on every field unless callers need to read or write it. Pair it with public getters/setters when controlled exposure is required.
public class OrderService {
private static final Decimal MAX_DISCOUNT = 0.30;
private List<OrderItem__c> items;
public OrderService(List<OrderItem__c> items) {
this.items = items;
}
}
Inside the class, private members are accessible. From any caller outside the class, they’re invisible — the compiler rejects the reference.
private is also the access for inner classes that should be hidden inside the outer class:
public class OuterService {
private class InternalState {
Integer counter;
DateTime lastRun;
}
}
protected — for inheritance chains
protected is the rarely-used middle level. The member is visible to the declaring class and any class that extends it.
public virtual class BaseHandler {
protected void log(String msg) {
System.debug(msg);
}
}
public class AccountHandler extends BaseHandler {
public void run() {
log('Running'); // OK — inherited access
}
}
Outside the inheritance tree, protected behaves like private. Use it only when you’re deliberately designing a virtual/abstract base class with hooks for subclasses.
public — the application default
public is the right answer for most application classes and methods used across an org. The member is visible everywhere in the same namespace.
public with sharing class AccountService {
public static List<Account> getActiveAccounts() {
return [SELECT Id, Name FROM Account WHERE Active__c = true];
}
}
Important subtlety: if your code is in an unmanaged package (or no package at all), the “namespace” is just the org. If it’s in a managed package, public is invisible to subscribers — they cannot call it from their own Apex. For cross-namespace calls, you need global.
global — the managed-package and integration entry point
global exposes the member across namespaces. You need it for:
- Managed package APIs — methods your AppExchange subscribers call from their own Apex.
@RestResourceclasses — must beglobal, plus the methods annotated with@HttpGet/@HttpPost/etc.@WebServiceSOAP endpoints — must beglobal.@InvocableMethodactions in managed packages so subscriber Flows can call them.Schedulable,Queueable,Batchableclasses in managed packages so subscribers can schedule or enqueue them.
@RestResource(urlMapping='/orders/*')
global with sharing class OrderResource {
@HttpGet
global static Order__c getOrder() {
String orderId = RestContext.request.requestURI.substringAfterLast('/');
return [SELECT Id, Name, Status__c FROM Order__c WHERE Id = :orderId LIMIT 1];
}
}
Warning: Once you ship
globalin a managed package, you cannot remove or change its signature. The platform locks it as an API contract to protect subscriber orgs. Useglobalonly when you genuinely need the cross-namespace exposure.
Common pitfalls
- Defaulting to
publicon fields. Useprivateand expose what callers need. - Using
globalon application classes. If the class only needs to be called within your org,publicis enough. - Forgetting
globalon REST methods.@RestResourcewon’t exposepublicmethods. - Confusing class access with method access. A
publicclass can haveprivatemethods — the class controls whether you can see the class at all; method modifiers control which members.
What interviewers are really looking for
The naming check (private/protected/public/global) is trivial. The signal interviewers want is whether you understand global as the cross-namespace boundary, not just “the widest one.” If you can explain that REST and SOAP endpoints require global, that managed packages need global for any callable surface, and that you should default to private for fields — you’ve shown the encapsulation instinct senior teams hire for.
Verified against: Apex Developer Guide — Access Modifiers. Last reviewed 2026-05-17 for Spring ‘26 release.