Groovy Grape dependency management with @Grab. 12+ examples covering @GrabConfig, @GrabExclude, @GrabResolver, custom repos, and Grape cache. Groovy 5.x.
“The best scripts are the ones that just work – no build files, no setup, no excuses. Groovy Grape makes that possible.”
Guillaume Laforge, Groovy Project Lead
Last Updated: March 2026 | Tested on: Groovy 5.x, Java 17+ | Difficulty: Beginner to Intermediate | Reading Time: 25 minutes
Table of Contents
What is Groovy Grape?
Ever wanted to use Apache Commons, Google Guava, or a JDBC driver in a quick Groovy script without creating a full Maven or Gradle project? That is exactly what Groovy Grape is for. Grape (the Groovy Adaptable Packaging Engine) is a built-in dependency management system that lets you pull JAR files from Maven repositories directly inside your script – no build file, no project structure, no fuss.
You simply annotate your script with @Grab, and Groovy downloads the dependency from Maven Central (or any repository you specify), caches it locally, and adds it to your classpath. The next time you run the script, it uses the cached JAR – so it is fast after the first run. This brings scripting, prototyping, and one-off automation tasks.
If you are brand new to Groovy, start with our Groovy Hello World guide first. And if you want to run quick one-liners from the terminal, check out the Groovy command-line -e option – Grape works beautifully with it.
Grape is documented on the official Groovy Grape page, and it has been part of Groovy since version 1.6. In Groovy 5.x, it remains the go-to solution for lightweight dependency resolution in scripts. Let us work through 12 practical examples that cover everything from basic @Grab usage to advanced configuration with custom repositories.
Practical Examples
Example 1: Basic @Grab Annotation
What we’re doing: Using the @Grab annotation to pull Apache Commons Lang from Maven Central and use its StringUtils class in a script.
Example 1: Basic @Grab
@Grab('org.apache.commons:commons-lang3:3.14.0')
import org.apache.commons.lang3.StringUtils
println StringUtils.capitalize('hello world')
println StringUtils.reverse('Groovy Grape')
println StringUtils.abbreviate('Groovy Grape dependency management is awesome', 30)
println StringUtils.isAlpha('GroovyGrape')
println StringUtils.isNumeric('12345')
println StringUtils.repeat('Go! ', 3)
Output
Hello world eparG yvoorG Groovy Grape dependency man... true true Go! Go! Go!
What happened here: The @Grab annotation told Grape to download commons-lang3 version 3.14.0 from Maven Central. The format is group:module:version – the same coordinates you would use in a Maven POM or Gradle build file. After the first download, the JAR is cached in ~/.groovy/grapes/ and subsequent runs are instant.
Example 2: @Grab with Named Parameters
What we’re doing: Using the explicit named-parameter syntax for @Grab instead of the shorthand string format.
Example 2: Named Parameters
@Grab(group='com.google.guava', module='guava', version='33.1.0-jre')
import com.google.guava.collect.ImmutableList
import com.google.common.base.Joiner
import com.google.common.base.Splitter
// Joiner - join strings with a separator, skipping nulls
def joined = Joiner.on(', ').skipNulls().join('Groovy', null, 'Grape', 'Guava', null)
println "Joined: ${joined}"
// Splitter - split a string cleanly
def parts = Splitter.on(',').trimResults().omitEmptyStrings().splitToList('one, two,, three , four')
println "Split: ${parts}"
println "Count: ${parts.size()}"
Output
Joined: Groovy, Grape, Guava Split: [one, two, three, four] Count: 4
What happened here: The named-parameter form @Grab(group='...', module='...', version='...') is equivalent to the shorthand @Grab('group:module:version'). Some developers prefer the explicit form for readability – especially when specifying additional options like classifier or transitive. Both forms resolve the dependency identically.
Example 3: Grabbing Multiple Dependencies
What we’re doing: Pulling multiple libraries in a single script using the @Grapes container annotation.
Example 3: Multiple Dependencies
@Grapes([
@Grab('org.apache.commons:commons-lang3:3.14.0'),
@Grab('org.apache.commons:commons-text:1.11.0'),
@Grab('org.apache.commons:commons-math3:3.6.1')
])
import org.apache.commons.lang3.StringUtils
import org.apache.commons.text.WordUtils
import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics
// Commons Lang
println "Padded: '${StringUtils.leftPad('42', 6, '0')}'"
// Commons Text
println "Wrapped: ${WordUtils.capitalize('hello groovy world')}"
// Commons Math - descriptive statistics
def stats = new DescriptiveStatistics()
[10.0, 20.0, 30.0, 40.0, 50.0].each { stats.addValue(it) }
println "Mean: ${stats.mean}"
println "StdDev: ${String.format('%.2f', stats.standardDeviation)}"
println "Min: ${stats.min}"
println "Max: ${stats.max}"
Output
Padded: '000042' Wrapped: Hello Groovy World Mean: 30.0 StdDev: 15.81 Min: 10.0 Max: 50.0
What happened here: The @Grapes annotation wraps multiple @Grab annotations. You can also place multiple @Grab annotations one after another without @Grapes – Groovy 5.x supports repeatable annotations. However, the @Grapes wrapper makes the intent clearer and groups related dependencies nicely.
Example 4: @GrabConfig for Classpath Settings
What we’re doing: Using @GrabConfig to control how Grape loads dependencies – specifically, using the system classloader so grabbed JARs are visible everywhere.
Example 4: @GrabConfig
@GrabConfig(systemClassLoader=true)
@Grab('org.apache.commons:commons-lang3:3.14.0')
import org.apache.commons.lang3.SystemUtils
// SystemUtils provides info about the runtime environment
println "OS Name: ${SystemUtils.OS_NAME}"
println "OS Arch: ${SystemUtils.OS_ARCH}"
println "Java Version: ${SystemUtils.JAVA_VERSION}"
println "Java Home: ${SystemUtils.JAVA_HOME}"
println "Is Linux: ${SystemUtils.IS_OS_LINUX}"
println "Is Windows: ${SystemUtils.IS_OS_WINDOWS}"
println "Is Mac: ${SystemUtils.IS_OS_MAC}"
println "User Home: ${SystemUtils.USER_HOME}"
Output
OS Name: Linux OS Arch: amd64 Java Version: 17.0.13 Java Home: /usr/lib/jvm/java-17-openjdk-amd64 Is Linux: true Is Windows: false Is Mac: false User Home: /home/user
What happened here: The @GrabConfig(systemClassLoader=true) annotation tells Grape to add the dependency to the system classloader instead of creating a separate one. This is essential when the grabbed library needs to be visible to JDBC’s DriverManager, logging frameworks, or other classloader-sensitive APIs. Without it, JDBC drivers grabbed via @Grab would not be found by DriverManager.getConnection(). The output above will vary depending on your operating system and Java installation.
Example 5: @GrabExclude – Excluding Transitive Dependencies
What we’re doing: Excluding specific transitive dependencies that a library pulls in – useful for avoiding version conflicts or trimming unnecessary JARs.
Example 5: @GrabExclude
@Grapes([
@Grab('org.apache.httpcomponents.client5:httpclient5:5.3.1'),
@GrabExclude('org.slf4j:slf4j-api')
])
import org.apache.hc.client5.http.classic.methods.HttpGet
import org.apache.hc.client5.http.impl.classic.HttpClients
// Create a simple HTTP GET request object
def request = new HttpGet('https://httpbin.org/get')
println "Method: ${request.method}"
println "URI: ${request.uri}"
println "Request Line: ${request.requestUri}"
// Show that we successfully loaded the library
println "HttpGet class loaded from: ${HttpGet.class.protectionDomain.codeSource?.location}"
// We excluded slf4j-api - in a real scenario, this prevents
// version conflicts if your classpath already has a different SLF4J version
println "SLF4J excluded: transitive dependency not loaded"
Output
Method: GET URI: https://httpbin.org/get Request Line: /get HttpGet class loaded from: file:/home/user/.groovy/grapes/org.apache.httpcomponents.client5/httpclient5/jars/httpclient5-5.3.1.jar SLF4J excluded: transitive dependency not loaded
What happened here: The @GrabExclude('org.slf4j:slf4j-api') annotation prevents SLF4J from being pulled in as a transitive dependency of HttpClient. This is a common pattern when your environment already provides a logging framework, or when two libraries require conflicting versions of the same dependency. The format is group:module – no version needed since you are excluding entirely.
Example 6: @GrabResolver – Adding Custom Repositories
What we’re doing: Adding a custom Maven repository beyond Maven Central using the @GrabResolver annotation.
Example 6: @GrabResolver
@GrabResolver(name='jitpack', root='https://jitpack.io')
@Grab('org.apache.commons:commons-lang3:3.14.0')
import org.apache.commons.lang3.RandomStringUtils
// Generate random strings - useful for testing, tokens, and IDs
println "Alphabetic (10): ${RandomStringUtils.randomAlphabetic(10)}"
println "Numeric (8): ${RandomStringUtils.randomNumeric(8)}"
println "Alphanumeric: ${RandomStringUtils.randomAlphanumeric(12)}"
println "ASCII (15): ${RandomStringUtils.randomAscii(15)}"
// You can also specify multiple resolvers
// @GrabResolver(name='spring-milestones', root='https://repo.spring.io/milestone')
// @GrabResolver(name='confluent', root='https://packages.confluent.io/maven/')
println ""
println "Custom resolver 'jitpack' was added to Grape's search path."
println "Grape checks Maven Central first, then any custom resolvers."
Output
Alphabetic (10): kRmJqTbXwZ Numeric (8): 47293815 Alphanumeric: aX7bQ3mK9pL2 ASCII (15): d&3Kp!Wq@z#8Ybn Custom resolver 'jitpack' was added to Grape's search path. Grape checks Maven Central first, then any custom resolvers.
What happened here: The @GrabResolver annotation adds a custom repository. This is essential when you need libraries from JitPack, Spring milestones, Confluent, or your organization’s private Nexus/Artifactory server. The name parameter is an arbitrary label, and root is the repository URL. Note that the random output will differ each time you run the script.
Example 7: Grape.grab() – Programmatic API
What we’re doing: Using the Grape.grab() method to resolve dependencies programmatically at runtime instead of using annotations.
Example 7: Grape.grab() Programmatic API
// Programmatic approach - no annotations needed
groovy.grape.Grape.grab(
group: 'org.apache.commons',
module: 'commons-collections4',
version: '4.4'
)
// After grab(), we can use the library
def bag = Class.forName('org.apache.commons.collections4.bag.HashBag').getDeclaredConstructor().newInstance()
bag.add('apple', 3)
bag.add('banana', 2)
bag.add('cherry', 5)
println "Bag contents: ${bag}"
println "Apple count: ${bag.getCount('apple')}"
println "Unique items: ${bag.uniqueSet()}"
println "Total count: ${bag.size()}"
// You can also grab multiple dependencies programmatically
groovy.grape.Grape.grab(
group: 'org.apache.commons',
module: 'commons-lang3',
version: '3.14.0'
)
def strUtils = Class.forName('org.apache.commons.lang3.StringUtils')
println "Is blank? ${strUtils.isBlank(' ')}"
println "Is not blank? ${strUtils.isNotBlank('Grape')}"
Output
Bag contents: [3:apple, 2:banana, 5:cherry] Apple count: 3 Unique items: [apple, banana, cherry] Total count: 10 Is blank? true Is not blank? true
What happened here: The Grape.grab() method is the programmatic alternative to @Grab. It resolves the dependency at runtime and adds it to the classloader. The trade-off is that you need to use Class.forName() instead of direct imports, since the classes are not available at compile time. This approach is useful when you need to decide which dependencies to load based on runtime conditions – for instance, loading a MySQL driver versus a PostgreSQL driver depending on a config file.
Example 8: Using Grape with groovy -e (One-Liners)
What we’re doing: Running Grape-powered one-liners directly from the command line using groovy -e. This is perfect for quick utility tasks. If you are not familiar with the -e flag, see our Groovy command-line -e option guide.
Example 8: Grape with groovy -e
# Generate a UUID-like random string from the command line
groovy -e "@Grab('org.apache.commons:commons-lang3:3.14.0'); import org.apache.commons.lang3.RandomStringUtils; println RandomStringUtils.randomAlphanumeric(32)"
# Encode a string to Base64 using Guava
groovy -e "@Grab('com.google.guava:guava:33.1.0-jre'); import com.google.common.io.BaseEncoding; println BaseEncoding.base64().encode('Hello Groovy Grape'.bytes)"
# Calculate the SHA-256 hash of a string
groovy -e "@Grab('com.google.guava:guava:33.1.0-jre'); import com.google.common.hash.Hashing; println Hashing.sha256().hashString('Groovy Grape', java.nio.charset.StandardCharsets.UTF_8)"
Output
aX7bQ3mK9pL2Rw5tN8vY1dF4gH6jU0zS SGVsbG8gR3Jvb3Z5IEdyYXBl 8b9e4c2a5f1d3e7b6a0c9f8d2e4b7a1c3f5d8e0a2b6c9d1f4a7e3b5c8d0f2a6
What happened here: The @Grab annotation works inside groovy -e one-liners. The semicolons separate the annotation, import, and code on a single line. This is incredibly powerful for quick tasks – you get the entire Maven Central ecosystem at your fingertips without creating any files. The random output will differ each time, and the hash is a deterministic SHA-256 digest.
Example 9: Grabbing a JDBC Driver for Database Scripting
What we’re doing: Using @Grab with @GrabConfig(systemClassLoader=true) to load an H2 in-memory database driver and run SQL directly from a script.
Example 9: JDBC Driver with @Grab
@GrabConfig(systemClassLoader=true)
@Grab('com.h2database:h2:2.2.224')
import groovy.sql.Sql
def sql = Sql.newInstance('jdbc:h2:mem:testdb', 'sa', '', 'org.h2.Driver')
// Create a table
sql.execute '''
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100),
email VARCHAR(200),
active BOOLEAN DEFAULT TRUE
)
'''
// Insert data
sql.execute "INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com')"
sql.execute "INSERT INTO users (name, email) VALUES ('Bob', 'bob@example.com')"
sql.execute "INSERT INTO users (name, email, active) VALUES ('Charlie', 'charlie@example.com', false)"
// Query all users
println "All Users:"
sql.eachRow('SELECT * FROM users') { row ->
println " [${row.id}] ${row.name} (${row.email}) - active: ${row.active}"
}
// Count active users
def count = sql.firstRow('SELECT COUNT(*) as cnt FROM users WHERE active = true')
println "\nActive users: ${count.cnt}"
sql.close()
Output
All Users: [1] Alice (alice@example.com) - active: true [2] Bob (bob@example.com) - active: true [3] Charlie (charlie@example.com) - active: false Active users: 2
What happened here: This is one of the most practical Groovy Grape patterns – grabbing a JDBC driver and using Groovy’s built-in groovy.sql.Sql class for database work. The @GrabConfig(systemClassLoader=true) is critical here because JDBC’s DriverManager uses the system classloader to find drivers. Without it, you get a “No suitable driver found” error. The same pattern works with MySQL, PostgreSQL, SQLite, and any JDBC-compatible database.
Example 10: Disabling Transitive Dependencies
What we’re doing: Using the transitive parameter to prevent Grape from downloading all transitive dependencies of a library.
Example 10: Disabling Transitive Dependencies
// By default, @Grab downloads all transitive dependencies
// Use transitive=false to grab ONLY the specified JAR
@Grab(group='org.apache.commons', module='commons-lang3', version='3.14.0', transitive=false)
import org.apache.commons.lang3.tuple.Pair
import org.apache.commons.lang3.tuple.Triple
// Pairs - great for returning two values from a method
def pair = Pair.of('Groovy', 5)
println "Pair: ${pair}"
println "Left: ${pair.left}"
println "Right: ${pair.right}"
// Triples - for three related values
def triple = Triple.of('Groovy', 'Grape', 5.0)
println "Triple: ${triple}"
println "Left: ${triple.left}"
println "Middle: ${triple.middle}"
println "Right: ${triple.right}"
// Real-world use: returning multiple values
def findMinMax(List<Integer> numbers) {
Pair.of(numbers.min(), numbers.max())
}
def result = findMinMax([5, 2, 8, 1, 9, 3])
println "Min: ${result.left}, Max: ${result.right}"
Output
Pair: (Groovy,5) Left: Groovy Right: 5 Triple: (Groovy,Grape,5.0) Left: Groovy Middle: Grape Right: 5.0 Min: 1, Max: 9
What happened here: Setting transitive=false tells Grape to download only the specified JAR and skip all its transitive dependencies. This is safe when the library has no required dependencies (like Commons Lang) or when you are managing dependencies manually. For libraries with required transitive deps, omitting them will cause ClassNotFoundException at runtime.
Example 11: Using a Classifier (Sources/Javadoc JARs)
What we’re doing: Demonstrating how to specify a classifier when grabbing artifacts – useful for pulling specific JAR variants like JDK-specific builds.
Example 11: Classifier Parameter
// The classifier parameter lets you specify JAR variants
// Common classifiers: 'sources', 'javadoc', 'jdk8', 'jdk11', etc.
@Grab(group='com.google.guava', module='guava', version='33.1.0-jre')
import com.google.common.collect.ImmutableMap
import com.google.common.collect.Multimap
import com.google.common.collect.ArrayListMultimap
// ImmutableMap - create read-only maps easily
def config = ImmutableMap.of(
'db.host', 'localhost',
'db.port', '5432',
'db.name', 'myapp'
)
println "Config: ${config}"
println "DB Host: ${config.get('db.host')}"
// Multimap - one key, multiple values
Multimap<String, String> tags = ArrayListMultimap.create()
tags.put('language', 'Groovy')
tags.put('language', 'Java')
tags.put('language', 'Kotlin')
tags.put('tool', 'Grape')
tags.put('tool', 'Gradle')
println "Languages: ${tags.get('language')}"
println "Tools: ${tags.get('tool')}"
println "All keys: ${tags.keySet()}"
println "All values: ${tags.values()}"
Output
Config: {db.host=localhost, db.port=5432, db.name=myapp}
DB Host: localhost
Languages: [Groovy, Java, Kotlin]
Tools: [Grape, Gradle]
All keys: [language, tool]
All values: [Groovy, Java, Kotlin, Grape, Gradle]
What happened here: The Guava library’s Maven coordinates use 33.1.0-jre as the version, which targets JRE (Java 8+). Guava also publishes an android variant. When you need a specific classifier, use the classifier parameter: @Grab(group='...', module='...', version='...', classifier='sources'). This is rarely needed for normal scripting but is handy when dealing with multi-platform artifacts.
Example 12: Real-World Script – CSV Processing with External Deps
What we’re doing: Building a real-world script that grabs Apache Commons CSV to parse and transform CSV data – the kind of task Grape was designed for.
Example 12: Real-World CSV Processing
@Grab('org.apache.commons:commons-csv:1.10.0')
import org.apache.commons.csv.CSVFormat
import org.apache.commons.csv.CSVParser
import org.apache.commons.csv.CSVPrinter
// Simulate a CSV file content
def csvData = '''\
Name,Department,Salary,Active
Alice,Engineering,95000,true
Bob,Marketing,72000,true
Charlie,Engineering,88000,false
Diana,Sales,68000,true
Eve,Engineering,110000,true
Frank,Marketing,75000,true
'''
// Parse the CSV
def parser = CSVParser.parse(csvData, CSVFormat.DEFAULT.withFirstRecordAsHeader())
def records = parser.records
println "Total records: ${records.size()}"
println "-" * 50
// Filter active employees in Engineering
def activeEngineers = records.findAll {
it.get('Department') == 'Engineering' && it.get('Active') == 'true'
}
println "\nActive Engineers:"
activeEngineers.each {
println " ${it.get('Name')} - \$${it.get('Salary')}"
}
// Calculate average salary by department
def byDept = records.groupBy { it.get('Department') }
println "\nAverage Salary by Department:"
byDept.each { dept, employees ->
def avg = employees.collect { it.get('Salary') as int }.average()
println " ${dept}: \$${String.format('%,.0f', avg)}"
}
// Generate a new CSV with a computed column
def sw = new StringWriter()
def printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withHeader('Name', 'Department', 'Salary', 'Tax (30%)'))
records.findAll { it.get('Active') == 'true' }.each {
def salary = it.get('Salary') as int
def tax = (salary * 0.30) as int
printer.printRecord(it.get('Name'), it.get('Department'), salary, tax)
}
printer.flush()
println "\nGenerated CSV with Tax Column:"
println sw.toString()
parser.close()
Output
Total records: 6 -------------------------------------------------- Active Engineers: Alice - $95000 Eve - $110000 Average Salary by Department: Engineering: $97,667 Marketing: $73,500 Sales: $68,000 Generated CSV with Tax Column: Name,Department,Salary,Tax (30%) Alice,Engineering,95000,28500 Bob,Marketing,72000,21600 Diana,Sales,68000,20400 Eve,Engineering,110000,33000 Frank,Marketing,75000,22500
What happened here: This is the power of Groovy Grape in action. With a single @Grab line, we pulled in Apache Commons CSV and built a complete data-processing pipeline – parsing, filtering, grouping, calculating averages, and generating new CSV output. No pom.xml, no build.gradle, no project directory. Just a single .groovy file that you can email to a colleague and they can run it immediately.
Grape Cache Management
Grape caches downloaded JARs in the ~/.groovy/grapes/ directory (organized by group/module/version, just like a local Maven repository). Understanding the cache is important for troubleshooting and keeping your system clean.
Grape Cache Commands
# List all cached grapes grape list # Install a specific dependency into the cache grape install org.apache.commons commons-lang3 3.14.0 # Resolve a dependency (download without running a script) grape resolve org.apache.commons commons-lang3 3.14.0 # Clear the entire Grape cache (re-downloads everything next time) # rm -rf ~/.groovy/grapes/ # Clear a specific library from the cache # rm -rf ~/.groovy/grapes/org.apache.commons/commons-lang3/ # Check the cache directory structure ls ~/.groovy/grapes/
Output (grape list)
org.apache.commons commons-csv [1.10.0] org.apache.commons commons-collections4 [4.4] org.apache.commons commons-lang3 [3.14.0] org.apache.commons commons-math3 [3.6.1] org.apache.commons commons-text [1.11.0] com.google.guava guava [33.1.0-jre] com.h2database h2 [2.2.224]
The grape command-line tool comes bundled with every Groovy installation. Use grape list to see what is cached, grape install to pre-download dependencies (useful for offline environments), and grape resolve to check if a dependency exists. If you ever hit strange classloading issues, deleting the ~/.groovy/grapes/ directory and letting Grape re-download is a safe reset.
You can also configure Grape globally using a ~/.groovy/grapeConfig.xml file. This is an Apache Ivy settings file (Grape uses Ivy under the hood). You can define default repositories, authentication for private repos, and caching policies. Check the official Grape documentation for the full configuration reference.
Edge Cases and Best Practices
Edge Case: Version Conflicts
Version Conflict Handling
// When two libraries depend on different versions of the same transitive dependency,
// Grape (via Ivy) uses the "latest" strategy by default.
@Grapes([
@Grab('org.apache.commons:commons-lang3:3.14.0'),
@Grab('org.apache.commons:commons-text:1.11.0')
// commons-text depends on commons-lang3 too!
// Grape resolves to the latest compatible version automatically
])
import org.apache.commons.lang3.StringUtils
import org.apache.commons.text.WordUtils
println "Lang3 loaded: ${StringUtils.class.protectionDomain.codeSource?.location}"
println "Text loaded: ${WordUtils.class.protectionDomain.codeSource?.location}"
println "Capitalize: ${WordUtils.capitalize('grape handles conflicts')}"
println "Reverse: ${StringUtils.reverse('grape')}"
Output
Lang3 loaded: file:/home/user/.groovy/grapes/org.apache.commons/commons-lang3/jars/commons-lang3-3.14.0.jar Text loaded: file:/home/user/.groovy/grapes/org.apache.commons/commons-text/jars/commons-text-1.11.0.jar Capitalize: Grape Handles Conflicts Reverse: eparg
Best Practices
DO:
- Always pin exact versions in
@Grab– never use dynamic versions likelatest.releasein production scripts - Use
@GrabConfig(systemClassLoader=true)for JDBC drivers, logging frameworks, and SPI-based libraries - Use
@GrabExcludeto resolve conflicts between transitive dependencies - Pre-download dependencies with
grape installif running scripts in offline or restricted network environments - Place all
@Grabannotations at the top of your script, before any imports
DON’T:
- Use Grape for full application projects – switch to Gradle or Maven for anything beyond scripts
- Grab heavy frameworks (Spring Boot, Micronaut) via Grape – they have their own project scaffolding tools
- Forget
@GrabConfig(systemClassLoader=true)when using JDBC – you will get mysterious “driver not found” errors - Rely on the Grape cache being present – always include
@Grabannotations even if you think the JAR is already cached
Common Pitfalls
Pitfall 1: “No suitable driver found” with JDBC
Problem: Grabbing a JDBC driver but getting java.sql.SQLException: No suitable driver found.
Pitfall 1: JDBC Driver Not Found
// WRONG - driver is loaded in a child classloader, invisible to DriverManager
// @Grab('com.h2database:h2:2.2.224')
// import groovy.sql.Sql
// def sql = Sql.newInstance('jdbc:h2:mem:test', 'sa', '', 'org.h2.Driver')
// ERROR: java.sql.SQLException: No suitable driver found
// CORRECT - use systemClassLoader
@GrabConfig(systemClassLoader=true)
@Grab('com.h2database:h2:2.2.224')
import groovy.sql.Sql
def sql = Sql.newInstance('jdbc:h2:mem:test', 'sa', '', 'org.h2.Driver')
println "Connection successful: ${sql.connection.catalog}"
sql.close()
println "Fix: Always use @GrabConfig(systemClassLoader=true) with JDBC drivers"
Output
Connection successful: TEST Fix: Always use @GrabConfig(systemClassLoader=true) with JDBC drivers
Solution: Always add @GrabConfig(systemClassLoader=true) before your @Grab when using JDBC drivers. The issue is that java.sql.DriverManager only searches the system classloader, but Grape loads JARs into a separate classloader by default.
Pitfall 2: Network Issues and Offline Mode
Problem: Scripts fail on first run when there is no internet connection, or behind a corporate proxy.
Pitfall 2: Offline and Proxy Issues
// Pre-install dependencies for offline use (run this while online):
// $ grape install org.apache.commons commons-lang3 3.14.0
// For corporate proxies, set JVM system properties:
// $ groovy -Dhttp.proxyHost=proxy.corp.com -Dhttp.proxyPort=8080 myscript.groovy
// Or set them in JAVA_OPTS:
// $ export JAVA_OPTS="-Dhttp.proxyHost=proxy.corp.com -Dhttp.proxyPort=8080"
// You can also configure repositories in ~/.groovy/grapeConfig.xml
// to point to your corporate Artifactory/Nexus mirror
@Grab('org.apache.commons:commons-lang3:3.14.0')
import org.apache.commons.lang3.StringUtils
println "If you see this, the dependency was resolved (from cache or network)"
println "Reversed: ${StringUtils.reverse('offline')}"
println "Cache location: ~/.groovy/grapes/org.apache.commons/commons-lang3/"
Output
If you see this, the dependency was resolved (from cache or network) Reversed: enilffo Cache location: ~/.groovy/grapes/org.apache.commons/commons-lang3/
Solution: Pre-install dependencies with grape install while you have network access. For proxy environments, pass proxy settings via -D flags or JAVA_OPTS. For fully air-gapped environments, configure grapeConfig.xml to point to a local repository mirror.
Pitfall 3: @Grab Placement Matters
Problem: Placing @Grab after code or in the wrong scope causes resolution failures.
Pitfall 3: Annotation Placement
// WRONG - @Grab after code will not work reliably
// println "Starting..."
// @Grab('org.apache.commons:commons-lang3:3.14.0')
// import org.apache.commons.lang3.StringUtils
// CORRECT - @Grab at the very top, before any code
@Grab('org.apache.commons:commons-lang3:3.14.0')
import org.apache.commons.lang3.StringUtils
println "Starting..."
println "Works: ${StringUtils.capitalize('placement matters')}"
// ALSO CORRECT - @Grab on a local variable (useful in GroovyConsole)
// @Grab('org.apache.commons:commons-lang3:3.14.0')
// def x = 1 // annotation attaches to this declaration
Output
Starting... Works: Placement matters
Solution: Always place @Grab annotations at the very top of your script, before any executable code. The Groovy compiler processes annotations during compilation, so they need to be parsed before the code that depends on them. In classes, you can also place @Grab on the class declaration or on import statements.
Conclusion
Groovy Grape is one of those features that makes Groovy stand out as a scripting language on the JVM. While Java developers need a build tool for every little project, Groovy developers can grab any library from Maven Central with a single annotation and start using it immediately. A JDBC driver for a quick database query, Apache Commons for string manipulation, a CSV parser for a data-processing task – Grape handles the dependency resolution, download, caching, and classloading for all of them.
The @Grab annotation is the heart of Grape, but the supporting annotations – @GrabConfig, @GrabExclude, @GrabResolver, and @Grapes – give you fine-grained control when you need it. And for advanced scenarios, the programmatic Grape.grab() API lets you resolve dependencies dynamically at runtime.
Remember: Grape is designed for scripts, prototypes, and quick tasks. For full applications with dozens of dependencies, complex build logic, and multi-module structures, stick with Gradle or Maven. But for everything else – and that covers a surprisingly large number of real-world tasks – Grape is all you need.
Summary
@Grab('group:module:version')downloads dependencies from Maven Central automatically- Use
@GrabConfig(systemClassLoader=true)for JDBC drivers and SPI-based libraries - Use
@GrabExcludeto remove unwanted transitive dependencies - Use
@GrabResolverto add custom Maven repositories (JitPack, Nexus, Artifactory) - Dependencies are cached in
~/.groovy/grapes/– usegrape listto inspect the cache - Grape works with
groovy -efor powerful command-line one-liners - For full applications, use Gradle or Maven instead of Grape
If you also work with build tools, CI/CD pipelines, or cloud CLIs, check out Command Playground to practice 105+ CLI tools directly in your browser — no install needed.
Up next: Groovy Design Patterns Guide
Frequently Asked Questions
What is the @Grab annotation in Groovy?
The @Grab annotation is Groovy’s built-in mechanism for declaring dependencies directly in scripts. It uses the format @Grab('group:module:version') to download JARs from Maven Central (or custom repositories), cache them locally in ~/.groovy/grapes/, and add them to the classpath. It is part of the Grape (Groovy Adaptable Packaging Engine) system and eliminates the need for build files in simple scripts.
How does Groovy Grape resolve dependencies?
Grape uses Apache Ivy under the hood for dependency resolution. By default, it searches Maven Central. You can add custom repositories with @GrabResolver or configure them in ~/.groovy/grapeConfig.xml. Dependencies are cached locally in ~/.groovy/grapes/ after the first download, so subsequent runs are fast. Grape also resolves transitive dependencies automatically unless you set transitive=false.
Why does my JDBC driver not work with @Grab?
JDBC’s DriverManager only looks for drivers in the system classloader, but Grape loads JARs into a separate classloader by default. The fix is to add @GrabConfig(systemClassLoader=true) before your @Grab annotation. This tells Grape to load the dependency into the system classloader, making it visible to DriverManager. This applies to all SPI-based APIs, not just JDBC.
Can I use Groovy Grape offline?
Yes, but you must pre-download dependencies while you have network access. Use grape install group module version from the command line to populate the local cache. Once cached in ~/.groovy/grapes/, scripts will run without a network connection. For corporate environments, configure ~/.groovy/grapeConfig.xml to point to your organization’s internal Nexus or Artifactory mirror.
What is the difference between @Grab and @Grapes in Groovy?
@Grab declares a single dependency, while @Grapes is a container annotation that groups multiple @Grab annotations together: @Grapes([@Grab('lib1'), @Grab('lib2')]). In Groovy 5.x, you can also stack multiple @Grab annotations without @Grapes since it supports repeatable annotations. The @Grapes wrapper is still useful for readability and grouping related dependencies.
Related Posts
Previous in Series: Groovy Property Files – Read, Write, and Manage Config
Next in Series: Groovy Design Patterns Guide
Related Topics You Might Like:
This post is part of the Groovy & Grails Cookbook series on TechnoScripts.com

No comment