The Apex String class shows up in every nontrivial piece of Salesforce code. Knowing the right method saves you 15 minutes of stack-overflow searching plus the time to debug the inevitable null-pointer exception. This is the reference I keep open in a tab.
Strings are immutable
Before the methods: every Apex String operation returns a new String. The original is never mutated. So:
String name = 'Acme';
name.toUpperCase(); // Result discarded — name is still 'Acme'
name = name.toUpperCase(); // Now name is 'ACME'
This is identical to Java/JavaScript and unlike most C-family languages. It also means string-building in a loop allocates new objects on every iteration — use String.join instead (covered below).
Null-safety: the most important pattern
The single most useful String method is String.isBlank():
String x = null;
String.isBlank(x); // true
String.isBlank(''); // true
String.isBlank(' '); // true (whitespace counts)
String.isBlank('x'); // false
String.isNotBlank(x); // false
Use it instead of x == null || x.trim() == ''. It's the safest default for "do I have a real value here?"
The 30 methods you'll actually use
Substring search
'Hello World'.contains('World'); // true
'Hello World'.containsIgnoreCase('world'); // true
'Hello World'.startsWith('Hello'); // true
'Hello World'.endsWith('World'); // true
'Hello World'.indexOf('World'); // 6 (position; -1 if not found)
'Hello World'.lastIndexOf('o'); // 7
Case manipulation
'Hello'.toUpperCase(); // 'HELLO'
'Hello'.toLowerCase(); // 'hello'
'Hello'.capitalize(); // 'Hello' (no-op if already capitalized)
'hello world'.equalsIgnoreCase('HELLO WORLD'); // true
Trimming and padding
' hi '.trim(); // 'hi'
' hi '.stripLeft(); // 'hi '
' hi '.stripRight(); // ' hi'
'5'.leftPad(3, '0'); // '005'
'5'.rightPad(3, '*'); // '5**'
Splitting and joining
'a,b,c'.split(','); // ['a', 'b', 'c']
'a-b_c'.split('[-_]'); // ['a', 'b', 'c'] (regex)
String.join(new List<String>{'a', 'b', 'c'}, ','); // 'a,b,c'
String.join(new List<Integer>{1, 2, 3}, '-'); // '1-2-3' (auto-converts)
String.join accepts any List — auto-converts Integer, ID, etc. Best way to build a comma-separated list inside a loop:
// BAD — allocates a new String every iteration
String csv = '';
for (Account a : accounts) csv += a.Name + ',';
// GOOD
List<String> names = new List<String>();
for (Account a : accounts) names.add(a.Name);
String csv = String.join(names, ',');
Replacement
'hello'.replace('l', 'L'); // 'heLLo' (literal)
'2024-01-15'.replaceAll('-', '/'); // '2024/01/15' (regex)
'hello'.replaceFirst('l', 'L'); // 'heLlo'
Length and substring
'Hello'.length(); // 5
'Hello World'.substring(6); // 'World'
'Hello World'.substring(0, 5); // 'Hello' (end-exclusive)
'Hello World'.left(5); // 'Hello'
'Hello World'.right(5); // 'World'
'Hello World'.mid(6, 5); // 'World' (start, length)
Conversion (the static side)
String.valueOf(42); // '42'
String.valueOf(Date.today()); // '2026-05-06 00:00:00'
String.valueOf(myAccount); // 'Account:{Id=001..., Name=Acme}'
Integer.valueOf('42'); // 42 (parsing the other way)
String.valueOf accepts any type and returns a string representation — useful for logging or debugging.
Templates
String.format('Hello {0}, welcome to {1}', new List<Object>{'Alice', 'Salesforce'});
// 'Hello Alice, welcome to Salesforce'
String.format('Order #{0} amount {1}', new List<Object>{12345, 99.99});
// 'Order #12345 amount 99.99'
Encoding
EncodingUtil.urlEncode('hello world', 'UTF-8'); // 'hello+world'
EncodingUtil.urlDecode('hello%20world', 'UTF-8'); // 'hello world'
EncodingUtil.base64Encode(Blob.valueOf('hi')); // 'aGk='
SOQL injection protection (critical)
When building dynamic SOQL with user input, ALWAYS escape:
// SAFE — escape user input
String safeName = String.escapeSingleQuotes(userInput);
String query = 'SELECT Id FROM Account WHERE Name LIKE \'%' + safeName + '%\'';
List<Account> results = Database.query(query);
// SAFER — bind variables when possible (no manual escape needed)
List<Account> results = [SELECT Id FROM Account WHERE Name LIKE :('%' + userInput + '%')];
escapeSingleQuotes prefixes every ' with a \. Without it, a malicious input like ' OR Name LIKE '% lets an attacker exfiltrate data. This is a class of bug audited in every Salesforce security review — get it right.
Common mistakes
if (myStr == '')after a SOQL. Salesforce never returns empty strings — fields are either null or have content. UseString.isBlankto handle both cases.- String concatenation in a loop. Slow and allocates many garbage objects. Use
String.join. - Forgetting
escapeSingleQuoteson dynamic SOQL. Every code review's #1 finding. - Using
.equals('foo')on a possibly-null reference. Throws NPE. Use'foo'.equals(myStr)orString.isNotBlank(myStr) && myStr.equals('foo'). .toString()on a possibly-null SObject field. Same NPE. UseString.valueOf(rec.field)— it's null-safe and returns 'null' as a literal.
Quick cheat sheet
| Need | Method |
|---|---|
| Null-or-empty check | String.isBlank(s) |
| Split CSV | s.split(',') |
| Build CSV | String.join(list, ',') |
| Substring search | s.contains('x') |
| Replace pattern | s.replaceAll('regex', 'new') |
| Format template | String.format('hi {0}', new List<Object>{name}) |
| Convert anything to String | String.valueOf(x) |
| Escape for dynamic SOQL | String.escapeSingleQuotes(input) |
| Case-insensitive equals | s.equalsIgnoreCase(other) |
| Trim whitespace | s.trim() |
The Apex String class is one of those topics where 90% of real code uses 10% of the methods. Master the ones above and you'll handle every text manipulation case I've seen in 12 years of Salesforce projects.
Leave a Comment