Read and Write Files in Scala
Learn to read and write files using Scala in this hands-on tutorial for Python developers. Master file I/O with practical examples and clear explanations.
Focus: read and write files using scala
You know the drill from Python: open(), read(), write(), close() — but Scala's file I/O looks different, and if you try to treat it like Python you'll hit confusing errors like java.io.FileNotFoundException or wonder why scala.io.Source is not closing your file. The pain is real: you need to read configuration files, write logs, or process CSV data in Scala, and there's no single built-in 'file object' like in Python. This lesson gives you a clear mental model and practical patterns so you can read and write files using Scala confidently and idiomatically.
The problem this lesson solves
When you need to read and write files using Scala, the standard library offers flexible but fragmented APIs. Unlike Python's single open() function, Scala splits file handling across scala.io.Source, java.nio.file, and java.io — and knowing which one to use for what task is the first hurdle.
Common pain points you'll hit without this lesson:
- Forgetting to close resources — Python's context manager (with open(...)) is automatic; Scala's Source is not. Leak file handles and you'll crash on a long-running job.
- Encoding surprises — reading a UTF-8 file as the platform default can corrupt text.
- Path confusion — java.io.File vs java.nio.file.Path can trip you up.
- Verbose boilerplate — writing a file with just java.io.PrintWriter feels clunky compared to Python's one-liner.
- Performance traps — using Source.fromFile(...).getLines naively can blow up memory on huge files.
This lesson solves those problems with a clear mental model, step-by-step approaches, and a hands-on project.
Core concept / mental model
Think of Scala file I/O in two layers, like Python's open() vs io modules:
- The legacy/JVM layer —
java.ioandscala.io.Source. These are like Python'sopen()— good for small-to-medium text files, line-by-line reading, and simple writing.scala.io.Sourceis Scala's own friendly wrapper for reading. - The modern/NIO layer —
java.nio.file. This is like Python'spathlib— designed for robust, production-grade file operations with better performance and error handling. It's the recommended choice for new code, especially large files.
A useful analogy: scala.io.Source is your quick open() with a for loop, while java.nio.file is your pathlib.Path with built-in methods like readAllLines and write.
Key definitions:
- scala.io.Source — an iterator over characters/lines in a file. One-shot; you must close it.
- java.nio.file.Files — static methods for reading/writing all bytes, lines, or streams.
- java.io.PrintWriter — a writer for formatted text output, similar to Python's print to a file.
How it works step by step
Step 1 – Choose your layer
- For reading a small text file into memory:
scala.io.Sourceorjava.nio.file.Files.readAllLines. - For writing a small text file:
java.nio.file.Files.write(simplest) orjava.io.PrintWriter(for formatted output). - For large files: use
Source.getLinesorFiles.newBufferedReaderin a stream/loop — never read all lines into memory.
Step 2 – Manage resources
Scala 2.13+ has scala.util.Using for automatic resource management — the equivalent of Python's with statement. Use it to close Source or BufferedReader automatically.
import scala.util.Using
Using(scala.io.Source.fromFile("data.txt")) { source =>
val content = source.mkString
// do something with content
}
Step 3 – Read lines
For line-by-line processing, use getLines to get an iterator. This is memory-efficient.
Step 4 – Write text
Use Files.write with java.nio.file.Path for a simple write (overwrites or appends). For formatted output, use PrintWriter inside a Using block.
Step 5 – Handle encoding
Always specify the charset (e.g., "UTF-8") explicitly to avoid platform dependencies.
Step 6 – Handle errors
Wrap I/O in try-catch or use Try to handle FileNotFoundException, IOException, etc.
Hands-on walkthrough
Exercise 1: Read and write a simple text file with scala.io.Source and java.nio.file
import scala.util.Using
import java.nio.file.{Files, Paths}
import java.nio.charset.StandardCharsets
// Write a file
val path = Paths.get("hello.txt")
Files.write(path, "Hello, Scala!\n".getBytes(StandardCharsets.UTF_8))
// Read it back
Using(scala.io.Source.fromFile(path.toFile)(scala.io.Codec.UTF8)) { source =>
val content = source.mkString
println(content) // Prints: Hello, Scala!
}
Output:
Hello, Scala!
Exercise 2: Read a large file line by line and write a transformed file
import scala.util.Using
import java.io.PrintWriter
import java.nio.charset.StandardCharsets
// Read line by line, uppercase, write out
Using(scala.io.Source.fromFile("input.txt")(scala.io.Codec.UTF8)) { source =>
Using(new PrintWriter("output.txt", "UTF-8")) { writer =>
source.getLines.foreach(line => writer.println(line.toUpperCase))
}
}
If input.txt contains line1 and line2, output.txt will contain LINE1 and LINE2.
Exercise 3: Use java.nio.file.Files for a one-shot read/write (simplest for small files)
import java.nio.file.{Files, Paths}
import java.nio.charset.StandardCharsets
import scala.jdk.CollectionConverters._
// Read all lines into a list
val path = Paths.get("config.properties")
val lines = Files.readAllLines(path, StandardCharsets.UTF_8).asScala.toList
// Write all lines back (overwrite)
Files.write(path, lines.asJava, StandardCharsets.UTF_8)
Exercise 4: Error handling with Try
import scala.util.{Try, Using}
import scala.io.Source
def readFile(fileName: String): Try[String] = Try {
Using(Source.fromFile(fileName)(scala.io.Codec.UTF8)) { source =>
source.getLines.mkString("\n")
}.get
}
readFile("missing.txt") match {
case scala.util.Success(content) => println(content)
case scala.util.Failure(e) => println(s"Failed: ${e.getMessage}")
}
Expected output for missing file:
Failed: missing.txt (No such file or directory)
Pro tip: Always use
Usingto close resources automatically — just like Python'swith. Never open aSourcewithout closing it.
Compare options / when to choose what
| Task | Python | Scala (recommended) | When to use it |
|---|---|---|---|
| Read small file into string | open(...).read() |
Files.readString(path, charset) |
Small configs, simple inputs |
| Read all lines | readlines() |
Files.readAllLines(...) |
Small files, quick scripts |
| Read line by line | for line in f |
Source.fromFile(...).getLines |
Large files, streaming |
| Write string | open(..., 'w').write(...) |
Files.write(path, bytes, ...) |
Simple writes |
| Write formatted text | print(..., file=f) |
PrintWriter |
Logging, CSV output |
| Append | open(..., 'a') |
Files.write(path, bytes, StandardOpenOption.APPEND) |
Log files |
When to choose what:
- Use Files for most new code — it's concise, modern, and handles large files well.
- Use Source when you want a Scala-native feel or need getLines with minimal ceremony.
- Use PrintWriter for formatted output, like printf-style.
- For binary files, use Files.readAllBytes / Files.write.
Troubleshooting & edge cases
Error: java.io.FileNotFoundException
Cause: File path doesn't exist, or working directory is different from where you run the app. Fix: print System.getProperty("user.dir") to see the working directory, and use absolute paths if needed.
Error: Garbled characters (mojibake) when reading/writing
Cause: Encoding mismatch. Fix: always specify charset explicitly, e.g., scala.io.Codec.UTF8 or StandardCharsets.UTF_8.
Error: OutOfMemoryError when reading a huge file
Cause: Calling mkString or readAllLines on a massive file. Fix: process line by line with getLines and never store all lines in memory.
Edge case: File not closed because you used Source directly without Using
Symptom: File lock on Windows, or “too many open files” on Unix. Fix: wrap in Using or call .close() in a finally block.
Edge case: Empty or missing lines
getLines strips line terminators, so empty lines become empty strings. Handle them if you need them.
What you learned & what's next
You've mastered how to read and write files using Scala:
- Core idea: Use scala.io.Source for reading, java.nio.file.Files for modern I/O, and PrintWriter for formatted writing.
- Hands-on exercises: You wrote and read text files, processed lines, used Using for resource safety, and handled errors with Try.
- Mental model: Two layers — legacy vs NIO — and when to choose each.
- Troubleshooting: You can debug encoding, missing files, and memory issues.
Now you're ready to move to the next lesson in the track, where you'll build on this foundation — perhaps serializing data structures or working with external libraries for more advanced I/O. Keep experimenting: try reading CSV, appending to logs, or processing a large dataset line by line.
Key takeaway: In Scala, file I/O is about choosing the right tool and managing resources — not just calling
open(). Master these patterns and you'll handle files like a pro.
Practice recap
Write a Scala script that reads a CSV file, filters rows based on a condition (e.g., age > 30), and writes the matching rows to a new CSV file. Use Using for both reading and writing, and specify UTF-8 encoding. Test it with a sample file to see the output.
Common mistakes
- Forgetting to close
scala.io.Source— causes resource leaks; always useUsingorfinally. - Not specifying encoding — leads to garbled text; always pass
Codec.UTF8orStandardCharsets.UTF_8. - Reading the entire large file into memory with
mkStringorreadAllLines— instead usegetLinesfor streaming. - Using
java.io.Filewithout checking existence — causesFileNotFoundException; verify or catch it.
Variations
- Use
scala.io.Sourcefor simple reading vsjava.nio.file.Filesfor modern, robust I/O. - Use
java.io.PrintWriterfor formatted writing vsFiles.writefor one-shot bytes. - Use third-party libraries like
os-lib(https://github.com/com-lihaoyi/os-lib) for even simpler file operations.
Real-world use cases
- Reading configuration files (e.g.,
.propertiesor.conf) at application startup to set parameters. - Processing large log files line by line to extract errors or aggregate statistics without exhausting memory.
- Writing export files (CSV, JSON) from a Scala backend to send data to other systems.
Key takeaways
- Scala offers multiple file APIs; choose
scala.io.Sourcefor reading text,java.nio.file.Filesfor modern I/O, andPrintWriterfor formatted output. - Always manage resources with
scala.util.Usingto avoid leaks, just like Python'swith. - Explicitly specify the character encoding (UTF-8) to prevent mojibake.
- For large files, process line by line with
getLinesinstead of reading everything into memory. - Handle errors gracefully using
Tryorcatchto avoid crashes on missing files or permission issues.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.