2Groovy Basics

Groovy Basics

This section highlights some important aspects of Groovy to allow you to better understand the examples in the sections that follow.

Commenting Your Scripts

It is important that you document your scripts so that you and your colleagues who might view the code months from now will remember what the logic is doing. You can use either a double-slash combination // which makes the rest of the current line a comment, or you can use the open-comment and close-comment combination of /* followed later by */. The latter style can span multiple lines.

Here is an example of both styles in action:

// Loop over the names in the list
for (name in listOfNames) {
  /*
   * Update the location for the current name.
   * If the name passed in does not exist, will result in a no-op
   */
  updateLocationFor(name,      // name of contact
                    'Default', /* location style */
                   )
}

When using multi-line comments, it is illegal for a nested /* ... */ comment to appear inside of another one. So, for example, the following is not allowed:

// Nested, multi-line comment below is not legal
def interest = 0
/*
  18-MAY-2001 (smuench) Temporarily commented out calculation!
 
  /*
   * Interest Accrual Calculation Here
   */
   interest = complexInterestCalculation()
*/

Instead, you can comment out an existing multi-line block like this:

// Nested, multi-line comment below is legal
def interest = 0
//
// 18-MAY-2001 (smuench) Temporarily commented out calculation!
// 
//  /*
//   * Interest Accrual Calculation Here
//   */
//   interest = complexInterestCalculation()
//

Or, alternatively had your initial code used the // style of comments, the following is also legal:

// Nested, multi-line comment below is not legal
def interest = 0
/*
  18-MAY-2001 (smuench) Temporarily commented out calculation!
 
  //
  // Interest Accrual Calculation Here
  //
  interest = complexInterestCalculation()
*/

The most common style-guide for comments would suggest to use multi-line comments at the beginning of the script, and single-line comments on subsequent lines. This allows you to most easily comment out code for debugging purposes. Thus, you typical script would look like this:

/*
 * Object validation rule for BankAccount
 * 
 * Ensures that account is not overdrawn
 */
def balance = CurrentBalance
// Use an object function to calculate uncleared charges
def unclearedCharges = unclearedChargesAmountForAccount()
// Perform some other complicated processing
performComplicatedProcessing()
// return true if the account is not overdrawn
return balance > unclearedCharges

Defining Variables

Groovy is a dynamic language, so variables in your scripts can be typed dynamically using the def keyword as follows:

// Assign the number 10 to a variable named "counter"
def counter = 10

// Assign the string "Hello" to a variable named "salutation"
def salutation = 'Hello'

// Assign the current date and time to a variable named "currentTime"
def currentTime = now()

Using the def keyword you can define a local variable of the right type to store any kind of value, not only the three examples above. Alternatively you can declare a specific type for variables to make your intention more explicit in the code. For example, the above could be written like this instead:

// Assign the number 10 to a variable of type Integer named "counter"
Integer counter = 10

// Assign the string "Hello" to a variable named "salutation"
String salutation = 'Hello'

// Assign the current date and time to a variable named "currentTime"
Date currentTime = now()
Note: You can generally choose to use the def keyword or to use a specific type for your variables according to your own preference, however when your variable needs to hold a business object, you must to define the variable’s type using the def keyword. See the tip in Using Substitution Expressions in Strings below for more information.

Referencing the Value of a Field in the Current Object

When writing scripts that execute in the context of the current business object, you can reference the value of any field in the current object by simply using its API name. This includes all of the following contexts:

  • object validation rules

  • field-level validation rules

  • formula field expressions

  • object triggers

  • field triggers, and

  • object functions

To write a script that references the value of fields named contactPhoneNumber and contactTwitterName, you would use the following code:

// Assign value of field "contactPhoneNumber" to "phone" var
def phone = contactPhoneNumber

// Assign value of field "contactTwitterName" to "twitterName" var
def twitterName = contactTwitterName

// Assemble text fragment by concatenating static text and variables
def textFragment = 'We will try to call you at ' + phone +
                   ' or send you a tweet at ' + twitterName

Defining a local variable to hold the value of a field is a good practice if you will be referencing its value more than once in your code. If you only need to use it once, you can directly reference a field's name without defining a local variable for it, like this:

def textFragment = 'We will try to call you at ' + contactPhoneNumber +
                   ' or send you a tweet at ' + contactTwitterName
Note: When referencing a field value multiple times, you can generally choose to use or not to use a local variable according to your own preference, however when working with an RowIterator object, you must to use the def keyword to define a variable to hold it. See the tip in the Using Substitution Expressions in Strings for more information.

Working with Numbers, Dates, and Strings

Groovy makes it easy to work with numbers, dates and strings. The expression for a literal number is just the number itself:
// Default discount is 5%
def defaultDiscount = 0.05
// Assume 31 days in a month
def daysInMonth = 31
To create a literal date, use the date() or dateTime() function:
// Start by considering January 31st, 2019
def lastDayOfJan = date(2019,1,31)
// Tax forms are late after 15-APR-2019 23:59:59
def taxSubmissionDeadline = dateTime(2019,4,15,23,59,59)
Write a literal string using a matching pair of single quotes, as shown here.
// Direct users to the Acme support twitter account
def supportTwitterHandle = '@acmesupport'
It is fine if the string value contains double-quotes, as in:
// Default podcast signoff
def salutation = 'As we always say, "Animate from the heart."'
However, if your string value contains single quotes, then use a matching pair of double-quotes to surround the value like this:
// Find only gold customers with credit score over 750
customers.appendViewCriteria("status = 'Gold' and creditScore > 750")

You can use the normal + and - operators to do date, number, and string arithmetic like this:

// Assign a date three days after the CreatedDate
def targetDate = CreatedDate + 3

// Assign a date one week (seven days) before the value
// of the SubmittedDate field
def earliestAcceptedDate = SubmittedDate - 7

// Increase an employee's Salary field value by 100 dollars
Salary = Salary + 100

// Decrement an salesman's commission field value by 100 dollars
Commission = Commission - 100

// Subtract (i.e. remove) any "@"-sign that might be present
// in the contact's twitter name
def twitNameWithoutAtSign = ContactTwitterName - '@'

// Add the value of the twitter name to the message
def message = 'Follow this user on Twitter at @' + twitNameWithoutAtSign

Using Substitution Expressions in Strings

Groovy supports using two kinds of string literals, normal strings and strings with substitution expressions. To define a normal string literal, use single quotes to surround the contents like this:

// These are normal strings
def name = 'Steve'
def confirmation = '2 message(s) sent to ' + name

To define a string with substitution expressions, use double-quotes to surround the contents. The string value can contain any number of embedded expressions using the ${expression} syntax. For example, you could write:

// The confirmation variable is a string with substitution expressions
def name = 'Steve'
def numMessages = 2
def confirmation = "${numMessages} message(s) sent to ${name}"

Executing the code above will end up assigning the value 2 messages(s) sent to Steve to the variable named confirmation. It does no harm to use double-quotes all the time, however if your string literal contains no substitution expressions it is slightly more efficient to use the normal string with single-quotes.

Tip: As a rule of thumb, use normal (single-quoted) strings as your default kind of string, unless you require the substitution expressions in the string.

Using Conditional Expressions

When you need to perform the conditional logic, you use the familiar if/else construct. For example, in the text fragment example in the previous section, if the current object's ContactTwitterName returns null, then you won't want to include the static text related to a twitter name. You can accomplish this conditional text inclusion using if/else like this:

def textFragment = 'We will try to call you at ' + ContactPhoneNumber
if (ContactTwitterName != null) {
  textFragment += ', or send you a tweet at '+ContactTwitterName
}
else {
  textFragment += '. Give us your twitter name to get a tweet'
}
textFragment += '.' 

While sometimes the traditional if/else block is more easy to read, in other cases it can be quite verbose. Consider an example where you want to define an emailToUse variable whose value depends on whether the EmailAddress field ends with a .gov suffix. If the primary email ends with .gov, then you want to use the AlternateEmailAddress instead. Using the traditional if/else block your script would look like this:

// Define emailToUse variable whose value is conditionally
// assigned. If the primary email address contains a '.gov'
// domain, then use the alternate email, otherwise use the
// primary email.
def emailToUse
if (endsWith(EmailAddress,'.gov') {
  emailToUse = AlternateEmailAddress 
}
else {
  emailToUse = EmailAddress
}

Using Groovy's handy inline if / then / else operator, you can write the same code in a lot fewer lines:

def emailToUse = endsWith(EmailAddress,'.gov') ? AlternateEmailAddress : EmailAddress

The inline if / then / else operator has the following general syntax:

BooleanExpression ? If_True_Use_This_Expression : If_False_Use_This_Expression

Since you can use whitespace to format your code to make it more readable, consider wrapping inline conditional expressions like this:

def emailToUse = endsWith(EmailAddress,'.gov') 
                 ? AlternateEmailAddress 
                 : EmailAddress

Using the Switch Statement

If the expression on which your conditional logic depends may take on many different values, and for each different value you'd like a different block of code to execute, use the switch statement to simplify the task. As shown in the example below, the expression passed as the single argument to the switch statement is compared with the value in each case block. The code inside the first matching case block will execute. Notice the use of the break statement inside of each case block. Failure to include this break statement results in the execution of code from subsequent case blocks, which will typically lead to bugs in your application.

Notice, further, that in addition to using a specific value like 'A' or 'B' you can also use a range of values like 'C'..'P' or a list of values like ['Q','X','Z']. The switch expression is not restricted to being a string as is used in this example; it can be any object type.

def logMsg
def maxDiscount = 0
// warehouse code is first letter of product SKU
// uppercase the letter before using it in switch
def warehouseCode = upperCase(left(SKU,1))
// Switch on warehouseCode to invoke appropriate
// object function to calculate max discount
switch (warehouseCode) {
  case 'A': 
    maxDiscount = Warehouse_A_Discount()
    logMsg = 'Used warehouse A calculation'
    break
  case 'B':
    maxDiscount = Warehouse_B_Discount()
    logMsg = 'Used warehouse B calculation'
  case 'C'..'P':
    maxDiscount = Warehouse_C_through__P_Discount()
    logMsg = 'Used warehouse C-through-P calculation'
    break    
  case ['Q','X','Z']: 
    maxDiscount = Warehouse_Q_X_Z_Discount()
    logMsg = 'Used warehouse Q-X-Z calculation'
    break
  default:
    maxDiscount = Default_Discount()
    logMsg = 'Used default max discount'
}
println(logMsg+' ['+maxDiscount+']')
// return expression that will be true when rule is valid
return Discount == null || Discount <= maxDiscount

Returning a Boolean Result

Two business object contexts expect your groovy script to return a boolean true or false result. These include:

  • object-level validation rules

  • field-level validation rules

Groovy makes this easy. One approach is to use the groovy true and false keywords to indicate your return as in the following example:

// Return true if value of the commission field is greater than 1000
if (commission > 1000) { 
  return true
}
else {
  return false
}

However, since the expression commission > 1000 being tested above in the if statement is itself a boolean-valued expression, you can write the above logic in a more concise way by simply returning the expression itself like this:

return commission > 1000 

Furthermore, since Groovy will implicitly change the last statement in your code to be a return, you could even remove the return keyword and just say:

commission > 1000

This is especially convenient for simple comparisons that are the only statement in a validation rule.

Assigning a Value to a Field in the Current Object

To assign the value of a field, use the Groovy assignment operator = and to compare expressions for equality, use the double-equals operator == as follows:

// Compare the ContactTwitterName field's value to the constant string 'steve'
if (ContactTwitterName == 'steve') {
  // Assign a new value to the ContactTwitterName field
  ContactTwitterName = 'stefano'
}
Tip: See Avoiding Validation Threshold Errors By Conditionally Assigning Values for a tip about how to avoid your field assignments from causing an object to hit its validation threshold.

Writing Null-Aware Expressions

When writing your scripts, be aware that field values can be null. You can use the nvl() null value function to easily define a value to use instead of null as part of any script expressions you use. Consider the following examples:

// Assign a date three days after the PostedDate
// Use the current date instead of the PostedDate if the
// PostedDate is null
def targetDate = nvl(PostedDate,now()) + 3

// Increase an employee's custom Salary field value by 10 percent
// Use zero if current Salary is null
Salary = nvl(Salary,0) * 1.1
Tip: Both expressions you pass to the nvl() function must have the same datatype, or you will see type-checking warnings when saving your code. For example, if Salary is a number field, then it is incorrect to use an expression like nvl(Salary,’<No Salary>’) because the first expression is a number while the second expression is a string.

Understanding Null Versus the Empty String

In Groovy, there is a subtle difference between a variable whose value is null and a variable whose value is the empty string. The value null represents the absence of any object, while the empty string is an object of type String with zero characters. If you try to compare the two, they are not the same. For example, any code inside the following conditional block will not execute because the value of varA (null) does not equals the value of varB (the empty string).

def varA = null
def varB = '' /* The empty string */
if (varA == varB) {
  // Do something here when varA equals varB
}

Another common gotcha related to this subtle difference is that trying to compare a variable to the empty string does not test whether it is null. For example, the code inside the following conditional block will execute (and cause a NullPointerException at runtime) because the null value of varA is not equal to the empty string:

def varA = null
if (varA != '') {
  // set varB to the first character in varA
  def varB = varA.charAt(0)
}

To test whether a string variable is neither null nor empty, you could explicitly write out both conditions like this:

if (varA != null && varA != '') {
  // Do something when varA is neither null nor empty
}

However, Groovy provides an even simpler way. Since both null and the empty string evaluate to false when interpreted as a boolean, you can use the following instead:

if (varA) {
  // Do something when varA has a non-null and non-empty value
}

If varA is null, the condition block is skipped. The same will occur if varA is equal to the empty string because either condition will evaluate to boolean false. This more compact syntax is the recommended approach.

Using Groovy's Safe Navigation Operator

If you are using "dot" notation to navigate to reference the value of a related object, you should use Groovy's safe-navigation operator ?. instead of just using the . operator. This will avoid a NullPointerException at runtime if the left-hand-side of the operator happens to evaluate to null. For example, consider a TroubleTicket object with a reference field named assignedTo representing the staff member assigned to work on the trouble ticket. Since the assignedTo field may be null before the ticket gets assigned, any code referencing fields from the related object should use the safe-navigation operator as shown here:

// access referenced object and access its last name
// Using the ?. operator, if related object is null,
// the expression evaluates to null instead of throwing
// NullPointerException
def assignedToName = assignedToObject?.lastName
Tip: For more information on why the code here accesses assignedToObject instead of a field named assignedTo, see Understanding Secondary Fields Related to a Reference

Printing and Viewing Diagnostic Messages

To assist with debugging, use the Logs window to view details on runtime exceptions as well as the runtime diagnostic messages your scripts have generated. Open the window by clicking on the Logs button at the bottom of the Visual Builder window, located next to the Audits button. Log messages related to your user account appear the first time the window is opened. Check the Enable Logging checkbox when you want messages logged by your own script code to be written to the diagnostic log. The messages are shown by default in chronological order. When a runtime exception occurs, additional details on the offending script and line number where the error occurred are visible in the tooltip by hovering your mouse over the exception message. The search field allows you to filter the log messages. To export the messages to a file, use the Export toolbar button. To close the Logs window again, click again on the Logs button in the window’s title bar.

If you keep the Logs window open while you work, consider the following approach. Before starting a new attempt to reproduce the problem, click the Clear toolbar button to remove any previous messages generated. After encountering the error you are diagnosing, click the Refresh toolbar button to see the latest log messages generated.


Viewing Diagnostic Messages for Debugging

Writing Diagnostic Log Messages from Your Scripts

To write messages to the diagnostic log, use the print or println function. The former writes its value without any newline character, while the latter writes it value along with a newline. For example:

// Write a diagnostic message to the log. Notice how
// convenient string substitution expressions are
println("Status = ${Status}")

In this release, the diagnostic messages in the log are not identified by context, so it can be helpful to include information in the printed diagnostic messages to identify what code was executing when the diagnostic message was written. For example:

// Write a diagnostic message to the log, including info about the context
println("[In: BeforeInsert] Status = ${Status}")

Working with Lists

A list is an ordered collection of objects. You can create list of objects using Groovy's square-bracket notation and a comma separating each list element like this:

// Define a list of numbers
def list = [101, 334, 1208, 20]

Of course, the list can be of strings as well:

// Define a list of strings
def names = ['Steve','Paul','Jane','Josie']

If needed, the list can contain objects of any type, including a heterogeneous set of object types, for example a mix of strings and numbers.

To refer to a specific element in the list, use the square brackets with an integer argument like this.

// Store the third name in the list in a variable
def thirdName = names[2] // zero based index!

Remember that the list is zero-based so list[0] is the first element of the list and list[5] is the six element. Of course you can also pass a variable as the value of the operand like this:

for (j in 2..3) {
  def curName = names[j]
  // do something with curName value here
}

To update a specific list item's value, you can use the combination of the subscript and the assignment operator:

names[2] = 'John'

To add an entry to the end of the list, use the add() method:

names.add('Ringo')

A list can contain duplicates, so if you write code like the following, then the string Ringo will be added twice to the list:

// This will add 'Ringo' twice to the list!
names.add('Ringo')
names.add('Ringo')

To test if an entry already exists in the list, use the contains() function. This way, you can ensure that you don't add the same item twice if duplicates are not desirable for your purposes:

// The exclamation point is the "not" operator, so this
// first checks if the 'names' list does NOT contain 'Ringo' before
// adding it to the list
if (!names.contains('Ringo')) {
  names.add('Ringo')
}

To remove an entry from the list, use the remove() method.

names.remove('Ringo')

Note that this only removes the first occurrence of the item in the list, returning a boolean result indicating true if the desired item was found and removed. Therefore, if your list allows duplicates and you need to remove them all, you'll need to write a loop to call remove() until it returns false.

You can iterate over the entries in a list using the for...in loop like this:

// Process each name in the list, returning
// false if any restricted name is encountered
for (name in names) {
  // call an object function for each name processed
  if (isNameRestricted(name)) {
    return false
  }
}
return true

You can define an empty list using the square-bracket notation with nothing inside like this:

def foundElements = [] // empty list!

Working with Maps

A map is an unordered collection of name/value pairs. The name in each name/value pair is called the map's key for that entry since it is the key to looking up the value in the map later. You can create a map using Groovy's square-bracket notation, using a colon to separate each key and value, and a comma between each key/value pair like this:

// Define a map of name/value pairs that associate
// a status value (e.g. "Open", "Closed", "Pending") with a
// maximum number of days
def maxDaysByStatus = [Open:30, Closed:90, Pending:45]

Notice that by default, the map key is assumed to be a string so you don't need to include the key values in quotes. However, if any key value contains spaces you will need to use quotes around it like this:

def maxDaysByStatus = [Open:30, Closed:90, Pending:45, 'On Backorder':10]

If you want to use another type as the map key, you need to surround the key with parentheses. Consider the following example without the parentheses:

def x = 1
def y = 2
def xvalue = 'One'
def yvalue = 'Two'
// this creates a map with entries ('x'->'One') and ('y'->'Two')
def m = [x:xvalue,y:yvalue]

The above example creates a map with key values of the strings x and y, rather than using the value of the variable x and the value of the variable y as map keys. To obtain this effect, surround the key expressions with parentheses like this:

def x = 1
def y = 2
def xvalue = 'One'
def yvalue = 'Two'
// this creates a map with entries (1->'One') and (2->'Two')
def m = [(x):xvalue,(y):yvalue]

This creates a map with key values of the numbers 1 and 2.

To reference the value of a map entry, use dot notation like this, using the may key value as if it were a field name on the map object:

def closedDayLimit = maxDaysByStatus.Closed

If the key value contains a literal dot character or contains spaces or special characters, you can also use the square-bracket notation, passing the key value as the operand:

def onBackorderDayLimit = maxDaysByStatus['On Backorder']

This square bracket notation is also handy if the key value is coming from the value of a variable instead of a literal string, for example:

// Loop over a list of statuses to process
for (curStatus in ['Open','On Backorder']) {
  def limitForCurStatus = maxDaysByStatus[curStatus]
  // do something here with the current status' limit
}  

To add an new key/value pair to the map, use the put() method:

// Add an additional status to the map
maxDaysByStatus.put('Ringo')

A map cannot contain duplicate key entries, so if you use put() to put the value of an existing element, the existing value for that key is overwritten. You can use the containsKey() function to test whether or not a particular map entry already exists with a given key value, or you can use the containsValue() function to test if any map entry exists that has a given value — there might be zero, one, or multiple entries!

// Test whether a map key matching the value of the
// curKey variable exists or not
if (maxDaysByStatus.containsKey(curKey)) {
  def dayLimit = maxDaysByStatus[curKey]
  // do something with dayLimit here
}
else {
  println("Unexpected error: key ${curKey} not found in maxDaysByStatusMap!")
}

To remove an entry from the map, use the remove() method. It returns the value that was previously associated with the key passed in, otherwise it returns null if it did not find the given key value in the map to remove.

maxDaysByStatus.remove('On Backorder')

You can define an empty map using the square-bracket notation with only a colon inside like this:

def foundItemCounts = [:] // empty map!

Working with Ranges

Using ranges, you can conveniently creates lists of sequential values. If you need to work with a list of integers from 1 to 100, rather than creating a list with 100 literal numbers in it, you can use the .. operator to create a range like this:

def indexes = 1..100

The range is particularly useful in performing iterations over a list of items in combination with a for loop like this:

def indexes = 1..100
for (j in indexes) {
  // do something with j here
}

Of course, you need not assign the range to a variable to use it in a loop, you can use it inline like this:

for (j in 1..100) {
  // do something with j here
}