Upgrading your scripts for Groovy 3 compatibility

Groovy 3 introduces a list of breaking changes that can affect Collibra Groovy workflow scripts. You can consult the full list in the Groovy 3 release notes. There are two breaking changes that can most likely affect Collibra workflows:

  • If a Groovy switch statement has a default branch, it must be the last branch.
  • Groovy is now more compliant with the JavaBeans specification for one edge case scenario involving any field having a name starting with an uppercase letter (GROOVY-9618).
  • Enums cannot have a non-private constructor.
  • To use an interface “Function” as a parameter it must be typed, for example: _methodHandler(Function<Integer, Integer> requiredFunction).

You can upgrade the workflows groovy script code to be compatible with these changes before we upgrade Collibra to the version containing Groovy 3.

Change Groovy switch statement

If a Groovy 3 switch statement has a default branch, it must be the last branch. This means that you can no longer place the default branch in any other position except last in the list of branches.

The following does not compile under Groovy 3:

switch(yourVariable) {
  default:
    // default action
    break
  case 'a':
    // case a action
    break  
}

To correct the above code, place the default branch last:

switch(yourVariable) {
  case 'a':
    // case a action
    break
  default:
    // default action
    break  
}

Your code will behave exactly the same as all the branches have a break statement, so each branch is isolated and doesn’t affect other branches.

Switch fall-through

As switch fall-through occurs when branches don't have a break statement, making it possible to execute multiple branches, not only the one that matches.

Example of switch fall-through:

def result = ""
switch(yourVariable) {
  default:
    result += "default "
    // as there is no break statement, "case a" will be executed next
  case 'a':
    result += "a "
    // as there is no break statement, "case b" will be executed next
  case 'b':
    result += "b"
}

When yourVariable has the value 'a', the result variable will equal "a b". When yourVariable has the value 'b', the result variable will equal "b". In all other cases, the result variable will be equal “default a b”.

To make the code compile under Groovy 3, place the default branch last. However, to obtain the same results as the fall-through switch, you must also adjust the rest of the code:

Groovy 3 switch fall-through equivalent:

def result = ""
switch(yourVariable) {
  case 'a':
    result = "a b"
    break
  case 'b':
    result = "b"
    break
  default:
    result = "default a b"
    break
}

Adjusting for Groovy JavaBeans specification compatibility changes

Groovy 3 is more compliant with the JavaBeans specification for one edge case scenario involving any field having a name starting with an uppercase letter. This change has an impact on the handling of properties.

Groovy properties

The definition of properties according to the Groovy public documentation is:

A property is an externally visible feature of a class. Rather than just using a public field to represent such features (which provides a more limited abstraction and would restrict refactoring possibilities), the typical approach in Java is to follow the conventions outlined in the JavaBeans Specification, i.e. represent the property using a combination of a private backing field and getters/setters. Groovy follows these same conventions but provides a simpler way to define the property.

The code sample below will generate the following:

  • A backing private String name field, a getName and a setName method.

  • A backing private int age field, a getAge and a setAge method.

class Person {
    String name                             
    int age                                 
}

By convention, Groovy also recognizes properties even if there is no backing field, provided there are getters or setters that follow the Java Beans specification.

class PseudoProperties {
    //1. a pseudo property "name"
    void setName(String name) {}
    String getName() {}

    //2. a pseudo read-only property "age"
    int getAge() { 42 }

    //3. a pseudo write-only property "groovy"
    void setGroovy(boolean groovy) {  }
}
def p = new PseudoProperties()
p.name = 'Foo' // uses (1)                     
assert p.age == 42 // uses (2)                 
p.groovy = true // uses (3)

Groovy 3 breaking change

In Groovy 3 the handling of properties that start with an uppercase letter has changed to be more compliant with the JavaBeans specification.

The way how properties are mapped to the accessor method has changed. In groovy 2 it was possible to access the field instead of the accessor methods in some scenarios as shown below:

Groovy 2

class A {
  private String X = 'fieldX'
  private String Prop = 'fieldProp'
  String getProp() { 'Prop' }
  String getX() { 'X' }
}
new A().with {
  assert prop == 'Prop' // uses getProp() accessor
  assert Prop == 'fieldProp' // uses field directly
  assert x == 'X' // uses getX() accessor
  assert X == 'fieldX' // uses field direclty
}

Groovy 3

class A {
  private String X = 'fieldX'
  private String Prop = 'fieldProp'
  String getProp() { 'Prop' }
  String getX() { 'X' }
}
new A().with {
  assert prop == 'Prop' // use getProp() accessor
  assert Prop == 'Prop' // use getProp() accessor
  assert x == 'X' // uses getX() accessor
  assert X == 'X' // uses getX() accessor
}

A similar situation occurs when you use static properties:

Groovy 2

class A {
  private static String X = 'fieldX'
  private static String Prop = 'fieldProp'
  static String getProp() { 'Prop' }
  static String getX() { 'X' }
}
A.with {
  assert prop == 'Prop' // uses static getProp() accessor
  assert Prop == 'fieldProp' // uses field directly
  assert x == 'X' // uses static getX() accessor
  assert X == 'fieldX' // uses field directly
}

Groovy 3

class A {
  private static String X = 'fieldX'
  private static String Prop = 'fieldProp'
  static String getProp() { 'Prop' }
  static String getX() { 'X' }
}
A.with {
  assert prop == 'Prop' // uses static getProp() accessor
  assert Prop == 'Prop' // uses static getProp() accessoor
  assert x == 'X' // uses static getX() accessor
  assert X == 'X' // uses static getX() accessor
}

This breaking change doesn’t affect classes where accessor methods are not overwritten.

Recommendation

To make existing workflow scripts compatible with Groovy 3, we recommend using lowercase property names, except in when the property name is all uppercase:

class A {
  private String X = 'fieldX'
  private String XML = 'fieldXML'
  
  String getX() { 'X' }
  String getXML() { 'XML' }
}
new A().with {
  assert x == 'X' // instead of using uppercase X property 
  assert XML == 'XML' // in this case using XML property is the only way
}