Salesforce Dictionary - Free Salesforce GlossarySalesforce Dictionary
Full Visualforce Controller entry
How-to guide

Writing a maintainable Visualforce Controller

Writing a Visualforce Controller well is mostly about picking the right pattern and following the lifecycle conventions. The four-step routine covers: pick Standard Controller, Custom Controller, or Controller Extension based on the use case; declare sharing behavior explicitly; implement action methods with the right signature; and test the controller in Apex unit tests. Each step is a small decision; the cumulative effect is the difference between a maintainable controller and a brittle one that future developers struggle to inherit.

By Dipojjal Chakrabarti · Founder & Editor, Salesforce DictionaryLast updated May 19, 2026

Writing a Visualforce Controller well is mostly about picking the right pattern and following the lifecycle conventions. The four-step routine covers: pick Standard Controller, Custom Controller, or Controller Extension based on the use case; declare sharing behavior explicitly; implement action methods with the right signature; and test the controller in Apex unit tests. Each step is a small decision; the cumulative effect is the difference between a maintainable controller and a brittle one that future developers struggle to inherit.

  1. Pick the controller pattern based on the use case

    For a page that mostly displays and edits standard object behavior (a custom Account detail page, a custom Opportunity edit page), pick the Standard Controller for the object. Declare the controller on the apex:page tag: standardController=Account. For a page that implements logic outside the standard object lifecycle (a multi-record edit, a custom wizard, a complex search), write a Custom Controller. Declare it on the apex:page tag: controller=MyCustomController. For a page that needs the standard behavior plus a small extension, write a Controller Extension and declare both: standardController=Account extensions=MyExtension. Document the choice in a class header comment.

  2. Declare sharing behavior explicitly

    Every controller class should explicitly declare its sharing behavior with one of three keywords: with sharing, without sharing, or inherited sharing. with sharing respects the running user sharing rules and is the safer default for most controllers. without sharing bypasses sharing and is appropriate only when the controller intentionally runs administrative logic. inherited sharing inherits from the calling class and is useful for utility classes. Default behavior (no keyword) is without sharing, which surprises developers expecting safer behavior. Always specify the keyword explicitly; reviewers should flag any controller class that omits it.

  3. Implement action methods with the right signature

    Action methods must be public or global, take no parameters, and return a PageReference (often null to stay on the current page) or void. Action methods called from Visualforce buttons or links have to match this signature; otherwise the page does not call them. Inside the action method, validate the inputs received through setters, perform any DML or callouts, and decide what page to navigate to next (return a new PageReference for navigation, return null to stay). Handle exceptions explicitly with try-catch; uncaught exceptions surface as ugly platform error messages rather than the controlled error display the page can render.

  4. Test the controller in Apex unit tests

    Write @isTest methods that exercise the controller directly. For Standard Controller-based pages, wrap a test sObject in ApexPages.StandardController and pass it to the Controller Extension constructor. For Custom Controllers, instantiate the class with test data. Call the controller action methods and assert the resulting state of the controller and any DML side effects. Set ApexPages.currentPage().getParameters() to simulate URL parameters. Aim for 75 percent coverage on the controller; without it, the controller cannot deploy to production. Add the tests to your CI pipeline so changes to the controller logic get validated automatically.

Gotchas
  • The default sharing behavior for a controller without an explicit keyword is without sharing. Always specify the keyword; the default is rarely what you want.
  • Action methods called from Visualforce must take no parameters. Methods that take parameters cannot be invoked from commandButton or commandLink; refactor to read inputs through setters instead.
  • Getters can be called many times during page rendering. Expensive computation in getters slows down the page; cache the result in an instance variable and have the getter return the cached value.
  • Standard Controllers vary in capability per object. Some objects do not support all Standard Controller methods; check the documentation for the specific object before relying on standardized behavior.
  • Visualforce is in maintenance mode. New custom UI work should use Lightning Web Components with @AuraEnabled Apex methods, not new Visualforce Controllers.

See the full Visualforce Controller entry

Visualforce Controller includes the definition, worked example, deep dive, related terms, and a quiz.