[SalesForce] Ternary Operator Compile Failure. Incompatible types in ternary operator

Assume we have some code in Apex:

APEX

Boolean isString = true; // or false
List<String> labels = new List<String>{ 'Label' };
Object entity = isString
        ? labels.get(0)
        : labels.size();

Believe you or not, the snippet cannot be compiled as it fails with:

Compile failure on line y, column x: Incompatible types in ternary operator: Integer, String

However, it can be rewritten and successfully compiled using an ordinary if-else statement. And it works as expected:

APEX

Boolean isString = true; // or false
List<String> labels = new List<String>{ 'Label' };
Object entity;
if (isString) {
    entity = labels.get(0);
} else {
    entity = labels.size();
}
System.debug(entity); // Label or 1

The only place in ApexDocs where Ternary operator (Right associative) is described says only:

This operator acts as a short-hand for if-then-else statements. If x, a Boolean, is true, y is the result. Otherwise, z is the result. x cannot be null.

Nothing more else here. Moreover, everyone can make sure that the ternary operator doesn't act exactly as a short-hand for if-then-else statements, using the example from above.

I also checked the same snippet in Java (JDK 16) to eliminate all questions regarding Java and it compiles and works as expected:

JAVA

Boolean /*boolean*/ isString = true; // or false
List<String> labels = new ArrayList<>();
labels.add("Label");
Object entity = isString
        ? labels.get(0)
        : labels.size();
System.out.println(entity); // Label or 1

How to describe such behavior? Is it a bug or undocumented expected behavior? Any ideas why right-hand side expression result i.e. either labels.get(0) or labels.size() cannot be assigned to the reference of Object generic type using the ternary operator in Apex due to compile error?

Best Answer

Apex has a particular restriction that both paths of the ternary operator must be compatible types, and if using an assignment, must be compatible with the assignment.

// Valid
Decimal d = true? 5: 10.0;
// Invalid
Integer i = true? 5: 10.0;

The first one is valid because the Integer 5 can be cast to a Decimal automatically, while the second is invalid, because 10.0, a Decimal, cannot automatically be converted to an Integer.

As far as I'm aware, this is a feature unique to Apex. Both results must be compatible types, and must be assignable to the data type. I understand that Object can contain both String and Integer values, as in your example, but this additional constraint prevents incompatible types being present on both paths.

You could, however, fix this particular instance with:

Boolean isString = false; // or true
List<String> labels = new List<String>{ 'Label' };
Object entity = isString
        ? (Object)labels.get(0)
        : (Object)labels.size();

As this forces both types to now be a compatible value (namely, Object).

As for why Apex does this, I have no idea, you'd have to ask a product engineer, if they even know why this is. Just know both sides need to be compatible types.