When you build custom Visualforce apps with interactive forms, search filters, or data tables, maintaining information across server requests is crucial. Because standard web traffic doesn't naturally "remember" things, Salesforce uses View State to track your Apex controller variables and component details. If you aren't careful with how much data you store, your page weight will bloat rapidly, leading to slow performance and crashed pages.
📌 Key Points
- The absolute maximum size for View State in Salesforce is 170 KB.
- Use the
transientkeyword in Apex to block read-only data from taking up View State space. - Only put inputs and buttons that actually submit data inside
<apex:form>tags. - You can inspect exactly what is taking up space using the View State Inspector in Development Mode.
- For highly complex, data-heavy pages, modernizing with Lightning Web Components (LWC) eliminates View State limits entirely.
🔍 1. Understanding the View State Lifecycle
Every time a user clicks a button or triggers a page action, the View State is bundled up and sent to the server so Salesforce knows exactly where the user left off. It holds three main things:
- Apex Controller Variables: Any public or private member variables in your Apex code that aren't marked as transient.
- Component State: The values and structural hierarchy of Visualforce tags that live inside an
<apex:form>. - Standard Controller Data: Field values and record IDs managed directly by standard controllers.
- Hard Limit: 170 KB per Visualforce request.
- Optimization Tool: The
transientApex keyword. - Pagination Pattern:
ApexPages.StandardSetControllerfor server-side chunking. - Diagnostic Utility: View State Inspector tab in the Developer Console / Footer.
🛠️ 2. Proven Strategies to Reduce View State Size
To keep your pages snappy and well below the 170 KB limit, use these essential architectural patterns:
- Use the
transientKeyword: Addtransientto variables or lists that only display data on the screen but don't need to be remembered when the user submits the form (like search results). - Minimize SOQL Fields: Never query an entire object (e.g., all 500 fields on an Account) if you only need the Name and Industry. Query exactly what you plan to show.
- Scope Your Forms Carefully: Don't wrap your entire Visualforce page in one giant
<apex:form>tag. Only wrap the specific input fields and buttons that send data to the server. - Server-Side Pagination: Instead of loading thousands of records into a single list, use the
StandardSetControllerto hold records in server memory and pass only a small chunk (like 20 records) to the page at a time. - Use Lightweight Wrapper Classes: When dealing with data tables featuring checkboxes (like "select all"), wrap only the Record ID and a Boolean flag into a custom Apex class instead of passing the entire Salesforce object.
💻 3. Implementing View State Optimization in Code
This example shows how user inputs are safely bound to the controller, while a large, read-only list of records is optimized using the transient keyword.
ViewStateDemo.page)Notice how the read-only table is kept separate from the input form where possible.
<apex:page controller="ViewStateController" tabStyle="Account">
<apex:form>
<apex:pageMessages />
<!-- This section submits data, so it needs to track state -->
<apex:pageBlock title="Account Quick Creator">
<apex:pageBlockSection columns="1">
<apex:inputText value="{!accountName}" label="Account Name" required="true" />
<apex:inputText value="{!accountIndustry}" label="Industry" />
</apex:pageBlockSection>
<apex:pageBlockButtons location="bottom">
<apex:commandButton action="{!saveAccount}" value="Save Account" />
</apex:pageBlockButtons>
</apex:pageBlock>
<!-- This section only displays data. -->
<apex:pageBlock title="Recent Accounts (Read-Only via Transient)">
<apex:pageBlockTable value="{!recentAccounts}" var="acc">
<apex:column value="{!acc.Name}" />
<apex:column value="{!acc.Industry}" />
</apex:pageBlockTable>
</apex:pageBlock>
</apex:form>
</apex:page>
ViewStateController.cls)We apply the
transient keyword to the list of records so they aren't serialized into the page's hidden memory.
public with sharing class ViewStateController {
// Maintained in View State to preserve user input across clicks
public String accountName { get; set; }
public String accountIndustry { get; set; }
// Marked TRANSIENT: Excluded from View State serialization (saves massive memory)
public transient List<Account> recentAccounts { get; set; }
public List<Account> getRecentAccounts() {
if (recentAccounts == null) {
recentAccounts = [
SELECT Id, Name, Industry
FROM Account
WITH USER_MODE
ORDER BY CreatedDate DESC
LIMIT 100
];
}
return recentAccounts;
}
public PageReference saveAccount() {
if (String.isNotBlank(accountName)) {
Account newAcc = new Account(
Name = accountName.trim(),
Industry = accountIndustry
);
insert as user newAcc;
ApexPages.addMessage(new ApexPages.Message(
ApexPages.Severity.CONFIRM,
'Account created successfully!'
));
// Clear input fields
accountName = '';
accountIndustry = '';
}
return null; // Refresh page state
}
}
🐛 4. How to Inspect and Debug View State Size
If you hit the 170 KB limit, you need to find out exactly which variable is hoarding memory.
- In Salesforce Setup, search for Users and click Edit next to your user profile.
- Check the boxes for both Development Mode and Show View State tab in Development Mode.
- Open your Visualforce page in a browser. Expand the developer footer at the bottom of your screen.
- Click the View State tab to examine a folder tree showing exactly which components and variables are consuming the most memory bytes.
⚠️️ 5. Common Traps & Developer Mistakes
Declaring
public List<Opportunity> oppList { get; set; } without the transient keyword and querying 1,000 records will store all those field values, object schemas, and internal relationship data directly inside the View State payload. This will instantly push you past the 170 KB threshold.
Because
transient variables are ignored by View State, their values reset to null when a user clicks a button. If your action method needs that data, you must re-query the records or pass specific row IDs back to the server using <apex:param>.
Comments
Questions, corrections and your own experience are all welcome.
Post a Comment