{"slug": "groovy-6-writing-less-code-and-saying-more", "title": "Groovy 6: Writing Less Code and Saying More", "summary": "Groovy 6 adds a new HTTP Builder module, async/await concurrency, for await streaming, contracts and additional compiler support to the JVM language, according to an article detailing the release. The article illustrates the changes with a customer-report service that fetches data from four backend APIs, showing that an @HttpBuilderClient interface can return a CompletableFuture and that async/await starts the four independent calls concurrently before await constructs the report. The article argues that making program semantics explicit helps both developers and AI coding agents.", "body_md": "Groovy is a JVM language that combines Java interoperability with an expressive syntax, supporting both static and dynamic typing. Groovy 6 builds on that foundation with modern concurrency, streaming, HTTP clients, contracts and additional compiler support. This article looks at those capabilities through one small example — a service that assembles a customer report — and at why making program semantics explicit can help both developers and AI coding agents.\n\n**In this article:**\n\nGroovy 6 brings together several changes that make everyday code easier to write, but some of the most interesting improvements are not about saving keystrokes. They are about making the structure and guarantees of a program more explicit.\n\nConsider a small service that assembles a customer report. It needs customer details, orders, incidents, and usage data from four backend APIs.\n\n```\nCustomerReport createReport(String customerId) {\n    def customer  = customerApi.get(customerId)\n    def orders    = orderApi.findFor(customerId)\n    def incidents = incidentApi.findFor(customerId)\n    def usage     = usageApi.events(customerId)\n\n    new CustomerReport(\n        customer: customer,\n        orders: orders,\n        incidents: incidents,\n        usage: usage\n    )\n}\n```\n\nThere is nothing particularly Groovy 6-specific here. It is ordinary application code. The interesting question is how much ceremony is needed when the APIs are asynchronous, the usage data is large, and the resulting objects have rules that callers should not violate.\n\n## \n\nGroovy 6 includes a new HTTP Builder module. Its declarative client lets an API be described as an interface:\n\n```\n@HttpBuilderClient('https://customers.example')\ninterface CustomerApi {\n    @Get('/customers/{id}')\n    Customer get(String id)\n}\n```\n\nThe interface describes the endpoint, path and return type. There is no client implementation in the example.\n\nThe same API can return a `CompletableFuture` when the report should fetch several independent resources concurrently:\n\n```\n@HttpBuilderClient('https://customers.example')\ninterface CustomerApi {\n    @Get('/customers/{id}')\n    CompletableFuture<Customer> get(String id)\n}\n```\n\nThat changes the report method without changing the API description itself.\n\n## \n\nThe four backend calls do not depend on one another. Running them one after another means the total time is dominated by the sum of their latencies.\n\nGroovy 6’s `async`/` await` let the code express the concurrency without turning the method into a future-composition exercise:\n\n```\nCustomerReport createReport(String customerId) {\n    def customer  = async { customerApi.get(customerId) }\n    def orders    = async { orderApi.findFor(customerId) }\n    def incidents = async { incidentApi.findFor(customerId) }\n    def usage     = async { calculateUsage(usageApi.events(customerId)) }\n\n    def (customerData, orderData, incidentData, usageData) =\n        await(customer, orders, incidents, usage)\n\n    new CustomerReport(\n        customer: customerData,\n        orders: orderData,\n        incidents: incidentData,\n        usage: usageData\n    )\n}\n```\n\nThe four operations start independently, and `await` waits for all of them before constructing the report. \nThere is no need to turn the report into a chain of `thenApply`, `thenCompose` and `join` calls, or to make its control flow follow the mechanics of `CompletableFuture`.\n\n`groovy.concurrent` also provides agents, actors, dataflow variables, channels and parallel collection operations. \nDevelopers familiar with GPars will recognise many of these concepts; Groovy 6 brings them into its own concurrency toolkit.\n\n## \n\nUsage events may be numerous enough that collecting them all before calculating the summary is unnecessary. \nGroovy 6 adds `for await`, so an asynchronous sequence can be consumed as events arrive.\n\n``` python\ndef calculateUsage(events) {\n    def total = 0\n    def exports = 0\n\n    for await (event in events) {\n        total++\n\n        if (event.type == 'export') {\n            exports++\n        }\n    }\n\n    new UsageSummary(total, exports)\n}\n```\n\nThe report still gets a `UsageSummary`, but the calculation does not require the complete event stream to be materialised first.\n\n## \n\nThe report method has an assumption that is easy to miss from its implementation alone: `customerId` must identify a customer. \nA contract makes that assumption explicit.\n\n```\n@Requires({ !customerId.empty })\nCustomerReport createReport(String customerId) {\n    ...\n}\n```\n\nThere are similar rules for the data produced by `calculateUsage`. \nA usage summary cannot contain negative counts, and the number of exports cannot exceed the total number of events.\n\n```\n@Immutable\n@Invariant({ total >= 0 && exports >= 0 && exports <= total })\nclass UsageSummary {\n    int total\n    int exports\n}\n```\n\nDevelopers who have used GContracts will recognise the Design by Contract approach. \nGContracts provided this model as a separate Groovy project; `groovy-contracts` replaced that external project in Groovy 4 and graduates from incubation in Groovy 6.\n\nThese annotations are more than documentation. Groovy Contracts transforms them into assertions during compilation, so violations are checked when the resulting code runs. Groovy also provides type-checking extensions that can establish additional properties at compile time, including nullability, purity and allowed modifications.\n\nGroovy is still often described simply as a dynamic language, which leaves out a substantial part of what the language offers. \n`@TypeChecked` and `@CompileStatic` have been available for years, allowing developers to opt into static type checking and static compilation where they want it. \nGroovy 6 extends the information available to the compiler without forcing the whole language into a single programming model. \nDevelopers can therefore choose how much of a program’s semantics they want the compiler to establish.\n\n## \n\nBefore changing `createReport`, a developer needs to know what callers are allowed to pass to it. \nBefore changing `UsageSummary`, they need to know which states are valid. \nWithout explicit contracts, those facts may be spread across implementations, callers, tests and documentation. \nUnderstanding the code means reconstructing the rules from those sources.\n\nWith contracts, some of that knowledge is part of the code itself:\n\n```\n@Requires({ !customerId.empty })\nCustomerReport createReport(String customerId) {\n    ...\n}\n\n@Immutable\n@Invariant({ total >= 0 && exports >= 0 && exports <= total })\nclass UsageSummary {\n    ...\n}\n```\n\nAn AI coding agent faces the same problem when it enters an unfamiliar repository. Before making a change, it has to build a model of the surrounding code and its constraints. Explicit contracts provide some of that model without requiring the agent to inspect every implementation path from which the same facts could otherwise be inferred.\n\nThat can reduce the amount of source placed into context and the reasoning needed to recover its constraints. \nThe Groovy project’s article [“Groovy 6 for humans and AI agents”](https://groovy.apache.org/blog/groovy6-ai-friendly) demonstrates this with an example that reduces 288 tokens of source to 83 tokens of verified specification, without opening the implementation bodies. \nThe numbers are specific to that example rather than a general compression ratio, but they show the effect.\n\nThe important difference between such a specification and a summary generated by an AI agent is that the specification is part of the program and can be checked against the implementation. Tools can consume the compact representation without losing that connection to the source.\n\n## \n\nThe features used in our customer report cover only part of the Groovy 6 release.\n\nGroovy 6 adds optional modules for HTTP, CSV and Markdown, along with integrations for Reactor and RxJava. \nGINQ has graduated from incubation, while the language gains features such as `val`, richer destructuring, compound-assignment operator overloading and nested `copyWith` support.\n\nThere is substantial work underneath the language as well, including improvements to AST transformations, compiler diagnostics, generated bytecode, invokedynamic dispatch and allocation behaviour.\n\nThe complete set of changes is covered in the [Groovy 6.0 release notes](https://groovy-lang.org/releasenotes/groovy-6.0.html).\n\nOur customer report started as four ordinary backend calls. Groovy 6 lets us run them concurrently, consume a large result as a stream, and describe properties of the resulting code in a form the compiler can check.\n\nThose properties no longer have to remain assumptions that a developer or a tool reconstructs from the implementation. They can become part of the program itself.\n\nThe result is not just less code. It is code that says more.\n\n## \n\nDo you have questions about Agentic Engineering, Testing of LLM-based applications or specific developer topics? [Feel free to reach out](https://dev.karakun.com/people/jochen). I’m always happy to exchange knowledge, ideas, and experiences.", "url": "https://wpnews.pro/news/groovy-6-writing-less-code-and-saying-more", "canonical_source": "https://dev.karakun.com/2026/09/24/Groovy-6-Writing-Less-Code.html", "published_at": "2026-09-24 00:00:00+00:00", "updated_at": "2026-09-30 12:20:07.607527+00:00", "lang": "en", "topics": ["developer-tools", "artificial-intelligence"], "entities": ["Groovy 6", "Groovy", "Java", "CompletableFuture", "GPars", "groovy.concurrent", "HttpBuilderClient"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/groovy-6-writing-less-code-and-saying-more", "markdown": "https://wpnews.pro/news/groovy-6-writing-less-code-and-saying-more.md", "text": "https://wpnews.pro/news/groovy-6-writing-less-code-and-saying-more.txt", "jsonld": "https://wpnews.pro/news/groovy-6-writing-less-code-and-saying-more.jsonld"}}