Groovy Grape — Script Dependency Management
In this tutorial, you will learn about Groovy Grape. We cover key concepts, practical examples, and best practices to help you master this topic.
Groovy Grape (@Grab) lets scripts declare Maven dependencies inline, automatically resolving and downloading libraries from remote repositories at runtime without build tools.
What You'll Learn
- Using @Grab annotations
- Repository configuration
- Dependency resolution cache
- Version ranges and exclusions
Why It Matters
Grape makes scripts self-contained — no Maven/Gradle setup needed. Doda Browser uses Grape in its diagnostic scripts to pull the latest monitoring libraries without pre-installing them.
Real-World Use
Quick diagnostic scripts, demos, prototyping, security scanning scripts that need different libraries for different tasks, and CI/CD pipeline local scripts.
flowchart LR
A["@Grab"] --> B["Maven Central"]
B --> C["Download"]
C --> D["Cache"]
D --> E["Classpath"]
A --> F["@GrabResolver"]
F --> G["Custom Repo"]
style A fill:#2563eb,stroke:#2563eb,color:#fff
style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
style E fill:#dbeafe,stroke:#2563eb,color:#1e40af
Basic @Grab Usage
@Grab('com.google.code.gson:gson:2.10.1')
import com.google.gson.Gson
def gson = new Gson()
def data = [name: 'Alice', age: 30]
println gson.toJson(data)
The dependency is downloaded from Maven Central and added to the classpath automatically.
Multiple Dependencies
@Grab('org.apache.httpcomponents:httpclient:4.5.14')
@Grab('com.fasterxml.jackson.core:jackson-databind:2.15.2')
import org.apache.http.client.methods.HttpGet
import org.apache.http.impl.client.HttpClients
import com.fasterxml.jackson.databind.ObjectMapper
def client = HttpClients.createDefault()
def request = new HttpGet('https://api.github.com/users/octocat')
def response = client.execute(request)
def json = new ObjectMapper().readValue(response.entity.content, Map)
println "User: ${json.login}"
@GrabConfig
Configure Grape behavior:
@GrabConfig(systemClassLoader = true)
@Grab('mysql:mysql-connector-java:8.0.33')
import com.mysql.cj.jdbc.Driver
// Connector is loaded into system classloader
// which makes it available for DriverManager
def conn = DriverManager.getConnection(
'jdbc:mysql://localhost:3306/mydb',
'user', 'password'
)
println "Connected: ${conn.metaData.databaseProductVersion}"
@GrabResolver
Add custom Maven repositories:
@GrabResolver(name = 'my-repo', root = 'https://maven.mycompany.com/repository')
@Grab('com.mycompany:internal-lib:1.0.0')
import com.mycompany.internal.Library
def lib = new Library()
lib.process()
@GrabExclude
Exclude transitive dependencies:
@Grab('org.springframework:spring-core:6.0.11')
@GrabExclude('commons-logging:commons-logging')
import org.springframework.util.StopWatch
// commons-logging is excluded from transitive resolution
def sw = new StopWatch()
sw.start('task')
Thread.sleep(100)
sw.stop()
println "Time: ${sw.totalTimeMillis}ms"
Version Ranges
// Any version >= 2.0 and < 3.0
@Grab('com.google.guava:guava:[2.0,3.0)')
import com.google.common.collect.ImmutableList
def list = ImmutableList.of('a', 'b', 'c')
println list
Version ranges are resolved according to Maven's specification.
Grape Cache
// Dependencies are cached locally in ~/.groovy/grapes/
// Clear cache: rm -rf ~/.groovy/grapes/
def cacheDir = new File("${System.getProperty('user.home')}/.groovy/grapes")
if (cacheDir.exists()) {
println "Grape cache size: ${cacheDir.directorySize()} bytes"
}
Grape with CLI
// grape.groovy
import groovy.grape.Grape
// Programmatic usage
Grape.grab(group: 'com.google.code.gson', module: 'gson', version: '2.10.1')
println "Done"
Common Mistakes
1. Missing version string
// Wrong — no version
@Grab('com.google.guava:guava')
// Right
@Grab('com.google.guava:guava:32.1.3-jre')
2. Forgetting @GrabResolver for non-standard repos
Dependencies in private repos need explicit resolver configuration before @Grab.
3. ClassLoader conflicts
Libraries loaded by different classloaders (system vs Groovy) can cause ClassCastException. Use @GrabConfig(systemClassLoader=true) for JDBC drivers.
4. Slow first run
Grape downloads on first use. Cache clears or offline usage can cause delays. Pre-warm with a setup script.
5. Incompatible transitive dependencies
Maven enforcer rules are not applied by Grape. Test dependency resolution manually.
Practice Questions
1. What does @Grab do?
It declares a Maven dependency that Grape resolves and downloads at runtime, adding it to the classpath.
2. Where are Grape dependencies cached?
In ~/.groovy/grapes/ organized by group, module, and version.
3. How do you add a custom Maven repository?
Use @GrabResolver(name='repo', root='https://...') before the @Grab annotations.
4. What is the purpose of @GrabExclude?
It excludes a transitive dependency from resolution, useful when two libraries bring conflicting versions.
Challenge: Write a Groovy script using Grape to fetch JSON from a REST API and parse it without any pre-installed libraries.
FAQ
{{< faq question="Does Grape work in production?" >}} It is primarily designed for development and scripting. For production, use a proper build tool (Gradle/Maven) with explicit dependency declarations. {{< /faq >}}
{{< faq question="Can Grape resolve SNAPSHOT versions?" >}} Yes, but SNAPSHOT resolution is slower and Caching behavior differs. Avoid SNAPSHOTs in scripts. {{< /faq >}}
{{< faq question="What happens if a dependency fails to download?" >}}
The script throws a GrapeException. Wrap in try/catch for graceful fallback.
{{< /faq >}}
{{< faq question="Can I use Grape in compiled Groovy?" >}}
Yes, groovy.grape.Grape.grab() works in compiled code too, but it is unconventional.
{{< /faq >}}
{{< faq question="Is Grape threadsafe?" >}} Grape resolution is synchronized. Multiple scripts resolving the same dependency concurrently will wait. {{< /faq >}}
Mini Project
Create a Grape-powered script that analyzes a website's SSL certificate.
@Grab('org.jsoup:jsoup:1.16.1')
@Grab('com.fasterxml.jackson.core:jackson-databind:2.15.2')
import org.jsoup.Jsoup
import com.fasterxml.jackson.databind.ObjectMapper
def url = args[0] ?: 'https://example.com'
println "Analyzing $url..."
def doc = Jsoup.connect(url).get()
def title = doc.title()
def links = doc.select('a[href]').size()
def images = doc.select('img').size()
def report = [
url: url,
title: title,
links: links,
images: images,
timestamp: new Date().toString()
]
def json = new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(report)
println json
What's Next
Now that you understand Grape, proceed to GroovyServ.
| Topic | Description | Link |
|---|---|---|
| GroovyServ | Faster script startup | {{< ref "28-groovyserv" >}} |
| Scripting | Script execution patterns | {{< ref "25-scripting" >}} |
| Gradle | Build tool integration | {{< ref "17-gradle-integration" >}} |
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro