Properly trimming Strings in Velocity takes more than trim()

We sometimes see people trim()-ing Strings in Velocity before comparing them, like:

#if( $lead.Region.trim().equals("Northeast") )

Without evidence that leads or apps have added extra whitespace around values, worrying about trim() here is likely overkill. If data is validated via picklists or reliable apps (e.g. you can easily trim Marketo form fields before submission[1]) you’re just forcing Velocity/Java to allocate memory for new Strings.

Some people trim() before isEmpty(), in case a value consists of only spaces:

#if( $lead.LastName.trim().isEmpty() )

The real flaw in both cases isn’t the concept of trimming, it’s that the types of whitespace trimmed by trim() are very limited and don’t represent the real-world whitespace that janks up databases. For example, U+00A0 NBSP isn’t covered by trim() since it only removes ASCII whitespace! In my experience, NBSP is a bigger wtf-how-did-that-get-in-there offender[2] than basic spacebar, so you want to catch it.

So rather than trim(), use the \h and \v regex classes for horizontal and vertical spaces:

#set( $trimmedLastName = $lead.LastName.replaceAll("^[\h\v]+|[\h\v]+${esc.d}","") )

That’ll remove most everything commonly called “whitespace”, from the typical CR, LF, SP to NBSP to PS and LS, too.

Trim every String upfront

You do not want to have to remember to trim wherever you reference a field. Instead, trim all Strings on $lead before your other code:

#foreach( $entry in $lead.entrySet() )
#set( $key = $entry.getKey() )
#set( $val = $display.alt($entry.getValue()) )
#if( $val.class.name.equals("java.lang.String") )
#set( $lead[$key] = $val.replaceAll("^[\h\v]+|[\h\v]+${esc.d}","") )
#end
#end

The same can be done with OpportunityList or AnyCustomObject_cList#foreach over all the records and for each record, #foreach over its entries as above.[3]


Notes

[1] Like so:

MktoForms2.whenReady(function(readyForm){
    readyForm.onSubmit(function(submittingForm){
        const values = submittingForm.getValues();
        for( const [k,v] of Object.entries(values) ) values[k] = v.trim();        
        submittingForm.setValues(values);
    });
});
            

N.B. JS trim() is more aggressive and real-world-aware than Java trim(), so you don’t need a custom regex.

[2] Not so mysterious if your “data normalization” preprocessor was too forgiving... like Java trim()!

[3] The class.name condition is because some datatypes, like Integer, are preserved for custom object fields. Similarly, $display.alt() is because custom object fields can be null.

In contrast, every field on $lead is a non-null String in the Velocity context.