Groovy Grape – Dependency Management for Scripts | 12 Tested Examples

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

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 like latest.release in production scripts
  • Use @GrabConfig(systemClassLoader=true) for JDBC drivers, logging frameworks, and SPI-based libraries
  • Use @GrabExclude to resolve conflicts between transitive dependencies
  • Pre-download dependencies with grape install if running scripts in offline or restricted network environments
  • Place all @Grab annotations 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 @Grab annotations 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 @GrabExclude to remove unwanted transitive dependencies
  • Use @GrabResolver to add custom Maven repositories (JitPack, Nexus, Artifactory)
  • Dependencies are cached in ~/.groovy/grapes/ – use grape list to inspect the cache
  • Grape works with groovy -e for 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.

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

RahulAuthor posts

Avatar for Rahul

Rahul is a passionate IT professional who loves to sharing his knowledge with others and inspiring them to expand their technical knowledge. Rahul's current objective is to write informative and easy-to-understand articles to help people avoid day-to-day technical issues altogether. Follow Rahul's blog to stay informed on the latest trends in IT and gain insights into how to tackle complex technical issues. Whether you're a beginner or an expert in the field, Rahul's articles are sure to leave you feeling inspired and informed.

No comment

Leave a Reply

Your email address will not be published. Required fields are marked *