Annotations in Apex are markers that begin with @ and attach to a class, method, or field. They tell the compiler or runtime to treat that element specially — run it as a test, expose it to a Lightning component, defer it asynchronously, override default visibility for tests, and so on. Most are platform-specific behaviours you cannot get any other way.
The annotations you’ll use most
| Annotation | Applies to | What it does |
|---|---|---|
@isTest | Class or method | Marks code as a test; doesn’t count against the org’s code size limit |
@TestSetup | Method in a @isTest class | Runs once before each test method to seed shared data |
@TestVisible | Field or method | Makes a private/protected member accessible only from test classes |
@future | Static method | Runs asynchronously in a separate transaction |
@AuraEnabled | Method or field | Exposes to Lightning Web Components and Aura; cacheable=true enables client caching |
@InvocableMethod | Static method | Makes the method callable from Flow |
@InvocableVariable | Field | Exposes the field as a Flow input/output parameter |
@RestResource | Class | Maps the class to a REST endpoint URL |
@HttpGet, @HttpPost, @HttpPut, @HttpDelete, @HttpPatch | Static method in @RestResource class | Maps the method to an HTTP verb |
@ReadOnly | Method | Raises the query row limit to 1 million (no DML allowed in scope) |
@RemoteAction | Static method | Exposes to JavaScript Remoting in Visualforce |
@Deprecated | Class or method | Marks managed-package member as deprecated for subscribers |
@SuppressWarnings | Class or method | Suppresses specific PMD warnings (often used with 'PMD.ApexDoc') |
@NamespaceAccessible | Member in a managed package | Exposes to other classes in the same namespace |
@JsonAccess | Class | Restricts JSON.deserialize from creating instances |
@RestResource, @SchedulableContext, @QueueableContext | Various | Platform-specific contracts |
@isTest and @TestSetup
@isTest
public class AccountServiceTest {
@TestSetup
static void seedData() {
insert new List<Account>{
new Account(Name = 'Test 1', Industry = 'Technology'),
new Account(Name = 'Test 2', Industry = 'Finance')
};
}
@isTest
static void getActive_returnsRecords() {
Test.startTest();
List<Account> result = AccountService.getActive(10);
Test.stopTest();
System.assert(!result.isEmpty(), 'Expected non-empty result');
}
}
@isTest classes are not counted against the 6 MB Apex code size limit per org — that’s why test code is segregated, and why production deploys require 75% coverage from these @isTest classes specifically.
@TestVisible
You want methods and fields to be private for normal callers but reachable from tests. @TestVisible makes that possible without weakening production encapsulation.
public class OrderProcessor {
@TestVisible private Boolean dryRun = false;
@TestVisible private void rollback() { /* ... */ }
}
@isTest
static void testRollback() {
OrderProcessor p = new OrderProcessor();
p.dryRun = true; // OK only because of @TestVisible
p.rollback();
}
@future
The original async-Apex annotation. The method runs in a separate transaction, after the current one commits.
public class IntegrationCallouts {
@future(callout=true)
public static void notifyExternalSystem(Set<Id> accountIds) {
// Runs async, with HTTP callout permission, in its own governor limit budget
}
}
Limitations: parameters must be primitives or collections of primitives (no sObjects), cannot be called from another @future, no return value. Modern code mostly prefers Queueable for the same reasons plus richer parameter types.
@AuraEnabled
The bridge between Apex and Lightning. Methods marked @AuraEnabled are callable from @wire and imperative-apex in LWC. Fields marked @AuraEnabled are serialized to JSON when the runtime sends data to the client.
public with sharing class AccountController {
@AuraEnabled(cacheable=true)
public static List<Account> getRecentAccounts() {
return [SELECT Id, Name FROM Account ORDER BY CreatedDate DESC LIMIT 10];
}
}
cacheable=true opts the result into the Lightning client cache — required for @wire. Methods that perform DML cannot be cacheable.
@InvocableMethod
The Flow integration point. Annotate a method as @InvocableMethod and it appears as an Apex Action in Flow Builder.
public class FlowActions {
@InvocableMethod(label='Recompute Total' description='Recomputes order total')
public static List<Decimal> recompute(List<Id> orderIds) {
List<Decimal> totals = new List<Decimal>();
// ... compute ...
return totals;
}
}
Inputs and outputs must be lists (Flow calls in bulk). Each input is a list, each output is a list — Flow correlates by position.
@RestResource
Custom REST endpoints. The annotation maps the class to a URL pattern; method annotations map HTTP verbs.
@RestResource(urlMapping='/orders/*')
global with sharing class OrderResource {
@HttpGet
global static Order__c get() {
String id = RestContext.request.requestURI.substringAfterLast('/');
return [SELECT Id, Name FROM Order__c WHERE Id = :id LIMIT 1];
}
@HttpPost
global static Id create(String name) {
Order__c o = new Order__c(Name = name);
insert o;
return o.Id;
}
}
Class and methods must be global. The URL is /services/apexrest/orders/<id>.
@ReadOnly
Raises the synchronous query row limit from 50,000 to 1,000,000. Use for very large read-only operations, like generating a one-time report. The trade-off: no DML inside the annotated method’s call stack.
What interviewers are really looking for
Naming a handful of annotations covers the basic answer. The interview-worthy answer groups them by purpose: testing (@isTest, @TestSetup, @TestVisible), async (@future), UI integration (@AuraEnabled), Flow (@InvocableMethod), API (@RestResource + HTTP verbs), and packaging (@Deprecated, @NamespaceAccessible). If you can also name the constraints — @future parameter limits, @AuraEnabled(cacheable=true) no-DML rule, @RestResource global requirement — you’ve shown the depth that comes from shipping production code.
Verified against: Apex Developer Guide — Annotations. Last reviewed 2026-05-17 for Spring ‘26 release.