Velocitip: A subtle syntax screwup might not throw a fatal error, but can be a big deal

Check out this VTL snippet, intended to output trial keys for known combos of Country and custom field Intended Use:

#if( $lead.Intended_Use__c.equals("MycoAlert Trial Kit")) && ($lead.Country.equals("United States") )
#elseif( $lead.Intended_Use__c.equals("MycoAlert Plus Trial Kit")) && ($lead.Country.equals("United States") )
#elseif( $lead.Intended_Use__c.equals("MycoAlert Trial Kit")) && ($lead.Country.equals("Austria") )
#elseif( $lead.Intended_Use__c.equals("MycoAlert Plus Trial Kit")) && ($lead.Country.equals("Austria") )

At a glance, what do you think the output will be for this lead:

Now click below to reveal the actual output:

Show the output
 && (false )

Not what you expected?

Perhaps you counted the right and left parentheses, and indeed there are four opening ( and four closing ) on each line. But you missed that:

  • the && “operator” isn’t actually an operator here: it’s outside of the #elseif() directive, so it’s just just two ampersand characters with no special meaning
  • $lead.Country.equals("United States") is also outside the #elseif(), so it’s just evaluated in place, resulting in boolean false

You see, Velocity — like all template languages — centers on output. So anything between #-directives or $-references, as long as it doesn’t otherwise violate VTL grammar, is assumed to be a literal string.

You couldn’t get away with that wandering && in non-template languages. In JS, for example, this is a fatal syntax error:

if( lead.someProperty === "Some Value" ) { &&

Making it work

Here’s the code you probably thought you were seeing above:

#if( $lead.Intended_Use__c.equals("MycoAlert Trial Kit") && $lead.Country.equals("United States") )
#elseif( $lead.Intended_Use__c.equals("MycoAlert Plus Trial Kit") && $lead.Country.equals("United States") )
#elseif( $lead.Intended_Use__c.equals("MycoAlert Trial Kit") && $lead.Country.equals("Austria") )
#elseif( $lead.Intended_Use__c.equals("MycoAlert Plus Trial Kit") && $lead.Country.equals("Austria") )

Now, the && is an actual operator and $lead.Country is part of the condition.

Making it shorter and better

The fixed code above will work, but it’s hard to maintain as string constants are peppered throughout the code.

Better to use a map-first approach where you put all the strings up top, leaving you with only one line of “code” proper!

#set( $trialKeyPatterns = {
  ["MycoAlert Trial Kit","United States"] : "KGFV-WJN7-CW43",
  ["MycoAlert Plus Trial Kit","United States"] : "7XFC-WFZP-CTDY",
  ["MycoAlert Trial Kit","Austria"] : "VTQF-RL0D-Q6NJ",
  ["MycoAlert Plus Trial Kit","Austria"] : "QZIC-L36N-2BDQ"
${trialKeyPatterns.get([$lead.Intended_Use__c, $lead.Country])}

This works because the keys of a Java LinkedHashMap use value equivalence (equals), rather than reference equality (==). So map.get(key) finds the item whose key equals(key). Here, key is an ArrayList, and ArrayList.equals means “Are all indexes and items equal?”

In other words:

#set( $listA = ["MycoAlert Trial Kit","United States"] )
#set( $listB = ["MycoAlert Trial Kit","United States"] )
#set( $areTheyEqual = $listA.equals($listB) ) ## true
#set( $listC = ["United States","MycoAlert Trial Kit"] )
#set( $areTheyEqual = $listA.equals($listC) ) ## false
#set( $mapD = {
  $listA : "hello"
} )
#set( $getKey = $mapD.get($listA) ) ## "hello"
#set( $getKey = $mapD.get($listB) ) ## "hello"
#set( $getKey = $mapD.get($listC) ) ## null

✱ Yes, the terms are kind of confusing, even more so when you consider that Velocity ==, which we’re constantly reminding people not to use because it can act weird, isn’t the same as Java ==.