Specification: Ballerina I/O Library
Owners: @daneshk @BuddhiWathsala
Reviewers: @daneshk
Created: 2021/12/04
Updated: 2026/09/03
Edition: Swan Lake
Introduction
This is the specification for the I/O standard library of Ballerina language, which provides file related I/O operations.
The I/O library specification has evolved and may continue to evolve in the future. The released versions of the specification can be found under the relevant GitHub tag.
If you have any feedback or suggestions about the library, start a discussion via a GitHub issue or in the Discord server. Based on the outcome of the discussion, the specification and implementation can be updated. Community feedback is always welcome. Any accepted proposal which affects the specification is stored under /docs/proposals. Proposals under discussion can be found with the label type/proposal in GitHub.
The conforming implementation of the specification is released and included in the distribution. Any deviation from the specification is considered a bug.
Contents
1. Overview
Ballerina I/O standard library has six aspects of I/O operations.
- Console I/O
- Bytes I/O
- Strings I/O
- CSV I/O
- JSON I/O
- XML I/O
2. Console I/O
Console I/O contains APIs to read from the console, write to the console, and write to given output streams.
The following API reads content from standard input and return it as a string to the user.
# Retrieves the input read from the STDIN. # ```ballerina # string choice = io:readln("Enter choice 1 - 5: "); # string choice = io:readln(); # ``` # # + a - Any value to be printed # + return - Input read from the STDIN public function readln(any? a = ()) returns string;
The following two APIs write a given input to the standard output.
# Prints `any`, `error`, or string templates(such as `The respective int value is ${val}`) value(s) to the STDOUT. # ```ballerina # io:print("Start processing the CSV file from ", srcFileName); # ``` # # + values - The value(s) to be printed public isolated function print(Printable... values); # Prints `any`, `error` or string templates(such as `The respective int value is ${val}`) value(s) to the STDOUT # followed by a new line. # ```ballerina # io:println("Start processing the CSV file from ", srcFileName); # ``` # # + values - The value(s) to be printed public isolated function println(Printable... values);
Here, the print API prints the given values as it is and the println API prints the input followed by a new line.
In addition, both these print APIs support string templates as inputs like the following example.
import ballerina/io; public function main() { string name = "Harry"; int age = 20; io:println(`My name is ${name} and I am ${age} years old.`); }
The following two APIs allow users to write a given set of inputs to a specific output stream (STDOUT or STDERR).
# Prints `any`, `error`, or string templates(such as `The respective int value is ${val}`) value(s) to # a given stream(STDOUT or STDERR). # ```ballerina # io:fprint(io:stderr, "Unexpected error occurred"); # ``` # + fileOutputStream - The output stream (`io:stdout` or `io:stderr`) content needs to be printed # + values - The value(s) to be printed public isolated function fprint(FileOutputStream fileOutputStream, Printable... values); # Prints `any`, `error`, or string templates(such as `The respective int value is ${val}`) value(s) to # a given stream(STDOUT or STDERR) followed by a new line. # ```ballerina # io:fprintln(io:stderr, "Unexpected error occurred"); # ``` # + fileOutputStream - The output stream (`io:stdout` or `io:stderr`) content needs to be printed # + values - The value(s) to be printed public isolated function fprintln(FileOutputStream fileOutputStream, Printable... values);
As the print APIs, the fprint APIs also support string templates.
3. Bytes I/O
The bytes I/O APIs can be further categorized based on their streaming capabilities as streaming and non-streaming APIs.
Non-Streaming APIs
The following API reads the content of a given input file as a read-only bytes array and return it.
# Read the entire file content as a byte array. # ```ballerina # byte[]|io:Error content = io:fileReadBytes("./resources/myfile.txt"); # ``` # + path - The path of the file # + return - A read-only byte array or an `io:Error` public isolated function fileReadBytes(string path) returns readonly & byte[]|Error;
The following API writes given bytes content to a given file.
# Write a set of bytes to a file. # ```ballerina # byte[] content = [60, 78, 39, 28]; # io:Error? result = io:fileWriteBytes("./resources/myfile.txt", content); # ``` # + path - The path of the file # + content - Byte content to write # + option - To indicate whether to overwrite or append the given content # + return - An `io:Error` or else `()` public isolated function fileWriteBytes(string path, byte[] content, FileWriteOption option = OVERWRITE) returns Error?;
In write APIs, users can specify the option, either overwriting the existing file or appending it to the given file.
Streaming APIs
The following API reads the content of a given input file as a stream of blocks. Here, a block defines as a read-only bytes array.
# Read the entire file content as a stream of blocks. # ```ballerina # stream<io:Block, io:Error?>|io:Error content = io:fileReadBlocksAsStream("./resources/myfile.txt", 1000); # ``` # + path - The path of the file # + blockSize - An optional size of the byte block. The default size is 4KB # + return - A byte block stream or an`io:Error` public isolated function fileReadBlocksAsStream(string path, int blockSize = 4096) returns stream<Block, Error?>| Error;
The following API writes a given byte stream to a given file.
# Write a byte stream to a file. # ```ballerina # byte[] content = [[60, 78, 39, 28]]; # stream<byte[], io:Error?> byteStream = content.toStream(); # io:Error? result = io:fileWriteBlocksFromStream("./resources/myfile.txt", byteStream); # ``` # + path - The path of the file # + byteStream - Byte stream to write # + option - To indicate whether to overwrite or append the given content # + return - An `io:Error` or else `()` public isolated function fileWriteBlocksFromStream(string path, stream<byte[], Error?> byteStream, FileWriteOption option = OVERWRITE) returns Error?;
4. Strings I/O
Non-Streaming APIs
The following API reads the content of a given input file as a string.
# Reads the entire file content as a `string`. # The resulting string output does not contain the terminal carriage (e.g., `\r` or `\n`). # ```ballerina # string|io:Error content = io:fileReadString("./resources/myfile.txt"); # ``` # + path - The path of the file # + return - The entire file content as a string or an `io:Error` public isolated function fileReadString(string path) returns string|Error;
The following API writes given string content to a given file.
# Write a string content to a file. # ```ballerina # string content = "Hello Universe..!!"; # io:Error? result = io:fileWriteString("./resources/myfile.txt", content); # ``` # + path - The path of the file # + content - String content to write # + option - To indicate whether to overwrite or append the given content # + return - `()` when the writing was successful or an `io:Error` public isolated function fileWriteString(string path, string content, FileWriteOption option = OVERWRITE) returns Error?;
The following API reads the content of a given input file line by line and return a string array, and the string array contains each line as a string.
# Reads the entire file content as a list of lines. # The resulting string array does not contain the terminal carriage (e.g., `\r` or `\n`). # ```ballerina # string[]|io:Error content = io:fileReadLines("./resources/myfile.txt"); # ``` # + path - The path of the file # + return - The file as list of lines or an `io:Error` public isolated function fileReadLines(string path) returns string[]|Error;
The following API writes a given array of lines to a given file.
# Write an array of lines to a file. # During the writing operation, a newline character `\n` will be added after each line. # ```ballerina # string[] content = ["Hello Universe..!!", "How are you?"]; # io:Error? result = io:fileWriteLines("./resources/myfile.txt", content); # ``` # + path - The path of the file # + content - An array of string lines to write # + option - To indicate whether to overwrite or append the given content # + return - `()` when the writing was successful or an `io:Error` public isolated function fileWriteLines(string path, string[] content, FileWriteOption option = OVERWRITE) returns Error?;
Streaming APIs
The following API reads the content of a given input file as a stream of lines.
# Reads file content as a stream of lines. # The resulting stream does not contain the terminal carriage (e.g., `\r` or `\n`). # ```ballerina # stream<string, io:Error?>|io:Error content = io:fileReadLinesAsStream("./resources/myfile.txt"); # ``` # + path - The path of the file # + return - The file content as a stream of strings or an `io:Error` public isolated function fileReadLinesAsStream(string path) returns stream<string, Error?>|Error;
The following API writes a given line stream to a given file.
# Write stream of lines to a file. # During the writing operation, a newline character `\n` will be added after each line. # ```ballerina # string content = ["Hello Universe..!!", "How are you?"]; # stream<string, io:Error?> lineStream = content.toStream(); # io:Error? result = io:fileWriteLinesFromStream("./resources/myfile.txt", lineStream); # ``` # + path - The path of the file # + lineStream - A stream of lines to write # + option - To indicate whether to overwrite or append the given content # + return - `()` when the writing was successful or an `io:Error` public isolated function fileWriteLinesFromStream(string path, stream<string, Error?> lineStream, FileWriteOption option = OVERWRITE) returns Error?;
5. CSV I/O
Non-Streaming APIs
The following API reads the content of a given CSV file as a string array of arrays or array of ballerina records. Here, each CSV record represents as a string array or a record.
# Read file content as a CSV. # When the expected data type is record[], the first entry of the csv file should contain matching headers. # ```ballerina # string[][]|io:Error content = io:fileReadCsv("./resources/myfile.csv"); # record[]|io:Error content = io:fileReadCsv("./resources/myfile.csv"); # ``` # + path - The CSV file path # + skipHeaders - Number of headers, which should be skipped prior to reading records # + return - The entire CSV content in the channel as an array of string arrays, array of Ballerina records or an `io:Error` public isolated function fileReadCsv(string path, int skipHeaders = 0) returns string[][]|Error;
The following API writes given CSV content to a given file. When writing a Record[] content to a CSV file in OVERWRITE, by default, headers will be written to the CSV file as unlike string[][], the order of ballerina record fields is not guaranteed. For APPEND, order of the existing csv file is inferred using the headers and used as the order.
# Write CSV content to a file. # When the input is a record[] type in `OVERWRITE`, headers will be written to the CSV file by default. # For `APPEND`, order of the existing csv file is inferred using the headers and used as the order. # ```ballerina # type Coord record {int x;int y;}; # Coord[] contentRecord = [{x: 1,y: 2},{x: 1,y: 2}] # string[][] content = [["Anne", "Johnson", "SE"], ["John", "Cameron", "QA"]]; # io:Error? result = io:fileWriteCsv("./resources/myfile.csv", content); # io:Error? resultRecord = io:fileWriteCsv("./resources/myfileRecord.csv", contentRecord); # ``` # + path - The CSV file path # + content - CSV content as an array of string arrays or a array of Ballerina records. # + option - To indicate whether to overwrite or append the given content # + return - An `io:Error` or `()` when the writing was successful public isolated function fileWriteCsv(string path, string[][] content, FileWriteOption option = OVERWRITE) returns Error?;
The following API reads the content of a given CSV file as a stream of string arrays or ballerina records. Here, each CSV record represents as a string array or a ballerina record.
# Read file content as a CSV. # When the expected data type is stream<record, io:Error?>, # the first entry of the csv file should contain matching headers. # ```ballerina # stream<string[], io:Error?>|io:Error content = io:fileReadCsvAsStream("./resources/myfile.csv"); # stream<record{}, io:Error?>|io:Error content = io:fileReadCsvAsStream("./resources/myfile.csv"); # ``` # + path - The CSV file path # + return - The entire CSV content in the channel a stream of string arrays, Ballerina records or an `io:Error` public isolated function fileReadCsvAsStream(string path) returns stream<string[], Error?>|Error;
The following API writes a given CSV stream to a given file. When writing a Record[] content to a CSV file in OVERWRITE, by default, headers will be written to the CSV file as unlike string[] the order of record fields is not guaranteed. For APPEND, order of the existing csv file is inferred using the headers and used as the order
# Write CSV record stream to a file. # When the input is a `stream<record, io:Error?>` in `OVERWRITE`, headers will be written to the CSV file by default. # For `APPEND`, order of the existing csv file is inferred using the headers and used as the order. # ```ballerina # type Coord record {int x;int y;}; # Coord[] contentRecord = [{x: 1,y: 2},{x: 1,y: 2}] # string[][] content = [["Anne", "Johnson", "SE"], ["John", "Cameron", "QA"]]; # stream<string[], io:Error?> stringStream = content.toStream(); # stream<Coord, io:Error?> recordStream = contentRecord.toStream(); # io:Error? result = io:fileWriteCsvFromStream("./resources/myfile.csv", stringStream); # io:Error? resultRecord = io:fileWriteCsvFromStream("./resources/myfileRecord.csv", recordStream); # ``` # + path - The CSV file path # + content - A CSV record stream to be written # + option - To indicate whether to overwrite or append the given content # + return - An `io:Error` or `()` when the writing was successful public isolated function fileWriteCsvFromStream(string path, stream<string[], Error?> content, FileWriteOption option = OVERWRITE) returns Error?;
6. JSON I/O
The following API reads the content of a given JSON file and returns a Ballerina JSON object.
# Reads file content as a JSON. # ```ballerina # json|io:Error content = io:fileReadJson("./resources/myfile.json"); # ``` # + path - The path of the JSON file # + return - The file content as a JSON object or an `io:Error` public isolated function fileReadJson(string path) returns json|Error;
The following API writes given JSON to a given file.
# Write a JSON to a file. # ```ballerina # json content = {"name": "Anne", "age": 30}; # io:Error? result = io:fileWriteJson("./resources/myfile.json", content); # ``` # + path - The path of the JSON file # + content - JSON content to write # + return - `()` when the writing was successful or an `io:Error` public isolated function fileWriteJson(string path, json content) returns Error?;
7. XML I/O
The following API reads the content of a given XML file and returns a Ballerina XML object.
# Reads file content as an XML. # ```ballerina # xml|io:Error content = io:fileReadXml("./resources/myfile.xml"); # ``` # + path - The path of the XML file # + return - The file content as an XML or an `io:Error` public isolated function fileReadXml(string path) returns xml|Error;
The following API writes given XML to a given file.
# Write XML content to a file. # ```ballerina # xml content = xml `<book>The Lost World</book>`; # io:Error? result = io:fileWriteXml("./resources/myfile.xml", content); # ``` # + path - The path of the XML file # + content - XML content to write # + xmlOptions - XML writing options(XML entity type and DOCTYPE) # + fileWriteOption - file write option(`OVERWRITE` and `APPEND` are the possible values, and the default value is `OVERWRITE`) # + return - `()` value when the writing was successful or an `io:Error` public isolated function fileWriteXml(string path, xml content, FileWriteOption fileWriteOption = OVERWRITE, *XmlWriteOptions xmlOptions) returns Error?;
The XML entity type details can pass to the fileWriteXml using the XmlWriteOptions.
# Represents the XML entity type that needs to be written. # # + DOCUMENT_ENTITY - An XML document with a single root node # + EXTERNAL_PARSED_ENTITY - Externally parsed well-formed XML entity public enum XmlEntityType { DOCUMENT_ENTITY, EXTERNAL_PARSED_ENTITY } # Represents the XML DOCTYPE entity. # # + system - the system identifier # + public - the public identifier # + internalSubset - internal DTD schema public type XmlDoctype record {| string? system = (); string? 'public = (); string? internalSubset = (); |}; # The writing options of an XML. # # + xmlEntityType - the entity type of the XML input (the default value is `DOCUMENT_ENTITY`) # + doctype - XML DOCTYPE value (the default value is `()`) public type XmlWriteOptions record {| XmlEntityType xmlEntityType = DOCUMENT_ENTITY; XmlDoctype? doctype = (); |};
8. Static Code Rules
The following static code rules are applied to the I/O module.
| Id | Kind | Description |
|---|---|---|
| ballerina/io:1 | VULNERABILITY | I/O function calls should not be vulnerable to path injection attacks |
| ballerina/io:2 | VULNERABILITY | Configurable variables should not be printed to the console |
8.1. I/O function calls should not be vulnerable to path injection attacks
A file path built from untrusted input can be steered outside the directory the code intends to read from or write to.
| Property | Description |
|---|---|
| Rule ID | ballerina/io:1 |
| Rule Kind | Vulnerability |
| CWE | CWE-22 |
| OWASP Top 10:2025 | A01 Broken Access Control |
8.1.1. Why this is an issue?
The I/O functions take a file path as a plain string and hand it to the filesystem as given. A path segment such as ../ is resolved by the filesystem rather than rejected, so a value the caller controls can climb out of the intended directory and name any file the process has permission to open. The code reads as though it were confined to one directory while the path decides otherwise at run time.
8.1.2. What is the potential impact?
An attacker who controls part of the path can read files the service never meant to expose, such as configuration holding credentials or private keys, and on a write path can overwrite files the process owns.
8.1.3. How can I fix this?
Do not build a path from a value that arrives from outside the program. Where a caller must select a file, treat the value as a name rather than a path: validate it against a known set, or resolve it and confirm the result is still inside the intended directory.
Non-compliant code:
public function readUserFile(string fileName) returns string|error { // The caller controls the whole path, including any `../` segments return check io:fileReadString("/var/data/" + fileName); }
Compliant code:
public function readUserFile(string fileName) returns string|error { string path; match fileName { "report" => { path = "/var/data/report.txt"; } "summary" => { path = "/var/data/summary.txt"; } _ => { return error("unknown file"); } } return check io:fileReadString(path); }
The caller still selects the file, but the path itself is one the program wrote, so nothing the caller supplies reaches the filesystem.
8.1.4. Additional Resources
- CWE-22: Improper Limitation of a Pathname to a Restricted Directory
- OWASP Top 10:2025 A01 Broken Access Control
8.2. Configurable variables should not be printed to the console
A configurable variable written to standard output ends up in the platform's log store.
| Property | Description |
|---|---|
| Rule ID | ballerina/io:2 |
| Rule Kind | Vulnerability |
| CWE | CWE-532, CWE-200 |
| OWASP Top 10:2025 | A09 Security Logging and Alerting Failures |
8.2.1. Why this is an issue?
Configurable variables carry the values supplied at deployment, which is where credentials, tokens and connection secrets live. Standard output is collected by the container runtime and forwarded to whatever log aggregator the platform uses, so printing one moves the value out of the deployment configuration and into a durable store that a far wider set of people can read.
The rule does not attempt to decide which configurables hold secrets. Every deployment-supplied value is treated as sensitive, which is the same position ballerina/log:1 takes for log statements.
8.2.2. What is the potential impact?
A credential that reaches the log store is readable by anyone with access to logs, is retained for as long as the retention policy allows, and is copied into any downstream index or backup. Rotating it is the only remedy once it has been written.
8.2.3. How can I fix this?
Print a value that identifies the configuration rather than the configuration itself, or omit the statement.
Non-compliant code:
configurable string dbPassword = ?; public function connect() { io:println(dbPassword); io:println(string `Connecting with ${dbPassword}`); }
Compliant code:
configurable string dbPassword = ?; public function connect() { io:println("Connecting to the configured database"); }
Every configurable is treated as sensitive, including one that holds no secret, so a compliant message names none of them.