Working with Groovy file operations is surprisingly concise. See 12 practical examples covering read, write, delete, append, directory traversal, and more. Tested on Groovy 5.x.
“A program that doesn’t interact with files is like a carpenter who never picks up wood. Files are where real work happens.”
Brian Kernighan, Unix Philosophy
Last Updated: March 2026 | Tested on: Groovy 5.x, Java 17+ | Difficulty: Beginner to Intermediate | Reading Time: 20 minutes
Java file I/O means wrapping everything in try-catch-finally, managing streams, and remembering to close resources. It works, but it’s verbose. Groovy file operations take a completely different approach – reading a file is a single property access, writing is one assignment, and iterating over lines is a closure. The GDK adds dozens of methods directly to java.io.File.
In this tutorial, we’ll explore every major file operation Groovy offers – reading, writing, appending, deleting, directory traversal, temporary files, and file metadata. Every example is tested on Groovy 5.x and ready to use in your projects. If you’re just getting started with Groovy’s dynamic features, our def keyword guide covers the fundamentals you’ll need.
Table of Contents
Why Groovy for File Operations?
Before we jump into examples, let’s understand what makes Groovy’s file handling special. According to the official Groovy GDK documentation, Groovy adds methods directly to java.io.File that dramatically simplify I/O tasks.
Key Advantages:
- One-liner reads:
new File('data.txt').textreads the entire file - Automatic resource management:
withReader(),withWriter()close resources automatically - Closure-based iteration:
eachLine(),eachFile()make loops readable - No boilerplate: No try-catch-finally blocks needed for basic operations
- Full Java compatibility: All Java file APIs still work alongside Groovy enhancements
Here is a quick comparison to understand why Groovy is a better choice for file scripting:
Java vs Groovy File Read
// Java way - verbose and boilerplate-heavy
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("data.txt"));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) reader.close();
}
// Groovy way - clean and concise
new File('data.txt').eachLine { println it }
That’s the difference. One line vs fifteen lines. Same result. Let’s dig deeper.
Reading Files in Groovy
Groovy gives you multiple ways to read files, each suited to different scenarios. Here’s a quick reference before we get into the examples:
| Method | Returns | Best For |
|---|---|---|
file.text | Entire file as String | Small files, quick reads |
file.readLines() | List of Strings | Line-by-line processing |
file.eachLine { } | void (iterates) | Large files, memory efficient |
file.withReader { } | Closure result | Custom BufferedReader logic |
file.bytes | byte[] | Binary files |
Writing Files in Groovy
Writing is just as easy. Groovy provides properties and methods for both overwriting and appending:
| Method | Behavior | Best For |
|---|---|---|
file.text = "..." | Overwrites entire file | Quick writes |
file.append("...") | Adds to end of file | Logging, data accumulation |
file.withWriter { } | BufferedWriter with auto-close | Multi-line writes |
file.newWriter() | Returns a BufferedWriter | Manual control |
file.bytes = [...] | Writes raw bytes | Binary data |
12 Practical Examples
Example 1: Read Entire File with File.text
What we’re doing: Reading the complete content of a file using Groovy’s text property – the simplest way to groovy read file content.
Example 1: File.text Property
// First, create a sample file to work with
def file = new File('sample.txt')
file.text = 'Line 1: Hello Groovy\nLine 2: File operations are easy\nLine 3: This is amazing'
// Now read the entire file
def content = new File('sample.txt').text
println content
println "---"
println "Type: ${content.getClass().name}"
println "Length: ${content.length()} characters"
Output
Line 1: Hello Groovy Line 2: File operations are easy Line 3: This is amazing --- Type: java.lang.String Length: 69 characters
What happened here: The text property reads the entire file into a single String. This is Groovy’s GDK magic – Java’s File class doesn’t have a text property, but Groovy adds it. It handles opening the file, reading all content, and closing the resource. Perfect for small to medium files.
Example 2: Read File Line by Line with eachLine()
What we’re doing: Iterating through each line of a file using a closure – memory-efficient for large files.
Example 2: eachLine()
def file = new File('sample.txt')
file.text = 'Apple\nBanana\nCherry\nDate\nElderberry'
// eachLine with just the line
println "=== Lines ==="
file.eachLine { line ->
println "Fruit: ${line}"
}
// eachLine with line number (1-based)
println "\n=== With Line Numbers ==="
file.eachLine { line, lineNo ->
println "${lineNo}: ${line}"
}
Output
=== Lines === Fruit: Apple Fruit: Banana Fruit: Cherry Fruit: Date Fruit: Elderberry === With Line Numbers === 1: Apple 2: Banana 3: Cherry 4: Date 5: Elderberry
What happened here: The eachLine() method reads one line at a time, passing it to your closure. Unlike text which loads everything into memory, eachLine() is streamed – making it suitable for large files. The two-parameter version gives you the line number starting at 1.
Example 3: Read Lines into a List with readLines()
What we’re doing: Getting all lines as a List for random access or collection operations.
Example 3: readLines()
def file = new File('sample.txt')
file.text = 'Red\nGreen\nBlue\nYellow\nPurple'
def lines = file.readLines()
println "Total lines: ${lines.size()}"
println "First line: ${lines[0]}"
println "Last line: ${lines[-1]}"
println "Type: ${lines.getClass().name}"
// Use collection methods
println "\nSorted: ${lines.sort()}"
println "Filtered (5+ chars): ${lines.findAll { it.length() >= 5 }}"
Output
Total lines: 5 First line: Red Last line: Purple Type: java.util.ArrayList Sorted: [Blue, Green, Purple, Red, Yellow] Filtered (5+ chars): [Green, Yellow, Purple]
What happened here: The readLines() method returns a List<String>, which means you can use all of Groovy’s powerful collection methods – sort(), findAll(), collect(), and more. Note how Groovy’s negative indexing (lines[-1]) gives you the last element directly.
Example 4: Read with BufferedReader using withReader()
What we’re doing: Using a BufferedReader with automatic resource management for custom read logic.
Example 4: withReader()
def file = new File('sample.txt')
file.text = 'server=prod-01\nport=8080\ntimeout=30\ndebug=false\nmax_connections=100'
// withReader automatically closes the reader when done
def config = [:]
file.withReader { reader ->
String line
while ((line = reader.readLine()) != null) {
def parts = line.split('=')
if (parts.length == 2) {
config[parts[0].trim()] = parts[1].trim()
}
}
}
println "Parsed config:"
config.each { key, value ->
println " ${key} = ${value}"
}
println "\nServer: ${config.server}"
println "Port: ${config.port}"
Output
Parsed config: server = prod-01 port = 8080 timeout = 30 debug = false max_connections = 100 Server: prod-01 Port: 8080
What happened here: The withReader() method creates a BufferedReader and passes it to your closure. When the closure finishes – whether normally or with an exception – the reader is automatically closed. No try-finally needed. This is Groovy’s equivalent of Java’s try-with-resources, but cleaner.
Example 5: Write to a File
What we’re doing: Writing content to a file using multiple approaches – the text property, withWriter(), and newWriter().
Example 5: Writing Files
// Method 1: Using text property (simplest)
def file1 = new File('output1.txt')
file1.text = 'Hello from Groovy!\nThis overwrites everything.'
println "Method 1 - text property:"
println file1.text
// Method 2: Using withWriter (auto-closes)
def file2 = new File('output2.txt')
file2.withWriter('UTF-8') { writer ->
writer.writeLine 'First line'
writer.writeLine 'Second line'
writer.writeLine 'Third line'
}
println "\nMethod 2 - withWriter:"
println file2.text
// Method 3: Using newWriter (manual control)
def file3 = new File('output3.txt')
def writer = file3.newWriter()
writer.write('Manual writer control\n')
writer.write('Remember to close!\n')
writer.close()
println "Method 3 - newWriter:"
println file3.text
// Cleanup
[file1, file2, file3].each { it.delete() }
Output
Method 1 - text property: Hello from Groovy! This overwrites everything. Method 2 - withWriter: First line Second line Third line Method 3 - newWriter: Manual writer control Remember to close!
What happened here: Three different ways to write files. The text property is the simplest – just assign a string. The withWriter() method is preferred for multi-line writes because it handles resource cleanup. The newWriter() method gives you manual control but you must remember to close it yourself.
Pro Tip: Always prefer
withWriter()overnewWriter()in production code. It guarantees the stream is closed even if an exception occurs.
Example 6: Append to a File
What we’re doing: Adding content to the end of an existing file without overwriting.
Example 6: Appending
def logFile = new File('app.log')
// Start fresh
logFile.text = ''
// Append log entries
logFile.append("[2026-03-08 10:00:00] INFO - Application started\n")
logFile.append("[2026-03-08 10:00:01] INFO - Loading configuration\n")
logFile.append("[2026-03-08 10:00:02] WARN - Config file missing, using defaults\n")
logFile.append("[2026-03-08 10:00:05] INFO - Server listening on port 8080\n")
logFile.append("[2026-03-08 10:01:30] ERROR - Connection timeout to database\n")
println "Log file contents:"
println logFile.text
println "Log file size: ${logFile.length()} bytes"
// Append with encoding
logFile.append("[2026-03-08 10:02:00] INFO - Retrying connection...\n", 'UTF-8')
println "Lines after append: ${logFile.readLines().size()}"
logFile.delete()
Output
Log file contents: [2026-03-08 10:00:00] INFO - Application started [2026-03-08 10:00:01] INFO - Loading configuration [2026-03-08 10:00:02] WARN - Config file missing, using defaults [2026-03-08 10:00:05] INFO - Server listening on port 8080 [2026-03-08 10:01:30] ERROR - Connection timeout to database Log file size: 253 bytes Lines after append: 6
What happened here: The append() method adds content to the end of a file. Unlike the text property which overwrites everything, append() preserves existing content. You can also specify encoding as a second parameter. This is ideal for logging, audit trails, or any data accumulation scenario.
Example 7: Delete Files and Check Existence
What we’re doing: Checking if files exist before operations, and deleting files safely.
Example 7: Delete and Exists
// Create some test files
def file1 = new File('temp1.txt')
def file2 = new File('temp2.txt')
def file3 = new File('nonexistent.txt')
file1.text = 'Temporary file 1'
file2.text = 'Temporary file 2'
// Check existence
println "temp1.txt exists: ${file1.exists()}"
println "temp2.txt exists: ${file2.exists()}"
println "nonexistent.txt exists: ${file3.exists()}"
// Safe delete pattern
[file1, file2, file3].each { f ->
if (f.exists()) {
def deleted = f.delete()
println "Deleted ${f.name}: ${deleted}"
} else {
println "Skipped ${f.name}: file not found"
}
}
// Verify deletion
println "\nAfter deletion:"
println "temp1.txt exists: ${file1.exists()}"
println "temp2.txt exists: ${file2.exists()}"
Output
temp1.txt exists: true temp2.txt exists: true nonexistent.txt exists: false Deleted temp1.txt: true Deleted temp2.txt: true Skipped nonexistent.txt: file not found After deletion: temp1.txt exists: false temp2.txt exists: false
What happened here: The exists() method checks whether a file is present on disk. The delete() method returns true if the file was successfully deleted, false otherwise. Always check exists() before deleting to avoid silent failures or unexpected behavior.
Example 8: Create Directories with mkdir() and mkdirs()
What we’re doing: Creating single directories and nested directory structures.
Example 8: Directory Creation
// Create a single directory
def dir1 = new File('testdir')
println "Created 'testdir': ${dir1.mkdir()}"
println "Is directory: ${dir1.isDirectory()}"
// Create nested directories (mkdirs creates parent dirs too)
def dir2 = new File('parent/child/grandchild')
println "Created nested dirs: ${dir2.mkdirs()}"
println "Nested dir exists: ${dir2.exists()}"
// Create a file inside the directory
def file = new File('testdir/hello.txt')
file.text = 'Hello from inside a directory!'
println "File in dir: ${file.text}"
// Check if path is file or directory
println "\ntestdir is file: ${dir1.isFile()}"
println "testdir is directory: ${dir1.isDirectory()}"
println "hello.txt is file: ${file.isFile()}"
// Cleanup
file.delete()
dir1.delete()
new File('parent/child/grandchild').delete()
new File('parent/child').delete()
new File('parent').delete()
Output
Created 'testdir': true Is directory: true Created nested dirs: true Nested dir exists: true File in dir: Hello from inside a directory! testdir is file: false testdir is directory: true hello.txt is file: true
What happened here: There’s an important difference between mkdir() and mkdirs(). The mkdir() method creates only the final directory and fails if parent directories don’t exist. The mkdirs() method creates the entire path, including any missing parent directories – similar to mkdir -p in Unix.
Example 9: List Files in a Directory
What we’re doing: Listing files in a directory using listFiles(), eachFile(), and eachDir().
Example 9: List Files
// Setup test directory structure
def baseDir = new File('project')
baseDir.mkdirs()
new File('project/src').mkdirs()
new File('project/lib').mkdirs()
new File('project/README.md').text = '# Project'
new File('project/build.gradle').text = 'apply plugin: java'
new File('project/app.groovy').text = 'println "hello"'
new File('project/src/Main.groovy').text = 'class Main {}'
// listFiles() - returns array of File objects
println "=== listFiles() ==="
baseDir.listFiles().each { println "${it.name} (${it.isDirectory() ? 'DIR' : 'FILE'})" }
// eachFile - only direct children
println "\n=== eachFile ==="
baseDir.eachFile { file ->
println file.name
}
// eachDir - only directories
println "\n=== eachDir ==="
baseDir.eachDir { dir ->
println "Directory: ${dir.name}"
}
// eachFileMatch - filter by pattern
println "\n=== Groovy files only ==="
baseDir.eachFileMatch(~/.*\.groovy/) { file ->
println file.name
}
// Cleanup
new File('project/src/Main.groovy').delete()
new File('project/src').delete()
new File('project/lib').delete()
['README.md', 'build.gradle', 'app.groovy'].each { new File("project/$it").delete() }
baseDir.delete()
Output
=== listFiles() === src (DIR) lib (DIR) README.md (FILE) build.gradle (FILE) app.groovy (FILE) === eachFile === src lib README.md build.gradle app.groovy === eachDir === Directory: src Directory: lib === Groovy files only === app.groovy
What happened here: Groovy adds several traversal methods to File. The eachFile() method iterates over direct children, eachDir() iterates over subdirectories only, and eachFileMatch() filters using a regex pattern or other Groovy matching strategies. Notice how eachFileMatch(~/.*\.groovy/) uses a slashy regex to find only .groovy files.
Example 10: Recursive Directory Traversal with eachFileRecurse()
What we’re doing: Walking through an entire directory tree, including all subdirectories.
Example 10: Recursive Traversal
import groovy.io.FileType
// Setup a nested directory structure
def root = new File('webapp')
new File('webapp/src/main/groovy').mkdirs()
new File('webapp/src/test/groovy').mkdirs()
new File('webapp/resources').mkdirs()
new File('webapp/src/main/groovy/App.groovy').text = 'class App {}'
new File('webapp/src/main/groovy/Utils.groovy').text = 'class Utils {}'
new File('webapp/src/test/groovy/AppTest.groovy').text = 'class AppTest {}'
new File('webapp/resources/config.yml').text = 'server: 8080'
new File('webapp/build.gradle').text = 'apply plugin: groovy'
// Recurse through ALL files (not directories)
println "=== All Files (Recursive) ==="
root.eachFileRecurse(FileType.FILES) { file ->
println file.path.replace('\\', '/')
}
// Recurse through directories only
println "\n=== All Directories ==="
root.eachFileRecurse(FileType.DIRECTORIES) { dir ->
println dir.path.replace('\\', '/')
}
// eachDirRecurse - shortcut for directories
println "\n=== Using eachDirRecurse ==="
root.eachDirRecurse { dir ->
println " ${dir.name}"
}
// Cleanup
root.deleteDir()
Output
=== All Files (Recursive) === webapp/src/main/groovy/App.groovy webapp/src/main/groovy/Utils.groovy webapp/src/test/groovy/AppTest.groovy webapp/resources/config.yml webapp/build.gradle === All Directories === webapp/src webapp/src/main webapp/src/main/groovy webapp/src/test webapp/src/test/groovy webapp/resources === Using eachDirRecurse === src main groovy test groovy resources
What happened here: The eachFileRecurse() method walks through the entire directory tree. You pass FileType.FILES to get only files, FileType.DIRECTORIES for only directories, or FileType.ANY for both. Also notice deleteDir() – a GDK method that recursively deletes a directory and all its contents. Much easier than manually deleting files bottom-up.
Example 11: Temporary Files
What we’re doing: Creating and using temporary files that automatically get a unique name.
Example 11: Temporary Files
// Create a temp file with prefix and suffix
def tempFile = File.createTempFile('groovy_', '.tmp')
println "Temp file: ${tempFile.absolutePath}"
println "Temp file name: ${tempFile.name}"
// Write data to temp file
tempFile.text = 'Temporary data for processing'
println "Content: ${tempFile.text}"
// Create temp file in a specific directory
def customDir = new File('temp_work')
customDir.mkdirs()
def tempFile2 = File.createTempFile('data_', '.csv', customDir)
tempFile2.text = 'id,name,score\n1,Alice,95\n2,Bob,87'
println "\nCustom temp file: ${tempFile2.name}"
println "Content:\n${tempFile2.text}"
// Mark for deletion when JVM exits
tempFile.deleteOnExit()
tempFile2.deleteOnExit()
println "\nTemp dir: ${System.getProperty('java.io.tmpdir')}"
// Cleanup
tempFile.delete()
tempFile2.delete()
customDir.delete()
Output
Temp file: /tmp/groovy_1234567890.tmp Temp file name: groovy_1234567890.tmp Content: Temporary data for processing Custom temp file: data_9876543210.csv Content: id,name,score 1,Alice,95 2,Bob,87 Temp dir: /tmp
What happened here: The File.createTempFile() method generates a file with a unique name using the given prefix and suffix. The three-argument version lets you specify the directory. The deleteOnExit() method registers the file for automatic cleanup when the JVM shuts down – useful for test data or intermediate processing files.
Example 12: File Properties – Size, Last Modified, Permissions
What we’re doing: Inspecting file metadata including size, modification time, and permissions.
Example 12: File Properties
def file = new File('metadata_test.txt')
file.text = 'This is a test file with some content for metadata inspection.'
println "=== File Properties ==="
println "Name: ${file.name}"
println "Absolute path: ${file.absolutePath}"
println "Parent: ${file.parent}"
println "Size: ${file.length()} bytes"
println "Last modified: ${new Date(file.lastModified())}"
println "Is file: ${file.isFile()}"
println "Is directory: ${file.isDirectory()}"
println "Is hidden: ${file.isHidden()}"
println "Can read: ${file.canRead()}"
println "Can write: ${file.canWrite()}"
println "Can execute: ${file.canExecute()}"
// Get file extension (Groovy doesn't have a built-in, but easy to do)
def extension = file.name.tokenize('.').last()
println "Extension: ${extension}"
// Human-readable size
def sizeKB = file.length() / 1024.0
println "Size (KB): ${String.format('%.2f', sizeKB)}"
// Modify last modified time
file.setLastModified(0)
println "\nAfter setLastModified(0): ${new Date(file.lastModified())}"
file.delete()
Output
=== File Properties === Name: metadata_test.txt Absolute path: /home/user/metadata_test.txt Parent: /home/user Size: 62 bytes Last modified: Sun Mar 08 14:30:00 IST 2026 Is file: true Is directory: false Is hidden: false Can read: true Can write: true Can execute: false Extension: txt Size (KB): 0.06 After setLastModified(0): Thu Jan 01 05:30:00 IST 1970
What happened here: Java’s File class provides many metadata methods, all of which work directly in Groovy. The length() method returns file size in bytes, lastModified() returns a timestamp in milliseconds, and the permission methods check OS-level file permissions. Notice how we got the file extension by tokenizing the name on dots and taking the last part.
Directory Operations
Beyond listing files, Groovy provides built-in methods for working with directories. Here are the key operations:
Directory Operations Summary
import groovy.io.FileType
def dir = new File('demo_project')
dir.mkdirs()
// Create some files
new File('demo_project/app.groovy').text = 'println "app"'
new File('demo_project/utils.groovy').text = 'class Utils {}'
new File('demo_project/config.xml').text = '<config/>'
new File('demo_project/data.json').text = '{}'
new File('demo_project/sub').mkdirs()
new File('demo_project/sub/helper.groovy').text = 'class Helper {}'
// Collect all Groovy files recursively
def groovyFiles = []
dir.eachFileRecurse(FileType.FILES) { f ->
if (f.name.endsWith('.groovy')) {
groovyFiles << f.name
}
}
println "Groovy files: ${groovyFiles}"
// Get total size of directory
long totalSize = 0
dir.eachFileRecurse(FileType.FILES) { f ->
totalSize += f.length()
}
println "Total size: ${totalSize} bytes"
// Count files by extension
def extCount = [:]
dir.eachFileRecurse(FileType.FILES) { f ->
def ext = f.name.tokenize('.').last()
extCount[ext] = (extCount[ext] ?: 0) + 1
}
println "Files by type: ${extCount}"
// deleteDir() removes directory and all contents
dir.deleteDir()
println "Directory deleted: ${!dir.exists()}"
Output
Groovy files: [app.groovy, utils.groovy, helper.groovy] Total size: 82 bytes Files by type: [groovy:3, xml:1, json:1] Directory deleted: true
File Properties and Metadata
Groovy gives you access to all file metadata through simple method calls. Here’s a full reference:
| Property/Method | Returns | Description |
|---|---|---|
name | String | File name without path |
absolutePath | String | Full path on disk |
parent | String | Parent directory path |
length() | long | Size in bytes |
lastModified() | long | Timestamp in milliseconds |
isFile() | boolean | true if regular file |
isDirectory() | boolean | true if directory |
isHidden() | boolean | true if hidden file |
canRead() | boolean | Read permission check |
canWrite() | boolean | Write permission check |
Real-World Use Cases
Use Case 1: Log File Parser
Let’s build a practical log parser that extracts error messages and counts occurrences by severity level:
Real-World: Log Parser
// Create a sample log file
def logFile = new File('server.log')
logFile.text = '''[2026-03-08 09:00:01] INFO - Server started on port 8080
[2026-03-08 09:00:02] INFO - Database connection established
[2026-03-08 09:15:30] WARN - Slow query detected (2.3s)
[2026-03-08 09:30:45] ERROR - NullPointerException in UserService.getUser()
[2026-03-08 09:31:00] INFO - Retrying user lookup
[2026-03-08 09:31:01] ERROR - NullPointerException in UserService.getUser()
[2026-03-08 10:00:00] INFO - Scheduled cleanup task started
[2026-03-08 10:05:22] WARN - Disk usage at 85%
[2026-03-08 10:30:15] ERROR - OutOfMemoryError in ReportGenerator
[2026-03-08 11:00:00] INFO - Health check passed'''
// Count by severity
def counts = [INFO: 0, WARN: 0, ERROR: 0]
logFile.eachLine { line ->
def matcher = line =~ /\] (\w+)/
if (matcher.find()) {
def level = matcher.group(1)
if (counts.containsKey(level)) {
counts[level]++
}
}
}
println "=== Log Summary ==="
counts.each { level, count ->
println "${level}: ${count} entries"
}
// Extract all errors
println "\n=== Errors ==="
logFile.eachLine { line ->
if (line.contains('ERROR')) {
println line
}
}
// Find unique error types
def errorTypes = [] as Set
logFile.readLines()
.findAll { it.contains('ERROR') }
.each { line ->
def msg = line.split(' - ')[1]
errorTypes << msg.split(' in ')[0]
}
println "\nUnique errors: ${errorTypes}"
logFile.delete()
Output
=== Log Summary === INFO: 5 entries WARN: 2 entries ERROR: 3 entries === Errors === [2026-03-08 09:30:45] ERROR - NullPointerException in UserService.getUser() [2026-03-08 09:31:01] ERROR - NullPointerException in UserService.getUser() [2026-03-08 10:30:15] ERROR - OutOfMemoryError in ReportGenerator Unique errors: [NullPointerException, OutOfMemoryError]
Use Case 2: Configuration File Reader
A common task is reading key-value configuration files. Here’s a reliable config reader that handles comments and sections:
Real-World: Config Reader
// Create a sample config file
def configFile = new File('app.conf')
configFile.text = '''# Application Configuration
# Last updated: March 2026
# Server settings
server.host=localhost
server.port=8080
server.maxThreads=200
# Database settings
db.url=jdbc:mysql://localhost:3306/mydb
db.user=admin
db.pool.size=10
# Feature flags
feature.darkMode=true
feature.betaAccess=false'''
// Parse config, skipping comments and blank lines
def config = [:]
configFile.eachLine { line ->
line = line.trim()
if (line && !line.startsWith('#')) {
def (key, value) = line.split('=', 2)
config[key.trim()] = value.trim()
}
}
println "=== Parsed Configuration ==="
config.each { key, value ->
println " ${key} = ${value}"
}
// Group by prefix
println "\n=== Grouped by Section ==="
def grouped = config.groupBy { entry ->
entry.key.tokenize('.')[0]
}
grouped.each { section, entries ->
println "[${section}]"
entries.each { key, value ->
println " ${key} = ${value}"
}
}
// Access specific values with defaults
def port = config.getOrDefault('server.port', '3000')
def debug = config.getOrDefault('debug.enabled', 'false')
println "\nPort: ${port}"
println "Debug (default): ${debug}"
configFile.delete()
Output
=== Parsed Configuration === server.host = localhost server.port = 8080 server.maxThreads = 200 db.url = jdbc:mysql://localhost:3306/mydb db.user = admin db.pool.size = 10 feature.darkMode = true feature.betaAccess = false === Grouped by Section === [server] server.host = localhost server.port = 8080 server.maxThreads = 200 [db] db.url = jdbc:mysql://localhost:3306/mydb db.user = admin db.pool.size = 10 [feature] feature.darkMode = true feature.betaAccess = false Port: 8080 Debug (default): false
Edge Cases and Best Practices
Handling Character Encoding
When working with files that contain non-ASCII characters, always specify the encoding:
Character Encoding
def file = new File('unicode.txt')
// Write with specific encoding
file.setText('Hello! Groovy supports Unicode.', 'UTF-8')
// Read with specific encoding
def content = file.getText('UTF-8')
println content
// withReader with encoding
file.withReader('UTF-8') { reader ->
println reader.readLine()
}
// withWriter with encoding
file.withWriter('UTF-8') { writer ->
writer.writeLine 'Written with UTF-8 encoding'
}
println file.getText('UTF-8')
file.delete()
Output
Hello! Groovy supports Unicode. Hello! Groovy supports Unicode. Written with UTF-8 encoding
Best Practices Summary
- Small files: Use
file.textfor reading andfile.text = "..."for writing - Large files: Use
eachLine()orwithReader()to avoid loading everything into memory - Resource safety: Prefer
withReader()/withWriter()overnewReader()/newWriter()– automatic cleanup - Encoding: Always specify encoding (
'UTF-8') for non-ASCII data - Check before delete: Always call
exists()beforedelete() - Use deleteDir(): For recursive directory deletion instead of manual bottom-up cleanup
- Temp files: Use
File.createTempFile()withdeleteOnExit()for throwaway data
Pro Tip: Need to copy files? Check our Groovy Copy File guide for multiple approaches including streams, NIO, and directory copying.
Conclusion
Groovy file operations transform what’s typically verbose Java boilerplate into clean, readable one-liners. Reading file content, writing output, parsing logs, traversing directories – Groovy’s GDK enhancements handle all of it.
The text property alone eliminates dozens of lines of Java code. Add eachLine(), withReader(), eachFileRecurse(), and deleteDir() to the mix, and you have a complete file operations toolkit that’s both powerful and pleasant to use.
If you’re building scripts that need to copy files between directories, head over to our Groovy Copy File – Multiple Approaches guide next. And if you want to understand the dynamic typing that makes all of this possible, revisit our def keyword tutorial.
Summary
file.textreads or writes an entire file in one expressioneachLine()is memory-efficient for large file processingwithReader()andwithWriter()provide automatic resource managementappend()adds content without overwriting existing dataeachFileRecurse()anddeleteDir()simplify directory operations
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 Copy File – Multiple Approaches
Frequently Asked Questions
How do I read a file in Groovy?
The simplest way to read a file in Groovy is using the text property: def content = new File('data.txt').text. This reads the entire file into a String. For large files, use eachLine { } to process one line at a time without loading everything into memory.
How do I write to a file in Groovy?
Use the text property to overwrite: new File('output.txt').text = 'Hello'. Use append() to add content without overwriting: new File('log.txt').append('New entry\n'). For complex writes, use withWriter { } which automatically closes the stream.
How do I delete a file in Groovy?
Call delete() on a File object: new File('temp.txt').delete(). It returns true if successful. Always check exists() first. For deleting a directory and all its contents, use deleteDir().
What is the difference between eachLine() and readLines() in Groovy?
eachLine() processes lines one at a time via a closure – it’s memory-efficient for large files. readLines() loads all lines into a List<String> at once, which lets you use collection methods like sort(), findAll(), and random access. Choose based on file size and your processing needs.
Does Groovy automatically close file resources?
Yes, when you use methods like withReader(), withWriter(), and withInputStream(). These methods accept a closure and automatically close the underlying resource when the closure completes – even if an exception occurs. The text property and eachLine() also handle resource cleanup automatically.
Related Posts
Previous in Series: Groovy XML – Create and Modify XML Documents
Next in Series: Groovy Copy File – Multiple Approaches
Related Topics You Might Like:
- Groovy Copy File – Multiple Approaches
- Groovy Def Keyword – Dynamic Typing Explained
- Groovy String Tutorial – The Complete Guide
This post is part of the Groovy & Grails Cookbook series on TechnoScripts.com

No comment