Release notes for Groovy 2.3
Groovy 2.3 is the new major release of Groovy, featuring
official support for running Groovy on JDK 8, traits, new and improved
AST transformations like @TailRecursive
, @Builder
and @Sortable
, a
new NIO2 module with Path
support, lightening fast JSON parsing and
building,closure parameter type inference, a new markup template engine,
Groovysh and GroovyConsole ease of use improvements, a
new GroovyAssert
test utility, more @BaseScript
class capabilities,
and more.
Official support for running Groovy on JDK 8
This is the first version of Groovy to be officially compatible with JDK 8.
JDK 8 and its interface default methods introduced some incompatibilities with a few methods of the Groovy Development Kit, so we had to adapt to the situation, introducing minor breaking changes for the affected methods and their outcome.
Note that we’re not planning to backport the changes to older versions of Groovy, so if you want to run Groovy on JDK 8, you’ll have to upgrade to the shiniest version of Groovy!
Groovy 2.3 doesn’t support the new syntax constructs offered by Java 8 (such as lambdas, method references, default methods in interfaces, etc.), but you can very well already use the new APIs offered by JDK 8, and even use Groovy closures in lieu of Java 8 lambdas.
For reference, here are a couple of examples which use Java 8 streams, for iterating over a stream of ints, or over the lines of a file:
IntStream.range(1, 100).forEach { println it }
Files.lines(Paths.get('README.adoc'))
.map { it.toUpperCase() }
.forEach { println it }
In particular, in the two statements above, notice that we replaced Java 8 lambdas with Groovy closures, as Groovy provides a closure coercion mechanism which transforms a Groovy closure into a functional interface — unlike Java, Groovy also provides that coercion mechanism for abstract classes containing a single abstract method.
In future versions of Groovy, certain Java 8 syntax constructs, or particular Groovy methods decorating JDK 8 APIs might be added.
Traits
A major highlight for Groovy 2.3 is the introduction of the concept of traits.
Traits are reusable components of behavior that your classes can implement, and are an additional Object-Oriented concept alongside classes and interfaces.
Below, we create a trait with a concrete method fly()
which returns a
String
.
trait FlyingAbility {
String fly() { "I'm flying!" }
}
Then we create a class, Bird
, that implements that trait, and
instantiate it:
class Bird implements FlyingAbility {}
def b = new Bird()
We can check that the Bird instance does have the new fly()
method
mixed-in:
assert b.fly() == "I'm flying!"
Groovy traits are stateful (unlike Java 8 interface default methods). A trait can have Groovy properties like plain classes:
trait Named {
String name
}
This time, the Bird
class implements that Named
trait:
class Bird implements Named {}
We can instantiate the Bird with the named-argument constructor shortcut provided by Groovy:
def b = new Bird(name: 'Colibri')
We assert that the instantiated Bird does have the name property added to it:
assert b.name == 'Colibri'
They allow the composition of behavior without going into the "diamond inheritance" problem allowing you to decide which behavior prevails upon conflict, either by convention (last trait declared wins) or by explicitly overriding the conflicting method:
trait KiteSurfer { String surf() { 'kite' } }
trait WebSurfer { String surf() { 'web' } }
class Person { String name }
class Hipster extends Person
implements KiteSurfer, WebSurfer {}
def h = new Hipster()
assert h.surf() == 'web'
Above, the surf()
method from WebSurfer
wins, as it’s the last declared
trait, but you can reverse the trait implementation order if you want
kite to be returned. If you want to be more explicit, your Hipster
class
can override the surf()
method itself, and call WebSurfer.super.foo()
or
KiteSurfer.super.foo()
or do something entirely different.
Traits support inheritance, thus a trait can extend another trait or implement an interface, as shown below:
trait Named { String name }
trait FlyingAbility extends Named {
String fly() { "I'm a flying ${name}!" }
}
class Bird implements FlyingAbility {}
def b = new Bird(name: 'Colibri')
assert b.name == 'Colibri'
assert b.fly() == "I'm a flying Colibri!"
Traits are compatible with static type checking and compilation, as well as our usual dynamic behavior. Trait mixed-in methods are actually "real" methods (i.e. visible from Java as well) and not just dynamic. Note however, that not all existing AST transformations are compatible with traits.
Traits can also be implemented at runtime with as
or with
withTraits
if you just want to add behavior of a trait to an object
you’re instantiating, without having to create an intermediary
artificial class just for that purpose (also called per-instance
traits):
trait Named { String name }
trait Quacks {
String quack() { 'Quack!' }
}
class Animal {}
def na = new Animal().withTraits Named, Quacks
na.name = 'Daffy'
assert na.name == 'Daffy'
assert na.quack() == 'Quack!'
You can find more information on traits in the exhaustive traits documentation.
New and updated AST transformations
New transformations
@TailRecursive
@TailRecursive
on methods adds tail recursion to methods which are
recursive and call themselves at the last operation of the method body,
which helps avoid blowing up the stack with the recursive calls
(GROOVY-6570).
Here’s a slightly rewritten factorial implementation, that is friendly to tail-call transformation:
import groovy.transform.TailRecursive
@TailRecursive
def fact(BigInteger n, accu = 1G) {
if (n < 2) accu
else fact(n - 1, n * accu)
}
assert fact(1000) > 10e2566
@Builder
Recent Java APIs have adopted the builder pattern (not to be confused
with Groovy’s builders) to instantiate complex objects, without
requiring to multiply the number of constructors with variants taking
various combination of parameters. Groovy 2.3 introduces a @Builder
transformation to automate the creation of such builder APIs
(GROOVY-6484).
The @Builder
transformation offers different implementation strategies
that you can choose from:
-
a simple strategy for creating chained setters
-
an external strategy where you annotate an explicit builder class while leaving some buildee class being built untouched
-
a default strategy which creates a nested helper class for instance creation
-
and an initializer strategy which creates a nested helper class for instance creation which when used with
@CompileStatic
allows type-safe object creation
Here’s an example with the default strategy:
import groovy.transform.builder.Builder
@Builder
class Person {
String firstName
String lastName
int age
}
def person = Person.builder()
.firstName("Robert")
.lastName("Lewandowski")
.age(21)
.build()
assert person.firstName == "Robert"
assert person.lastName == "Lewandowski"
assert person.age == 21
You can have a look at the @Builder documentation for the other builder variants.
@Sortable
@Sortable
on classes implements comparison methods for you (through
implementing the Comparable
interface), according to the declaration
order of your properties
(GROOVY-6649).
For the following Person
class, its instances will be sorted by last
name, then by first name, and by age, in that order:
import groovy.transform.*
@Sortable
@Canonical
class Person {
String last
String first
int age
}
def folks = [
new Person('Simpson', 'Bart', 12),
new Person('Simpson', 'Homer', 40),
new Person('Kent', 'Clark', 36)
]
assert folks.sort()*.first == ['Clark', 'Bart', 'Homer']
Additionally, you can define included / excluded fields, access
individual field comparators with methods like comparatorByFirst()
.
More details on the @Sortable documentation page.
@SourceURI
With @SourceURI
, you can annotate a java.net.URI
or even a
java.lang.String
script variable or class field so that the variable or
field are injected the URI of the Groovy file.
If you evaluate or compile a Groovy script or class, the variable or field will contain a data URI, for example, for the following example:
import groovy.transform.SourceURI
@SourceURI String src
println src
The src
variable will contain the following data URI:
data:,import%20groovy.transform.SourceURI%0A%0A@SourceURI%20String%20src%0A%0Aprintln%20src
If you save the script in a file called sourceuri.groovy
in /tmp
, and
run that script with the groovy
command, you’ll see an absolute File
path printed:
file:/tmp/sourceuri.groovy
As we mentioned above, you can also write @SourceURI URI src
, if you want
to have a URI
instead of a String
.
Updated transformations
@Delegate improvements
@Delegate
supports includeTypes
and excludeTypes
attributes to give you
fine-grained control over which methods to include or exclude from
delegation. Rather than just matching on name, this option matches on
the name and parameter types expressed in an interface type
(GROOVY-6329).
@BaseScript class improvements
@BaseScript is a fairly recent addition in Groovy, and it allowed to annotate a variable in your script to instruct the compiler to use a particular base script class for this script. Now we have another notation which is nicer as you can annotate an import or a package (GROOVY-6592) to indicate that base script class:
@BaseScript(MyScript)
import groovy.transform.BaseScript
Additionally, base script classes can now use any abstract method for
the script body. This means that you can implement the run()
method to
implement specific behavior like setup and tear down in tests
(GROOVY-6585
and GROOVY-6615).
Given the following custom base script class, where we implement the
default run()
method, we also create a new abstract method called
internalRun()
:
abstract class CustomBase extends Script {
def run() {
before()
internalRun()
after()
}
abstract internalRun()
def before() { println 'before' }
def after() { println 'after' }
}
We can then have the script below transparently implement the
internalRun()
method instead of the usual run()
one:
import groovy.transform.BaseScript
@BaseScript CustomBase script
println 'Hello'
New NIO module for Java 7+
On JDK 7 and beyond, you can benefit from the same methods as the ones
of File but for the new NIO2 class Path
.
See GROOVY-6377 and the pull request for some further hints of the new methods.
You’ll find familiar methods of the Groovy GDK on File
also available on
Path
like these:
path.withReader { Reader r -> ... }
path.eachLine { String line -> ... }
path.eachFileRecurse { Path p -> ... }
path << 'some content'
path << bytes
path.readLines()
Performance improvements
Miscellaneous improvements
Various minor performance improvements across the board, for static
compilation, the invokedynamic
backend, as well as "normal"
dynamic Groovy, have been worked on.
Drastic JSON parsing and serialization performance improvements
Groovy JSON support has been refactored and tailored towards performance, making Groovy 2.3’s JSON support usually faster than all the JSON libraries available in the Java ecosystem.
Rick Hightower and Andrey Bleschestov covered the performance gains, both in parsing and serialization, in a benchmarks on Rick’s blog and on Andrey’s JSON benchmark project on GitHub. The results are impressive, as the parsing is generally roughly 2x to 4x faster with Groovy’s new parsers compared to existing libraries, and ~21x faster than pre-Groovy 2.3 parsing. On the serialization front, Groovy’s new serialization is also ~17x faster than before, and at the same level as competing libraries.
JSON slurper and builder enhancements
Beside the performance improvements of the JSON module, other updates have taken place.
With JsonSlurper, you’ll be able to set different parser types depending on the kind of input you wish to parse, particularly if you know the size of the payload you expect to parse, or whether you want a more tolerant parser which accepts elements like comments which are not normally supported by the JSON specification.
Here’s an example showing how to parse a non-conformant JSON payload:
import groovy.json.*
import static groovy.json.JsonParserType.*
def parser = new JsonSlurper().setType(LAX)
def conf = parser.parseText '''
// configuration file
{
// no quote for key, single quoted value
environment: 'production'
# pound-style comment
'server': 5
}
'''
assert conf.environment == 'production'
assert conf.server == 5
Closure parameter type inference
We closed a gap which forced you to type your closure parameters to get correct type inference with static type checking or static compilation enabled. In situations like the following, you would have to explicitly give the type of the parameter, but it’s no longer required:
['a','b'].each { it.toUpperCase() }
In the signature of your methods taking closures as arguments, you’ll also be able to annotate the closure parameter with @ClosureParams to give additional hints to the type checker to infer the type of the parameters passed to your closure.
You can also find more about this in Cédric’s blog post on closure parameter type inference.
New markup template engine
Groovy now has an additional template engine, in the form of the Markup template engine, which gives you a very fast template engine (thanks to static compilation), based on the familiar Markup builder approach and notation, but also offering formatting options (indentation, escaping), internationalization, includes, as well as proposing type checked templates and models.
More details about the new Markup template engine in the documentation, as well as in Cédric’s blog, if you want to learn more about the "behind the scenes" stories!
To illustrate the basic usage, consider you have the following template:
def tpl = '''
cars {
cars.each {
car(make: it.make, name: it.name)
}
}
'''
And have the following model:
model = [cars: [
new Car(make: 'Peugeot', name: '508'),
new Car(make: 'Toyota', name: 'Prius')
]]
You would generate the following XML (or HTML) output:
<cars>
<car make='Peugeot' name='508'/>
<car make='Toyota' name='Prius'/>
</cars>
By doing the following:
import groovy.text.markup.*
def config = new TemplateConfiguration()
def engine = new MarkupTemplateEngine(config)
def tmpl = engine.createTemplate(tpl)
System.out << tmpl.make(model)
You have useful methods available to your templates, like for including other templates:
// include another template
include template: 'foo.tpl'
// include raw content
include unescaped: 'raw.txt'
// escape & include
include escaped: 'to_escape.txt'
And if you want to have your model be type checked, you can either define the model types inside the template like so:
modelTypes = {
List<Car> cars
}
Or by using the dedicated template creation method:
def modelTypes = [cars: "List<Car>"]
def tmpl = engine.createTypeCheckedModelTemplate(tpl, modelTypes)
Note that this template engine is super fast as it’s statically compiled.
JUnit 4 GroovyAssert class
The
venerable GroovyTestCase
(JUnit 3 based approach) has often been used as a base class for your
test classes — unless you’ve been using
the Spock testing framework, of course.
One of the drawback of this class is that your test classes can’t extend
your own classes, but must derive from GroovyTestCase
to benefit from
the additional assertion methods.
In earlier versions of Groovy we introduced the JUnit
4-friendly GroovyAssert,
which is a convenient class offering the usual assertion methods of
GroovyTestCase
, but in the form of static methods that you can static
import in your test class. In Groovy 2.3, we’ve enriched GroovyAssert
with additional features. With these additions, there should be no functionality which is restricted to just JUnit 3,
so feel free to move to later versions if you haven’t already done so. We didn’t include all the
myriad of assertEquals
methods from GroovyTestCase
as they are typically
less useful than Groovy’s built-in power assert, but it provides some
handy shouldFail()
and assertScript()
methods
(GROOVY-6588).
For instance, if you want to leverage the shouldFail(String)
and
assertScript(String)
methods, you can do so as follows:
import static groovy.test.GroovyAssert.shouldFail
import org.junit.Test
class AssertTest {
@Test void checkBadAddition() {
shouldFail '''
groovy.test.GroovyAssert.assertScript 'assert 1 + 1 == 3'
'''
}
}
ConfigSlurper
ConfigSlurper has previously supported a single "environments" non-configurational conditional block, but you couldn’t define your own. With Groovy 2.3 you can also create your own such blocks. For instance if you wanted to support "flavors" like OS variants (GROOVY-6383).
Concretely, instead of the familiar environments / production blocks in Grails, let’s register a flavors / prod pair:
def conf = '''
a.b.c = 1
flavors {
prod {
a.b.c = 2
}
}
'''
def slurper = new ConfigSlurper('prod')
slurper.registerConditionalBlock('flavors', 'prod')
def config = slurper.parse(conf)
assert config.a.b.c == 2
In addition, the isSet()
/ hasSet()
combo methods
(GROOVY-4639) have been
added so you can double-check if a given node of your configuration has
been defined. Before, whether the node wasn’t defined or containing
null
, you couldn’t differentiate either case easily.
Tools enhancements
Groovysh
Along with a slightly reduced startup time, Groovysh has seen new improvements in its code-completion capabilities:
-
completion for keywords (GROOVY-6399)
-
completion for properties (GROOVY-6395)
Commands are now prefixed with ``:'' (link:https://issues.apache.org/jira/browse/GROOVY-6397).
GroovyConsole
It is now possible to configure the font used by the console
(GROOVY-6303, although
without a UI dialog yet), and also to be able to run a selected snippet
of code reusing the imports defined in your script making it easier to
just run quick snippets of your script. The ability to comment or
uncomment selected code by pressing Ctrl +
was added
with GROOVY-6459.
Documentation
New documentation
We are still working on the brand new documentation for Groovy (in Asciidoc(tor) format), so you can already have a glimpse at what’s already covered or not.
We’re looking forward to your help for fleshing out the various TBD ("To Be Done") sections of the documentation, as it’s a gigantic task to re-document each and every aspect of the language and its libraries! So please shout if you want to contribute to the new documentation! All help is warmly welcome!
Refreshed GroovyDoc documentation style
GroovyDoc has been updated with a new fresh and modern skin that will be part of the future visual identity of the Groovy website. Those style updates are also available by default for your own usage of GroovyDoc, making your own documentation nicer on the eye.
You can have a look at the GroovyDoc documentation for Groovy 2.3.0.
Refreshed Groovy GDK documentation style
We also took the opportunity to apply the same stylesheet to our
DocGenerator
tool which is responsible for the generation of the GDK
documentation, showing the methods the Groovy library adds on top of the
JDK classes.
Please also have a look at the new restyled GDK documentation.
Dependency upgrades
The following dependencies have been upgraded:
-
GPars 1.2 for all your concurrency, asynchronous or parallelism needs:
-
improvements in the dataflow area, such as lazy tasks and easy fork-and-join on Promises
-
actors and dataflow operators now use the Groovy
@DelegatesTo
annotation to allow for statically compiled bodies -
GPars timers and thread-locals have been made more friendly towards managed environments and the GParsConfig class now allows GPars to be completely shutdown
-
-
Gradle 1.10 for building Groovy
-
ASM 5.0.1 library for generating our bytecode (also needed for our JDK 8 support)
-
JLine 2.11 and JANSI 1.11 library for Groovysh
-
Ant 1.9.3 for the Ant builder
-
TestNG 6.8.8 for the TestNG module
Breaking changes
Groovy 2.3.0 introduces a limited list of breaking changes.
First of all,Groovy 2.3.0 now requires JDK 6 as its minimal JDK requirement. Some parts of Groovy 2.3.0 might still run under JDK 5 but no testing has been done on that platform and some parts are known not to work. We encourage everyone to move to at least JDK 6.
In Groovy 2.3.0, we reworked our implementation of generics handling. Although we don’t know of any particular breakage so far, the static type checker might report new errors as it can be stricter than before. If ever you encounter such new errors in this area, please report them as soon as you encounter them.
With the introduction of "traits" in Groovy 2.3, the trait
keyword
is an addition to the list of keyword of the languages, with the
consequence that variables or fields that would use trait
as name
with yield a compilation error. So you would have to change the name of
your variable and recompile your code.
A few updates have been made to the XML support around whitespace handling, and text node handling:
With the new default methods on interfaces in JDK 8, there was particularly one, a List#sort(Comparable) method, which conflicted with one of the GDK, so we had to remove ours to stay compliant with JDK 8.
We fixed a race condition in AbstractHttpServlet#applyResourceNameMatcher which incurred a small change in behavior. This feature is seldomly used and doesn’t seem to have impacted users of the Groovy servlet machinery so far.
You can look at the list of the breaking changes from our JIRA issue tracker.