This is an automated email from the ASF dual-hosted git repository. paulk pushed a commit to branch asf-site in repository https://gitbox.apache.org/repos/asf/groovy-website.git
commit 64a9e2ffd65bdeefe6e4ee6799bd8b465f7a8081 Author: Paul King <[email protected]> AuthorDate: Sat Mar 28 08:32:18 2026 +1000 add http build and loop transforms --- site/src/site/releasenotes/groovy-6.0.adoc | 196 ++++++++++++++++++++++++++++- 1 file changed, 195 insertions(+), 1 deletion(-) diff --git a/site/src/site/releasenotes/groovy-6.0.adoc b/site/src/site/releasenotes/groovy-6.0.adoc index 0f117cb..2bd23a7 100644 --- a/site/src/site/releasenotes/groovy-6.0.adoc +++ b/site/src/site/releasenotes/groovy-6.0.adoc @@ -152,9 +152,203 @@ to revert back to GrapeIvy. *If you have customized Ivy settings:* Your `~/.groovy/grapeConfig.xml` is only honoured by GrapeIvy. If switching to GrapeMaven, you will need to reconfigure any custom repositories or settings using `@GrabResolver` annotations or programmatically via the `Grape.addResolver()` API. +== HttpBuilder: Lightweight HTTP Client DSL (incubating) + +Groovy 6 introduces a new `groovy-http-builder` module +(https://issues.apache.org/jira/browse/GROOVY-11879[GROOVY-11879]) +providing a declarative DSL over the JDK's `java.net.http.HttpClient`. +It is designed for scripting and simple automation, +filling the gap left by the earlier HttpBuilder/HttpBuilder-NG libraries. + +=== Quick start + +Create a client with a base URI and start making requests: + +[source,groovy] +---- +import static groovy.http.HttpBuilder.http + +def client = http('https://api.github.com') +def result = client.get('/repos/apache/groovy') +assert result.json.license.name == 'Apache License 2.0' +---- + +=== Configuring the client + +Use a configuration closure to set default headers, timeouts, and redirect behavior: + +[source,groovy] +---- +import groovy.http.HttpBuilder +import java.time.Duration + +def client = HttpBuilder.http { + baseUri 'https://api.example.com' + header 'User-Agent', 'my-app/1.0' + connectTimeout Duration.ofSeconds(5) + followRedirects true +} +---- + +=== JSON POST + +[source,groovy] +---- +def result = client.post('/api/items') { + json([name: 'book', qty: 2]) +} +assert result.status == 200 +assert result.json.ok +---- + +=== Form POST + +[source,groovy] +---- +def result = client.post('/login') { + form(username: 'admin', password: 's3cret') +} +assert result.status == 200 +---- + +=== Query parameters + +[source,groovy] +---- +def result = client.get('/api/items') { + query page: 1, size: 10 +} +---- + +=== XML responses + +[source,groovy] +---- +def result = client.get('/api/repo.xml') +assert result.xml.license.text() == 'Apache License 2.0' +---- + +=== HTML scraping with jsoup + +When https://jsoup.org/[jsoup] is on the classpath, +HTML responses are automatically parsed into a jsoup `Document`: + +[source,groovy] +---- +@Grab('org.jsoup:jsoup:1.22.1') +import static groovy.http.HttpBuilder.http + +def client = http('https://example.com') +def result = client.get('/page') +def heading = result.html.select('h1').text() +---- + +=== Response parsing + +`HttpResult` provides convenient accessors for common content types: + +- `result.json` -- parsed via `JsonSlurper` +- `result.xml` -- parsed via `XmlSlurper` +- `result.html` -- parsed via jsoup (if available) +- `result.parsed` -- auto-dispatches based on the response `Content-Type` +- `result.body` -- the raw response body as a `String` + +== AST Transforms in More Places (incubating) + +Groovy 6 extends the AST transformation infrastructure to support +annotations on loop statements -- for-in, classic for, while, and do-while +(https://issues.apache.org/jira/browse/GROOVY-11878[GROOVY-11878]). +Since the JVM does not support annotations on statements in bytecode, +these are purely source-level transforms: the annotation drives +compile-time code generation and is then discarded. + +Two initial transforms take advantage of this capability: + +=== `@Invariant` on loops + +The existing `@Invariant` annotation from `groovy-contracts` can now be +placed on loops to assert a condition at the start of each iteration. +Violations throw a `LoopInvariantViolation`. + +[source,groovy] +---- +int sum = 0 +@Invariant({ sum <= 100 }) +for (int i in 1..5) { + sum += i +} +assert sum == 15 +---- + +This works with all loop types: + +[source,groovy] +---- +// classic for loop +int product = 1 +@Invariant({ product >= 1 }) +for (int i = 1; i <= 5; i++) { + product *= i +} +assert product == 120 + +// while loop +int n = 10 +@Invariant({ n >= 0 }) +while (n > 0) { + n-- +} +assert n == 0 + +// do-while loop +int count = 0 +@Invariant({ count >= 0 }) +do { + count++ +} while (count < 3) +assert count == 3 +---- + +Multiple invariants can be stacked on a single loop: + +[source,groovy] +---- +int sum = 0 +@Invariant({ sum >= 0 }) +@Invariant({ sum <= 100 }) +for (int i in 1..5) { + sum += i +} +assert sum == 15 +---- + +=== `@Parallel` on for-in loops + +The `@Parallel` transform runs each iteration of a for-in loop +in its own thread: + +[source,groovy] +---- +@Parallel +for (int i in 1..4) { + println i ** 2 +} +// Output (non-deterministic order): 1, 16, 9, 4 +---- + +NOTE: `@Parallel` is an incubating transform favoring simplicity. +Production use should consider proper concurrency mechanisms. + +=== Writing custom statement-level transforms + +Any source-retention AST transform can now target `STATEMENT_TARGET`. +The transform's `visit` method receives the `AnnotationNode` and the +`Statement` (a `LoopingStatement`) as its AST node pair, following the +same contract as existing class/method/field-level transforms. + == Under exploration -* Annotations in more places (source only), e.g. @Parallel, @Invariant on for loops + * Java compatibility: Module import declarations, additional destructuring * Improve REPL further (think nushell) * Performance
