A list of funny and tricky Apex examples
Inspired bywtfjs
- ✍🏻 Notation
- 👀Examples
- When a boolean is not a boolean
- String compare is case-insensitive (except when it's not)
- Object
equals
override - Shadowing System (global) classes
- "Phantom" Inner Class Type Equivalency
- List
contains
&indexOf
is broken final
parameters "exist", but can be reassigned- Fulfilling Interface Contracts with Static Methods
- Exceptions are "exceptional"
System
can have ambiguous return types- Odd List Initialization bug
- Local Scope Leak
- Broken type inference for
Set<>
- String.Format with single quotes
- Line continuation breaks for static method
- Fun with Hashcodes
- JSON Serialization
- Generics (parameterized interfaces) exist, but you can't use them
- Polymorphic Primitives
- Invalid HTTP method: PATCH
- 🔧 Since Fixed
// ->
is used to show the result of an expression. For example:
1+1;// -> 2
// >
means the result ofSystem.debug
or another output. For example:
System.debug('hello, world!');// > hello, world!
//!
Means a runtime exception was thrown:
Decimald=1/0;//! Divide by 0
// ~
Code fails to compile:
Decimald='foo';// ~ Illegal assignment from String to Decimal
//
is just a comment used for explanations. Example:
// Assigning a function to foo constant
Foofoo=newFoo();
Booleanb;
if(b!=true)system.debug('b is not true');//> b is not true
if(b!=false)system.debug('b is not false');//> b is not false
if(b){}//!Attempt to de-reference a null object
SeeAdvanced Apex Programming in Salesforcefor explanation.
Stringx='Abc';
Stringy='abc';
System.assert(x==y);
System.assertEquals(x,y);//! Expected: Abc, Actual: abc
Seeexplanation on StackExchange
Try tooverride
theequals
method on any class and you'll be greeted with a very unexpected compile error:@Override specified for non-overriding method
.
However, just remove theoverride
keyword and it compiles!
At first glance it might seem like this works, but it is in fact very broken:(
publicclassMyClass{
publicBooleanequals(Objectother) {
System.debug('Called my equals');
returnfalse;
}
}
MyClassm=newMyClass();
Objecto=newMyClass();
m.equals('a');// > 'Called my equals'
o.equals('a');// System.debug is never called:(
Source:Aidan Harding
Nothing prevents you from recreating a class with the same name of one that exists in theSystem
(default) namespace.
publicclassDatabase{
publicstaticList<sObject>query(Stringqry) {
System.debug(qry);
returnnull;
}
}
RunningDatabase.query('foo')
will call our new class (essentially overriding theDatabase methods)!?
The same principle also applies to standard SObjects:
publicclassAccount{ }
Accountacc=newAccount();
acc.AccountNumber='123';//! Variable does not exist: AccountNumber
Source:Daniel Ballinger
publicclassIExist{}
System.assertEquals(IExist.class,IExist.IDont.Class);// -> passes
Source:Kevin Jones
The easiest way to test this is by writing the output to an object field.
- Replace
accId
with a dummy account - Open a dev console and set the log levels to
Apex=Finest
- Run the following code
IdaccId='0012F00000YIc48QAD';
Accountacc=newAccount(Id=accId);
List<Id>haystack=newList<Id>();
haystack.add('0012F00000YIc46QAD');
haystack.add(accId);
haystack.add('0012F00000YIc49QAD');
Stringdebug='Index: '+haystack.indexOf(needle) +' Contains: '+haystack.contains(needle);
acc.AccountNumber=debug;
updateacc;
- Open the account. You should see the
AccountNumber
equalsIndex: 1 Contains: true
- Set log levels to
Apex=None
- Run the code again
- Refresh the account.
AccountNumber
will now equalIndex: -1 Contains: false
Apparently this is aknown issue and it has been fixed.This test shows otherwise...
Behind the scenes Salesforce seems to always convert 15 character Id's to 18.
Equivalency works as expected in most cases:
Ida15='0012F00000YIc48';
Ida18='0012F00000YIc48QAD';
System.assert(a15==a18);
However, for the Listcontains
&indexOf
methods, it doesn't:
List<Id>idList=newList<Id>{
'0012F00000YIc46',
'0012F00000YIc48',
'0012F00000YIc49'
};
System.debug(idList);//-> (0012F00000YIc46QAD, 0012F00000YIc48QAD, 0012F00000YIc49QAD)
System.debug(idList.indexOf('0012F00000YIc48'));// > -1
System.debug(idList.contains('0012F00000YIc48'));// > false
You can avoid this by first assigning the value you are checking to anId
type.
You won't find a reference to it in the docs, but the compiler does apparently allowfinal
parameters.However, it doesn't actually to prevent reassignment of such parameters:
publicvoidwithFinalParam(finalStringiAmFinal){
iAmFinal='just kidding';//compiles
}
Source:Kevin Jones
This shouldn't work but it does. Apparently also works with batch.
publicclassStaticsAreCoolimplementsSchedulable{
publicstaticvoidexecute(SchedulableContextsc){
}
}
Source:Kevin Jones
In their naming conventions:
publicclassOhGodBeesextendsException{}
// ~ Classes extending Exception must have a name ending in 'Exception'
and their Constructors:
publicclassBeesExceptionextendsException{
publicBeesException(){}
}
// ~ System exception constructor already defined: <Constructor>()
For explanation and further interesting observations,see Chris Peterson's blog post.
Database.query
is one of many cases where the SalesforceSystem
namespace doesn't play by its own rules. It can return either aList<SObject>
or a singleSObject
.No casting required.
Foo__cfoo=Database.Query('SELECT Id FROM Foo__c');
List<Foo__c>foos=Database.Query('SELECT Id FROM Foo__c');
Try writing your own method to do this and you'll get an error:
Method already defined: query SObject Database.query(String) from the type Database (7:27)
You can overload arguments, but notreturn
type.
The initialization syntax forList<T>
expectsT... args
.
So obviously, if you passed aList<T>
into it, you will get compile error:
List<Task>{newList<Task>()};// ~ Initial expression is of incorrect type, expected: Task but was: List<Task>
Except, if List comes fromnew Map<Id,T>().values()
...
The following code compiles without issue!
newList<Task>{newMap<Id,Task>().values()};
To add to the perplexity, when executed you will receive the following runtime error:
System.QueryException: List has no rows for assignment to SObject
Source: Matt Bingham
If you write an If/Else without braces, symbols scoped in the "if" seem to leak into the "else":
if(false)
Stringa='Never going to happen.';
else
a='I should not compile';
Worth noting that Java won't even allow you to declare a block scoped variable inside a "braceless IF" as it can never be referenced elsewhere.
Source:Kevin Jones
Let's take a look at the standardSet
class...
It can be iterated in a foreach loop:
Set<String>mySet=newSet<String>{'a','b'};
for(Strings:mySet){}
But, according to Salesforce (compiler & runtime), it does not actually implement theIterable
interface:
String.join(mySet,',');// ~ "Method does not exist or incorrect signature: void join(Set<String>, String)..."
// Just to make sure, lets check at runtime..
System.debug(mySetinstanceofIterable<String>);// > false
Except... It actually does:
String.join((Iterable<String>)mySet,',');// this works!?
Stringwho='World';
Stringmsg=String.format(
'Hello, \'{0}\'',
newList<String>{who}
);
System.assert(msg.contains(who));//! assertion failed
Unexpectedly,msg
is set toHello, {0}
🤔
To get this to work properly you must escapetwosingle quotes:
Stringwho='World';
Stringmsg=String.format(
'Hello, \'\'{0}\'\'',
newList<String>{who}
);
System.assert(msg.contains(who));// -> passes
Explanation by Daniel Ballinger
In apex, all statements must be terminated by a;
.This allows statements to span multiple lines:
Ordero=newOrderBuilder()
.addLineItem('foo',5)
.addLineItem('bar',10)
.setDiscount(0.5)
.toOrder();
However, for some reason if the method is static, apex doesn't let it span a newline:
Ordero=OrderBuilder
.newInstance()// ~ Variable does not exist: OrderBuilder
.addLineItem('foo',5)
.addLineItem('bar',10)
.setDiscount(0.5)
.toOrder();
Source:Leo Alves
- There's no way to control automatic serialization of object properties (like
[JsonProperty(PropertyName = "FooBar" )]
in C#) - There are reserved keywords that you can't use as property names.
Meaning the following cannot be parsed or generated usingJSON.deserialize
orJSON.serialize
:
{
"msg":"hello dingus",
"from":"Dr. Dingus"
}
Once upon a time, generics were actually part of Apex. However, they have since been removed (with the exception of system classes (List<>
,Batchable<>
,etc).
Why would you want generics when your OS has perfectly good Copy & Paste functionality built right into it?
Objectx=42;
System.debug(xinstanceOfInteger);//> true
System.debug(xinstanceOfLong);//> true
System.debug(xinstanceOfDouble);//> true
System.debug(xinstanceOfDecimal);//> true
Source:Daniel Ballinger
When you try this:
Httph=newHttp();
HttpRequestreq=newHttpRequest();
req.setEndpoint('hooli ');
req.setMethod('PATCH');
HttpResponseres=h.send(req);//! Invalid HTTP method: PATCH
There is a workaround,but only supported by some servers.
Thankfully, these WTF's have since been fixed by Salesforce. We'll keep them documented for historical purposes (and entertainment).
https://twitter /FishOfPrey/status/869381316105588736
https://twitter /FishOfPrey/status/1016821563675459585
https://salesforce.stackexchange /questions/224490/bug-in-list-contains-for-id-data-type
Resolved in Spring '20 by the "Restrict Reflective Access to Non-Global Controller Constructors in Packages"Critical Update
publicabstractclassImAbstract{
publicStringfoo;
}
ImAbstractorAmI= (ImAbstract)JSON.deserialize('{ "foo": "bar" }',ImAbstract.class);
System.debug(orAmI.foo);// > bar