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
}