Groovy File Operations – Read, Write, Delete with 12 Tested Examples

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.

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').text reads 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:

MethodReturnsBest For
file.textEntire file as StringSmall files, quick reads
file.readLines()List of StringsLine-by-line processing
file.eachLine { }void (iterates)Large files, memory efficient
file.withReader { }Closure resultCustom BufferedReader logic
file.bytesbyte[]Binary files

Writing Files in Groovy

Writing is just as easy. Groovy provides properties and methods for both overwriting and appending:

MethodBehaviorBest For
file.text = "..."Overwrites entire fileQuick writes
file.append("...")Adds to end of fileLogging, data accumulation
file.withWriter { }BufferedWriter with auto-closeMulti-line writes
file.newWriter()Returns a BufferedWriterManual control
file.bytes = [...]Writes raw bytesBinary 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() over newWriter() 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/MethodReturnsDescription
nameStringFile name without path
absolutePathStringFull path on disk
parentStringParent directory path
length()longSize in bytes
lastModified()longTimestamp in milliseconds
isFile()booleantrue if regular file
isDirectory()booleantrue if directory
isHidden()booleantrue if hidden file
canRead()booleanRead permission check
canWrite()booleanWrite 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.text for reading and file.text = "..." for writing
  • Large files: Use eachLine() or withReader() to avoid loading everything into memory
  • Resource safety: Prefer withReader()/withWriter() over newReader()/newWriter() – automatic cleanup
  • Encoding: Always specify encoding ('UTF-8') for non-ASCII data
  • Check before delete: Always call exists() before delete()
  • Use deleteDir(): For recursive directory deletion instead of manual bottom-up cleanup
  • Temp files: Use File.createTempFile() with deleteOnExit() 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.text reads or writes an entire file in one expression
  • eachLine() is memory-efficient for large file processing
  • withReader() and withWriter() provide automatic resource management
  • append() adds content without overwriting existing data
  • eachFileRecurse() and deleteDir() 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.

Previous in Series: Groovy XML – Create and Modify XML Documents

Next in Series: Groovy Copy File – Multiple Approaches

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 *