blob: ce0e923ef45581b627d4c2d4abd18a0936a465d6 [file] [log] [blame]
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.
[[Groovy2.3releasenotes-OfficialsupportforrunningGroovyonJDK8]]
== 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 were not planning to backport the changes to older versions
of Groovy, so if you want to run Groovy on JDK 8, youll have to upgrade
to the shiniest version of Groovy!
Groovy 2.3 doesnt 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:
[source,groovy]
----
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.
[[Groovy2.3releasenotes-Traits]]
== Traits
A major highlight for Groovy 2.3 is the introduction of the *concept of traits*.
link:{DOCS_BASEURL}/html/documentation/core-traits.html[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`.
[source,groovy]
----
trait FlyingAbility {
String fly() { "I'm flying!" }
}
----
Then we create a class, `Bird`, that implements that trait, and
instantiate it:
[source,groovy]
----
class Bird implements FlyingAbility {}
def b = new Bird()
----
We can check that the Bird instance does have the new `fly()` method
mixed-in:
[source,groovy]
----
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:
[source,groovy]
----
trait Named {
String name
}
----
This time, the `Bird` class implements that `Named` trait:
[source,groovy]
----
class Bird implements Named {}
----
We can instantiate the Bird with the named-argument constructor shortcut
provided by Groovy:
[source,groovy]
----
def b = new Bird(name: 'Colibri')
----
We assert that the instantiated Bird does have the name property added
to it:
[source,groovy]
----
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:
[source,groovy]
----
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 its 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:
[source,groovy]
----
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 (ie. 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
youre instantiating, without having to create an intermediary
artificial class just for that purpose (also called per-instance
traits):
[source,groovy]
----
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 link:{DOCS_BASEURL}/html/documentation/core-traits.html[traits documentation].
[[Groovy2.3releasenotes-NewandupdatedASTtransformations]]
== New and updated AST transformations
[[Groovy2.3releasenotes-Newtransformations]]
=== New transformations
[[Groovy2.3releasenotes-TailRecursive]]
==== @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
(link:https://issues.apache.org/jira/browse/GROOVY-6570[GROOVY-6570]).
Heres a slightly rewritten factorial implementation, that is friendly
to tail-call transformation:
[source,groovy]
----
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
----
[[Groovy2.3releasenotes-Builder]]
==== @Builder
Recent Java APIs have adopted the builder pattern (not to be confused
with Groovys 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
(link:https://issues.apache.org/jira/browse/GROOVY-6484[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
Heres an example with the default strategy:
[source,groovy]
-----
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 link:{DOCS_BASEURL}/html/documentation/core-metaprogramming.html#xform-Builder[@Builder documentation]
for the other builder variants.
[[Groovy2.3releasenotes-Sortable]]
==== @Sortable
`@Sortable` on classes implements comparison methods for you (through
implementing the `Comparable` interface), according to the declaration
order of your properties
(link:https://issues.apache.org/jira/browse/GROOVY-6649[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:
[source,groovy]
----
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 {DOCS_BASEURL}/html/documentation/core-metaprogramming.html#xform-Sortable[@Sortable documentation] page.
[[Groovy2.3releasenotes-SourceURI]]
==== @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:
[source,groovy]
----
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, youll 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`.
[[Groovy2.3releasenotes-Updatedtransformations]]
=== Updated transformations
[[Groovy2.3releasenotes-Delegateimprovements]]
==== @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
(link:https://issues.apache.org/jira/browse/GROOVY-6329[GROOVY-6329]).
[[Groovy2.3releasenotes-BaseScriptclassimprovements]]
==== @BaseScript class improvements
{DOCS_BASEURL}/html/gapi/groovy/transform/BaseScript.html[@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
(link:https://issues.apache.org/jira/browse/GROOVY-6592[GROOVY-6592]) to indicate
that base script class:
[source,groovy]
----
@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
(link:https://issues.apache.org/jira/browse/GROOVY-6585[GROOVY-6585]
and link:https://issues.apache.org/jira/browse/GROOVY-6615[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()`:
[source,groovy]
----
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:
[source,groovy]
----
import groovy.transform.BaseScript
@BaseScript CustomBase script
println 'Hello'
----
[[Groovy2.3releasenotes-NewNIOmoduleforJava7]]
== 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 link:https://issues.apache.org/jira/browse/GROOVY-6377[GROOVY-6377] and
the link:https://github.com/groovy/groovy-core/pull/260/files[pull request]
for some further hints of the new methods.
Youll find familiar methods of the Groovy GDK on `File` also available on
`Path` like these ones:
[source,groovy]
----
path.withReader { Reader r -> ... }
path.eachLine { String line -> ... }
path.eachFileRecurse { Path p -> ... }
path << 'some content'
path << bytes
path.readLines()
----
[[Groovy2.3releasenotes-Performanceimprovements]]
== Performance improvements
[[Groovy2.3releasenotes-Miscellanousimprovements]]
=== Miscellanous improvements
Various minor *performance improvements across the board*, for static
compilation, the `invokedynamic` backend, as well as "normal"
dynamic Groovy, have been worked on.
[[Groovy2.3releasenotes-DrasticJSONparsingandserializationperformanceimprovements]]
=== Drastic JSON parsing and serialization performance improvements
Groovy JSON support has been refactored and tailored towards
performance, making Groovy 2.3s 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 seralization, in
a link:http://rick-hightower.blogspot.fr/2014/04/groovy-and-boon-provide-fastest-json.html[benchmarks on Rick’s blog]
and on link:https://github.com/bura/json-benchmarks[Andrey’s JSON benchmark project on Github].
The results are impressive, as the *parsing is generally roughly 2x to 4x faster* with Groovys new
parsers compared to existing libraries, and *~21x faster than pre-Groovy 2.3 parsing*.
On the serialization front, Groovys
new *serialization is also ~17x faster than before*, and at the same
level as competing libraries.
[[Groovy2.3releasenotes-JSONslurperandbuilderenhancements]]
== JSON slurper and builder enhancements
Beside the performance improvements of the JSON module, other updates
have taken place.
With link:{DOCS_BASEURL}/html/gapi/groovy/json/JsonSlurper.html[JsonSlurper],
youll be able to set
different link:{DOCS_BASEURL}/html/gapi/groovy/json/JsonParserType.html[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.
Heres an example showing how to parse a non-conformant JSON payload:
[source,groovy]
----
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
----
[[Groovy2.3releasenotes-Closureparametertypeinference]]
== 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 its no longer required:
[source,groovy]
----
['a','b'].each { it.toUpperCase() }
----
In the signature of your methods taking closures as arguments, youll
also be able to annotate the closure parameter
with link:{DOCS_BASEURL}/html/gapi/groovy/transform/stc/ClosureParams.html[@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édrics blog post
on link:http://melix.github.io/blog/2014/01/closure_param_inference.html[closure parameter type inference].
[[Groovy2.3releasenotes-Newmarkuptemplateengine]]
== 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 link:{DOCS_BASEURL}/html/documentation/markup-template-engine.html[Markup template engine]
in the documentation, as well as in Cédrics link:http://melix.github.io/blog/[blog], if you want to learn more
about the "behind the scenes" stories!
To illustrate the basic usage, consider you have the following template:
[source,groovy]
----
def tpl = '''
cars {
cars.each {
car(make: it.make, name: it.name)
}
}
'''
----
And have the following model:
[source,groovy]
----
model = [cars: [
new Car(make: 'Peugeot', name: '508'),
new Car(make: 'Toyota', name: 'Prius')
]]
----
You would generate the following XML (or HTML) output:
[source,xml]
----
<cars>
<car make='Peugeot' name='508'/>
<car make='Toyota' name='Prius'/>
</cars>
----
By doing the following:
[source,groovy]
----
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:
[source,groovy]
----
// 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:
[source,groovy]
----
modelTypes = {
List<Car> cars
}
----
Or by using the dedicated template creation method:
[source,groovy]
----
def modelTypes = [cars: "List<Car>"]
def tmpl = engine.createTypeCheckedModelTemplate(tpl, modelTypes)
----
Note that this template engine is super fast as its statically
compiled.
[[Groovy2.3releasenotes-JUnit4GroovyAssertclass]]
== JUnit 4 GroovyAssert class
The
venerable link:{DOCS_BASEURL}/html/gapi/groovy/util/GroovyTestCase.html[GroovyTestCase]
(JUnit 3 based approach) has often been used as a base class for your
test classes unless youve been using
the link:http://www.spockframework.org/[Spock testing framework], of course.
One of the drawback of this class is that your test classes cant 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 link:{DOCS_BASEURL}/html/gapi/groovy/util/GroovyAssert.html[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 weve enriched `GroovyAssert`
with additional features. There should be no reason to move on from
JUnit 3 if you havent already done so. We didnt include all of the
myriad of `assertEquals` methods from `GroovyTestCase` as they are typically
less useful than Groovys built-in power assert, but it provides some
handy `shouldFail()` and `assertScript()` methods
(link:https://issues.apache.org/jira/browse/GROOVY-6588[GROOVY-6588]).
For instance, if you want to leverage the `shouldFail(String)` and
`assertScript(String)` methods, you can do so as follows:
[source,groovy]
----
import static groovy.test.GroovyAssert.shouldFail
import org.junit.Test
class AssertTest {
@Test void checkBadAddition() {
shouldFail '''
groovy.test.GroovyAssert.assertScript 'assert 1 + 1 == 3'
'''
}
}
----
[[Groovy2.3releasenotes-ConfigSlurper]]
== ConfigSlurper
ConfigSlurper has previously supported a single "environments"
non-configurational conditional block, but you couldnt 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
(link:https://issues.apache.org/jira/browse/GROOVY-6383[GROOVY-6383]).
Concretely, instead of the familiar environments / production blocks in
Grails, lets register a flavors / prod pair
[source,groovy]
----
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
(link:https://issues.apache.org/jira/browse/GROOVY-4639[GROOVY-4639]) have been
added so you can double check if a given node of your configuration has
been defined. Before, whether the node wasnt defined or containing
`null`, you couldnt differentiate either case easily.
[[Groovy2.3releasenotes-Toolsenhancements]]
== Tools enhancements
[[Groovy2.3releasenotes-Groovysh]]
=== Groovysh
Along with a slightly reduced startup time, Groovysh has seen new
improvements in its code-completion capabilities:
* completion for keywords
(link:https://issues.apache.org/jira/browse/GROOVY-6399[GROOVY-6399])
* completion for properties
(link:https://issues.apache.org/jira/browse/GROOVY-6395[GROOVY-6395])
Commands are now prefixed with ``:''
(link:https://issues.apache.org/jira/browse/GROOVY-6397).
[[Groovy2.3releasenotes-GroovyConsole]]
=== GroovyConsole
It is now possible to configure the font used by the console
(link:https://issues.apache.org/jira/browse/GROOVY-6303[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 link:https://issues.apache.org/jira/browse/GROOVY-6459[GROOVY-6459].
[[Groovy2.3releasenotes-Documentation]]
== Documentation
[[Groovy2.3releasenotes-Newdocumentation]]
=== New documentation
We are still working on the
brand link:{DOCS_BASEURL}/html/documentation/[new documentation] for Groovy
(in Asciidoc(tor) format), so you can already
have a glimpse at whats already covered or not.
Were looking forward to your help for fleshing out the various TBD ("To
Be Done") sections of the documentation, as its 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!
[[Groovy2.3releasenotes-RefreshedGroovyDocdocumentationstyle]]
=== 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 link:http://docs.groovy-lang.org/2.3.0/html/gapi/[GroovyDoc
documentation for Groovy 2.3.0].
[[Groovy2.3releasenotes-RefreshedGroovyGDKdocumentationstyle]]
=== 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 link:{DOCS_BASEURL}/html/groovy-jdk/[restyled GDK documentation].
[[Groovy2.3releasenotes-Dependencyupgrades]]
== 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
[[Groovy2.3releasenotes-Breakingchanges]]
== 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 dont 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*:
* https://issues.apache.org/jira/browse/GROOVY-6685[GROOVY-6685]
* https://issues.apache.org/jira/browse/GROOVY-6683[GROOVY-6683]
* https://issues.apache.org/jira/browse/GROOVY-6682[GROOVY-6682]
* https://issues.apache.org/jira/browse/GROOVY-6678[GROOVY-6678]
* https://issues.apache.org/jira/browse/GROOVY-6621[GROOVY-6621]
With the new default methods on interfaces in JDK 8, there was
particularly one,
a link:https://issues.apache.org/jira/browse/GROOVY-6465[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 link:https://issues.apache.org/jira/browse/GROOVY-6456[race condition in AbstractHttpServler#applyResourceNameMatcher]
which incurred a small change in behavior. This feature is seldomly used and doesnt seem to
have impacted users of the Groovy servlet machinery so far.
You can look at the list of
the link:https://issues.apache.org/jira/browse/GROOVY-6685?jql=project%20%3D%20GROOVY%20AND%20fixVersion%20in%20%28%222.3.0-rc-3%22%2C%20%222.3.0-beta-1%22%2C%20%222.3.0-beta-2%22%2C%20%222.3.0-rc-1%22%2C%20%222.3.0-rc-2%22%29%20AND%20labels%20%3D%20breaking%20AND%20status%20in%20%28Resolved%2C%20Closed%29[breaking changes from our JIRA]
issue tracker.