Specification: Ballerina FTP Library
Owners: @shafreenAnfar @dilanSachi @Bhashinee
Reviewers: @shafreenAnfar @Bhashinee
Created: 2020/10/28
Updated: 2026/02/05
Edition: Swan Lake
Introduction
This is the specification for the FTP standard library of Ballerina language, which provides FTP client/listener functionalities to send and receive files by connecting to FTP/SFTP server.
The FTP 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
- Specification: Ballerina FTP Library
1. Overview
FTP is a file transfer protocol. It’s a basic way of using the Internet to share files. SFTP (or Secure File Transfer Protocol) is an alternative to FTP that also allows transferring files, but adds a layer of security to the process. SFTP uses SSH (or secure shell) encryption to protect data as it’s being transferred. This means data is not exposed to outside entities on the Internet when it is sent to another party. This library provides support for both protocols.
Ballerina FTP library contains two core apis:
- Client - The
ftp:Clientis used to connect to FTP server and perform various operations on the files. - Listener - The
ftp:Listeneris used to listen to a remote FTP location and notify if files are added or removed from the FTP location.
2. Configurations
2.1. Security Configurations
public type PrivateKey record {| # Path to the private key file string path; # Private key password string password?; |};
- Credentials record represents the username and password configurations.
public type Credentials record {| # Username of the user string username; # Password of the user string password?; |};
- AuthConfiguration record represents the configurations needed for facilitating secure communication with a remote FTP server.
public type AuthConfiguration record {| # Username and password to be used Credentials credentials?; # Private key to be used PrivateKey privateKey?; # Preferred authentication methods and their order of preference PreferredMethod[] preferredMethods?; |};
- PreferredMethod enum specifies the supported authentication methods.
public enum PreferredMethod { # Security key file authentication PUBLICKEY, # Username and password authentication PASSWORD, # Interactive authentication (question and answer) KEYBOARD_INTERACTIVE, # Enterprise authentication system GSSAPI_WITH_MIC }
2.2. FileInfo
FileInforecord contains the metadata of the files.
public type FileInfo record {| # Relative file path for a newly-added file string path; # Size of the file int size; # Last-modified timestamp of the file in UNIX Epoch time int lastModifiedTimestamp; # File name string name; # `true` if the file is a folder boolean isFolder; # `true` if the file is a file boolean isFile; # Normalized absolute path of this file within its file system string pathDecoded; # Extension of the file name string extension; # Receiver as a URI String for public display string publicURIString; # Type of the file string fileType; # `true` if the `fileObject` is attached boolean isAttached; # `true` if someone reads/writes from/to this file boolean isContentOpen; # `true` if this file is executable boolean isExecutable; # `true` if this file is hidden boolean isHidden; # `true` if this file can be read boolean isReadable; # `true` if this file can be written boolean isWritable; # Depth of the file name within its file system int depth; # URI scheme of the file string scheme; # Absolute URI of the file string uri; # Root URI of the file system in which the file exists string rootURI; # A "friendly path" is a path, which can be accessed without a password string friendlyURI; |};
2.3. Error Types
The FTP module provides a hierarchy of error types for better error handling and more precise error identification.
public type Error distinct error;
ConnectionError- Represents errors that occur when connecting to the FTP/SFTP server. This includes network failures, host unreachable, connection refused, etc.
# Represents an error that occurs when connecting to the FTP/SFTP server. public type ConnectionError distinct Error;
FileNotFoundError- Represents errors that occur when a requested file or directory is not found on the remote server.
public type FileNotFoundError distinct Error;
FileAlreadyExistsError- Represents errors that occur when attempting to create a file or directory that already exists.
public type FileAlreadyExistsError distinct Error;
InvalidConfigError- Represents errors that occur when FTP/SFTP configuration is invalid (e.g., invalid port numbers, invalid regex patterns, invalid timeout values).
public type InvalidConfigError distinct Error;
public type ServiceUnavailableError distinct Error;
All specific error types are subtypes of the base Error type, allowing for both specific and general error handling:
// Handle specific error types ftp:Client|ftp:Error result = new(config); if result is ftp:ConnectionError { // Handle connection failures specifically } else if result is ftp:ServiceUnavailableError { // Transient error - retry the operation } else if result is ftp:Error { // Handle any other FTP error }
3. Client
The ftp:Client connects to FTP server and performs various operations on the files. It supports reading files in multiple formats (bytes, text, JSON, XML, CSV) with streaming support for large files, writing files in multiple formats, and file management operations including create, delete, rename, move, copy, and list.
3.1. Configurations
- When initializing the
ftp:Client,ftp:ClientConfigurationconfiguration can be provided.
public type ClientConfiguration record {| # Supported FTP protocols Protocol protocol = FTP; # Target service URL string host = "127.0.0.1"; # Port number of the remote service int port = 21; # Authentication options AuthConfiguration auth?; # If set to `true`, treats the login home directory as the root (`/`) and # prevents the underlying VFS from attempting to change to the actual server root. # If `false`, treats the actual server root as `/`, which may cause a `CWD /` command # that can fail on servers restricting root access (e.g., chrooted environments). boolean userDirIsRoot = false; # If set to `true`, allows missing or null values when reading files in structured formats boolean laxDataBinding = false; # Retry configuration for read operations RetryConfig retryConfig?; # Circuit breaker configuration to prevent cascade failures CircuitBreakerConfig circuitBreaker?; |};
- InputContent record represents the configurations for the input given for
putandappendoperations.
public type InputContent record{| # Path of the file to be created or appended string filePath; # `true` if the input type is a file boolean isFile = false; # The content read from the input file, if the input is a file stream<byte[] & readonly, io:Error?> fileContent?; # The input content, for other input types string textContent?; # If true, input will be compressed before uploading boolean compressInput = false; |};
- Following Compression options can be used when adding a file to the FTP server.
public enum Compression { # Zip compression ZIP, # No compression used NONE }
- FileWriteOption enum specifies how a file should be written to the server.
public enum FileWriteOption { # Replace the entire file with new content OVERWRITE, # Add new content to the end of the file APPEND }
3.2. Initialization
3.2.1. Insecure Client
A simple insecure client can be initialized by providing ftp:FTP as the protocol and the host and optionally, the port
to the ftp:ClientConfiguration.
# Gets invoked during object initialization. # # + clientConfig - Configurations for FTP client # + return - `ftp:Error` in case of errors or `()` otherwise public isolated function init(ClientConfiguration clientConfig) returns Error?;
3.2.2. Secure Client
A secure client can be initialized by providing ftp:SFTP as the protocol and by providing ftp:Credentials
and ftp:PrivateKey to ftp:AuthConfiguration.
ftp:ClientConfiguration ftpConfig = { protocol: ftp:SFTP, host: "<The FTP host>", port: <The FTP port>, auth: { credentials: { username: "<The FTP username>", password: "<The FTP passowrd>" } }, userDirIsRoot: true };
3.2.3. Client with Retry Configuration
A client can be initialized with retry configuration to automatically retry failed read operations:
ftp:ClientConfiguration ftpConfig = { protocol: ftp:FTP, host: "<The FTP host>", port: <The FTP port>, retryConfig: { count: 5, // Retry up to 5 times interval: 2.0, // Start with 2 second wait backOffFactor: 1.5, // Increase wait by 1.5x each time maxWaitInterval: 20.0 // Cap wait time at 20 seconds } }; ftp:Client ftpClient = check new(ftpConfig); // Non-streaming read operations (getBytes, getText, getJson, getXml, getCsv) will automatically retry on failure byte[] bytes = check ftpClient->getBytes("/path/to/file.txt");
3.3. Functions
- FTP Client API can be used to put files on the FTP server. For this, the
put()method can be used.
# Adds a file to FTP server. # ```ballerina # ftp:Error? response = client->put(path, channel); # ``` # # + path - The resource path # + content - Content to be written to the file in server # + compressionType - Type of the compression to be used, if # the file should be compressed before # uploading # + return - `()` or else an `ftp:Error` if failed to establish # the communication with the FTP server remote isolated function put(string path, stream<byte[] & readonly, io:Error?>|string|xml|json content, Compression compressionType=NONE) returns Error?;
append()can be used to append the content to an existing file in FTP server.
# Appends the content to an existing file in FTP server. # ```ballerina # ftp:Error? response = client->append(path, content); # ``` # # + path - The resource path # + content - Content to be written to the file in server # + return - `()` or else an `ftp:Error` if failed to establish # the communication with the FTP server remote isolated function append(string path, stream<byte[] & readonly, io:Error?>|string|xml|json content) returns Error?;
putBytes()can be used to write bytes to a file.
# Write bytes to a file. # ```ballerina # ftp:Error? response = client->putBytes(path, content, option); # ``` # # + path - File location on the server # + content - Binary data to write # + option - Replace or add to the file? # + return - An error if the operation fails remote isolated function putBytes(string path, byte[] content, FileWriteOption option = OVERWRITE) returns Error?;
putText()can be used to write text to a file.
# Write text to a file. # ```ballerina # ftp:Error? response = client->putText(path, content, option); # ``` # # + path - File location on the server # + content - Text to write # + option - Replace or add to the file? # + return - An error if the operation fails remote isolated function putText(string path, string content, FileWriteOption option = OVERWRITE) returns Error?;
putJson()can be used to write JSON data to a file.
# Write JSON data to a file. # ```ballerina # ftp:Error? response = client->putJson(path, content, option); # ``` # # + path - File location on the server # + content - JSON data to write # + option - Replace or add to the file? # + return - An error if the operation fails remote isolated function putJson(string path, json|record {} content, FileWriteOption option = OVERWRITE) returns Error?;
putXml()can be used to write XML data to a file.
# Write XML data to a file. # ```ballerina # ftp:Error? response = client->putXml(path, content, option); # ``` # # + path - File location on the server # + content - XML data to write # + option - Replace or add to the file? # + return - An error if the operation fails remote isolated function putXml(string path, xml|record {} content, FileWriteOption option = OVERWRITE) returns Error?;
putCsv()can be used to write CSV data to a file.
# Write CSV data to a file. # ```ballerina # ftp:Error? response = client->putCsv(path, content, option); # ``` # # + path - File location on the server # + content - CSV data (table or records) to write # + option - Replace or add to the file? # + return - An error if the operation fails remote isolated function putCsv(string path, string[][]|record {}[] content, FileWriteOption option = OVERWRITE) returns Error?;
putBytesAsStream()can be used to write bytes from a stream to a file.
# Write bytes from a stream to a file. # Useful for processing and uploading large files without loading everything into memory. # ```ballerina # ftp:Error? response = client->putBytesAsStream(path, content, option); # ``` # # + path - File location on the server # + content - Stream of byte chunks to write # + option - Replace or add to the file? # + return - An error if the operation fails remote isolated function putBytesAsStream(string path, stream<byte[], error?> content, FileWriteOption option = OVERWRITE) returns Error?;
putCsvAsStream()can be used to write CSV data from a stream to a file.
# Write CSV data from a stream to a file. # Useful for processing and uploading large CSV files without loading everything into memory. # ```ballerina # ftp:Error? response = client->putCsvAsStream(path, content, option); # ``` # # + path - File location on the server # + content - Stream of CSV rows to write # + option - Replace or add to the file? # + return - An error if the operation fails remote isolated function putCsvAsStream(string path, stream<string[]|record {}, error?> content, FileWriteOption option = OVERWRITE) returns Error?;
- To retrieve file content from FTP server,
get()can be used.
# Retrieves the file content from a remote resource. # ```ballerina # stream<byte[] & readonly, io:Error?>|ftp:Error channel = client->get(path); # ``` # # + path - The resource path # + return - A byte stream from which the file can be read or `ftp:Error` in case of errors remote isolated function get(string path) returns stream<byte[] & readonly, io:Error?>|Error;
getBytes()can be used to read file content as bytes.
# Read file content as bytes (raw binary data). # ```ballerina # byte[] content = check client->getBytes(path); # ``` # # + path - File or folder location on the server # + return - File content as bytes, or an error if the operation fails remote isolated function getBytes(string path) returns byte[]|Error;
getText()can be used to read file content as text.
# Read file content as text. # ```ballerina # string content = check client->getText(path); # ``` # # + path - File or folder location on the server # + return - File content as text, or an error if the operation fails remote isolated function getText(string path) returns string|Error;
getJson()can be used to read a file as JSON data. ReturnsContentBindingErrorif the file content cannot be parsed as JSON or bound to the target type.
# Read a file as JSON data. # ```ballerina # json content = check client->getJson(path); # ``` # # + path - Location of the file on the server # + targetType - What format should the data have? (JSON, structured data, or a custom format) # + return - The file content as JSON, or `ContentBindingError` if parsing/binding fails, or other `Error` for connection issues remote isolated function getJson(string path, typedesc<json|record {}> targetType = <>) returns targetType|Error;
getXml()can be used to read a file as XML data. ReturnsContentBindingErrorif the file content cannot be parsed as XML or bound to the target type.
# Read a file as XML data. # ```ballerina # xml content = check client->getXml(path); # ``` # # + path - Location of the file on the server # + targetType - What format should the data have? (XML, structured data, or a custom format) # + return - The file content as XML, or `ContentBindingError` if parsing/binding fails, or other `Error` for connection issues remote isolated function getXml(string path, typedesc<xml|record {}> targetType = <>) returns targetType|Error;
getCsv()can be used to read a CSV file from the server. ReturnsContentBindingErrorif the file content cannot be parsed or bound to the target type.
# Read a CSV (comma-separated) file from the server. # The first row of the CSV file should contain column names (headers). # ```ballerina # string[][] content = check client->getCsv(path); # ``` # # + path - Location of the CSV file on the server # + targetType - What format should the data have? (Table or structured records) # + return - The CSV file content as a table or records, or `ContentBindingError` if parsing/binding fails, or other `Error` for connection issues remote isolated function getCsv(string path, typedesc<string[][]|record {}[]> targetType = <>) returns targetType|Error;
getBytesAsStream()can be used to read file content as a stream of byte chunks.
# Read file content as a stream of byte chunks. # Useful for processing large files without loading the entire file into memory. # ```ballerina # stream<byte[], error?> response = check client->getBytesAsStream(path); # ``` # # + path - File or folder location on the server # + return - A continuous stream of byte chunks, or an error if the operation fails remote isolated function getBytesAsStream(string path) returns stream<byte[], error?>|Error;
getCsvAsStream()can be used to read a CSV file as a continuous stream of rows.
# Read a CSV file as a continuous stream of rows. # Useful for processing very large files one row at a time. # The first row of the CSV file should contain column names (headers). # ```ballerina # stream<string[], error?> response = check client->getCsvAsStream(path); # ``` # # + path - Location of the CSV file on the server # + targetType - What format should each row have? (Row values or structured record) # + return - A stream of rows from the CSV file, or an error if the operation fails remote isolated function getCsvAsStream(string path, typedesc<string[]|record {}> targetType = <>) returns stream<targetType, error?>|Error;
mkdir()can be used to create a new directory in FTP server.
# Creates a new directory in FTP server. # ```ballerina # ftp:Error? response = client->mkdir(path); # ``` # # + path - The directory path # + return - `()` or else an `ftp:Error` if failed to establish # the communication with the FTP server remote isolated function mkdir(string path) returns Error?;
rmdir()can be used to remove an empty directory in the server.
# Deletes an empty directory in FTP server. # ```ballerina # ftp:Error? response = client->rmdir(path); # ``` # # + path - The directory path # + return - `()` or else an `ftp:Error` if failed to establish # the communication with the FTP server remote isolated function rmdir(string path) returns Error?;
- To delete a file,
delete()can be used.
# Deletes a file from FTP server. # ```ballerina # ftp:Error? response = client->delete(path); # ``` # # + path - The resource path # + return - `()` or else an `ftp:Error` if failed to establish # the communication with the FTP server remote isolated function delete(string path) returns Error?;
- To rename a file or move it to another directory,
rename()can be used.
# Renames a file or moves it to a new location within the same FTP server. # ```ballerina # ftp:Error? response = client->rename(origin, destination); # ``` # # + origin - The source file location # + destination - The destination file location # + return - `()` or else an `ftp:Error` if failed to establish # the communication with the FTP server remote isolated function rename(string origin, string destination) returns Error?;
move()can be used to move a file to a different location on the file server.
# Move a file to a different location on the file server. # ```ballerina # ftp:Error? response = client->move(sourcePath, destinationPath); # ``` # # + sourcePath - The current file location # + destinationPath - The new file location # + return - An error if the operation fails remote isolated function move(string sourcePath, string destinationPath) returns Error?;
copy()can be used to copy a file to a different location on the file server.
# Copy a file to a different location on the file server. # ```ballerina # ftp:Error? response = client->copy(sourcePath, destinationPath); # ``` # # + sourcePath - The file to copy # + destinationPath - Where to create the copy # + return - An error if the operation fails remote isolated function copy(string sourcePath, string destinationPath) returns Error?;
exists()can be used to check if a file or folder exists on the file server.
# Check if a file or folder exists on the file server. # ```ballerina # boolean|ftp:Error response = client->exists(path); # ``` # # + path - File or folder location to check # + return - True if it exists, false if it doesn't, or an error if the check fails remote isolated function exists(string path) returns boolean|Error;
size()function can be used to get the size of a file.
# Gets the size of a file resource. # ```ballerina # int|ftp:Error response = client->size(path); # ``` # # + path - The resource path # + return - The file size in bytes or an `ftp:Error` if # failed to establish the communication with the FTP server remote isolated function size(string path) returns int|Error;
- To get the file list in a directory,
list()can be used.
# Gets the file name list in a given folder. # ```ballerina # ftp:FileInfo[]|ftp:Error response = client->list(path); # ``` # # + path - The direcotry path # + return - An array of file names or an `ftp:Error` if failed to # establish the communication with the FTP server remote isolated function list(string path) returns FileInfo[]|Error;
- To check if a resource is a directory,
isDirectory()can be used.
# Checks if a given resource is a directory. # ```ballerina # boolean|ftp:Error response = client->isDirectory(path); # ``` # # + path - The resource path # + return - `true` if given resource is a directory or an `ftp:Error` if # an error occurred while checking the path remote isolated function isDirectory(string path) returns boolean|Error;
3.4. Circuit Breaker
The circuit breaker pattern prevents cascade failures by temporarily blocking requests to an FTP server experiencing issues. When failures reach a threshold, the circuit "opens" and subsequent requests fail fast with CircuitBreakerOpenError without attempting the actual operation.
State Machine
The circuit breaker operates in three states:
- CLOSED: Normal operation. Requests are allowed and failures are tracked.
- OPEN: Circuit is tripped. All requests are rejected immediately with
CircuitBreakerOpenError. - HALF_OPEN: After the reset time elapses, one trial request is allowed. Success returns to CLOSED; failure returns to OPEN.
Configuration
- CircuitBreakerConfig record contains the circuit breaker settings.
public type CircuitBreakerConfig record {| # Rolling window configuration for failure tracking RollingWindow rollingWindow = {}; # Failure ratio threshold (0.0 to 1.0) that trips the circuit float failureThreshold = 0.5; # Time in seconds to wait before transitioning from OPEN to HALF_OPEN decimal resetTime = 30; # Categories of failures that count towards tripping the circuit FailureCategory[] failureCategories = [CONNECTION_ERROR, TRANSIENT_ERROR]; |};
- RollingWindow record configures the sliding window for tracking failures.
public type RollingWindow record {| # Minimum number of requests in the window before the circuit can trip int requestVolumeThreshold = 10; # Time window in seconds for tracking failures decimal timeWindow = 60; # Size of each time bucket in seconds (timeWindow / bucketSize = number of buckets) decimal bucketSize = 10; |};
- FailureCategory enum specifies which types of failures count towards tripping the circuit.
public enum FailureCategory { # Connection failures (network issues, timeouts) CONNECTION_ERROR, # Authentication failures (invalid credentials) AUTHENTICATION_ERROR, # Server disconnection during operation TRANSIENT_ERROR, # All errors count as failures ALL_ERRORS }
Usage Example
ftp:ClientConfiguration ftpConfig = { protocol: ftp:FTP, host: "ftp.example.com", port: 21, auth: { credentials: {username: "user", password: "pass"} }, circuitBreaker: { failureThreshold: 0.5, resetTime: 30, rollingWindow: { requestVolumeThreshold: 5, timeWindow: 60, bucketSize: 10 }, failureCategories: [ftp:CONNECTION_ERROR, ftp:TRANSIENT_ERROR] } }; ftp:Client ftpClient = check new(ftpConfig); // Operations will fail fast with CircuitBreakerOpenError when circuit is open byte[]|ftp:Error content = ftpClient->getBytes("/file.txt"); if content is ftp:CircuitBreakerOpenError { // Handle circuit breaker open state - server is unavailable }
4. Listener
The ftp:Listener is used to listen to a remote FTP location and trigger a WatchEvent type of event when new
files are added to or deleted from the directory. The onFileChange function is invoked when a new file is added
and/or deleted.
4.1. Configurations
- When initializing the
ftp:Listener, following configurations can be provided.
Note: The monitoring-related fields (
path,fileNamePattern,fileAgeFilter,fileDependencyConditions) are deprecated at the listener level. Use the@ftp:ServiceConfigannotation on services instead. See Section 4.2 for details.
public type ListenerConfiguration record {| # Supported FTP protocols Protocol protocol = FTP; # Target service url string host = "127.0.0.1"; # Port number of the remote service int port = 21; # Authentication options AuthConfiguration auth?; # @deprecated Use @ftp:ServiceConfig annotation on service instead. # Remote FTP directory location string path = "/"; # @deprecated Use @ftp:ServiceConfig annotation on service instead. # File name pattern that event need to trigger string fileNamePattern?; # Periodic time interval to check new update decimal pollingInterval = 60; # If set to `true`, treats the login home directory as the root (`/`) and # prevents the underlying VFS from attempting to change to the actual server root. # If `false`, treats the actual server root as `/`, which may cause a `CWD /` command # that can fail on servers restricting root access (e.g., chrooted environments). boolean userDirIsRoot = false; # @deprecated Use @ftp:ServiceConfig annotation on service instead. # Configuration for filtering files based on age FileAgeFilter fileAgeFilter?; # @deprecated Use @ftp:ServiceConfig annotation on service instead. # Array of dependency conditions for conditional file processing FileDependencyCondition[] fileDependencyConditions = []; # If set to `true`, allows missing or null values when reading files in structured formats boolean laxDataBinding = false; # Configuration for distributed task coordination using warm backup approach. # When configured, only one member in the group will actively poll while others act as standby. CoordinationConfig coordination?; |};
Deprecated Fields Migration:
| Deprecated Field | Migration |
|---|---|
path | Move to @ftp:ServiceConfig { path: "..." } on service |
fileNamePattern | Move to @ftp:ServiceConfig { fileNamePattern: "..." } on service |
fileAgeFilter | Move to @ftp:ServiceConfig { fileAgeFilter: {...} } on service |
fileDependencyConditions | Move to @ftp:ServiceConfig { fileDependencyConditions: [...] } on service |
WatchEventrecord represents the latest status change of the server from the last status change.
public type WatchEvent record {| # Array of `ftp:FileInfo` that represents newly added files FileInfo[] addedFiles; # Array of strings that contains deleted file names string[] deletedFiles; |};
4.2. Service Configuration Annotation
The @ftp:ServiceConfig annotation allows configuring monitoring paths and file patterns at the service level rather than the listener level. This enables multiple services attached to a single listener to monitor different directories independently.
# Configuration for FTP service monitoring. # Use this to specify the directory path and file patterns this service should monitor. # # + path - Directory path on the FTP server to monitor for file changes # + fileNamePattern - File name pattern (regex) to filter which files trigger events # + fileAgeFilter - Configuration for filtering files based on age (optional) # + fileDependencyConditions - Array of dependency conditions for conditional file processing public type ServiceConfiguration record {| string path; string fileNamePattern?; FileAgeFilter fileAgeFilter?; FileDependencyCondition[] fileDependencyConditions = []; |}; # Annotation to configure FTP service monitoring path and file patterns. public annotation ServiceConfiguration ServiceConfig on service;
Usage Rules:
-
Consistency Requirement: If any service attached to a listener uses
@ftp:ServiceConfig, then ALL services attached to that listener must use it. -
Mutual Exclusion: When
@ftp:ServiceConfigis used:- The deprecated listener-level fields (
path,fileNamePattern,fileAgeFilter,fileDependencyConditions) are completely ignored - A deprecation warning is logged if these fields were set in listener configuration
- The deprecated listener-level fields (
-
Required Field: The
pathfield is mandatory in@ftp:ServiceConfig
Validation Error Messages:
| Scenario | Error Type | Message |
|---|---|---|
| Mixed usage (some services with annotation, some without) | InvalidConfigError | "All services attached to a listener must use @ftp:ServiceConfig annotation when any service uses it. Service '{serviceName}' is missing the annotation." |
| Invalid path pattern | InvalidConfigError | "Invalid path '{path}' in @ftp:ServiceConfig. Path must be an absolute path starting with '/'." |
| Invalid fileNamePattern regex | InvalidConfigError | "Invalid regex pattern '{pattern}' in @ftp:ServiceConfig.fileNamePattern: {error}" |
Example: Multiple Services Monitoring Different Directories
listener ftp:Listener ftpListener = check new({ protocol: ftp:SFTP, host: "ftp.example.com", port: 22, auth: { credentials: {username: "user", password: "pass"} }, pollingInterval: 30 }); @ftp:ServiceConfig { path: "/incoming/csv", fileNamePattern: ".*\\.csv" } service on ftpListener { remote function onFileCsv(record {}[] content, ftp:FileInfo fileInfo) returns error? { // Processes CSV files from /incoming/csv } } @ftp:ServiceConfig { path: "/incoming/json", fileNamePattern: ".*\\.json" } service on ftpListener { remote function onFileJson(json content, ftp:FileInfo fileInfo) returns error? { // Processes JSON files from /incoming/json } }
4.3. Initialization
4.3.1. Insecure Listener
An insecure FTP listener can be initialized by providing the mandatory protocol, host, and path parameters to the
ftp:ListenerConfiguration.
# Gets invoked during object initialization. # # + listenerConfig - Configurations for FTP listener # + return - `()` or else an `ftp:Error` upon failure to initialize the listener public isolated function init(*ListenerConfiguration listenerConfig) returns Error?;
4.3.2. Secure Listener
A secure listener can be initialized by providing ftp:SFTP as the protocol and by providing ftp:Credentials
and ftp:PrivateKey to ftp:AuthConfiguration.
Using deprecated listener-level configuration (for backward compatibility):
ftp:ListenerConfiguration ftpConfig = { protocol: ftp:SFTP, host: "<The SFTP host>", port: <The SFTP port>, path: "<The remote SFTP directory location>", // Deprecated - use @ftp:ServiceConfig instead pollingInterval: <Polling interval>, fileNamePattern: "<File name pattern>", // Deprecated - use @ftp:ServiceConfig instead auth: { credentials: {username: "<The SFTP username>", password: "<The SFTP password>"}, privateKey: { path: "<The private key file path>", password: "<The private key file password>" } }, userDirIsRoot: true };
Recommended approach using @ftp:ServiceConfig:
ftp:ListenerConfiguration ftpConfig = { protocol: ftp:SFTP, host: "<The SFTP host>", port: <The SFTP port>, pollingInterval: <Polling interval>, auth: { credentials: {username: "<The SFTP username>", password: "<The SFTP password>"}, privateKey: { path: "<The private key file path>", password: "<The private key file password>" } }, userDirIsRoot: true }; listener ftp:Listener ftpListener = check new(ftpConfig); @ftp:ServiceConfig { path: "<The remote SFTP directory location>", fileNamePattern: "<File name pattern>" } service on ftpListener { // Service implementation }
4.4. Usage
After initializing the listener, a service must be attached to the listener. There are two ways for this.
- Attach the service to the listener directly.
service ftp:Service on ftpListener { remote function onFileChange(ftp:WatchEvent & readonly event, ftp:Caller caller) { // process event } }
- Attach the service dynamically.
// Create a service object ftp:Service ftpListener = service object { remote function onFileChange(ftp:WatchEvent & readonly event, ftp:Caller caller) { // process event } };
The remote method onFileChange() is invoked when the listener notices a file change in the FTP server. This function supports
having both ftp:WatchEvent and ftp:Caller parameters or having only ftp:WatchEvent parameter.
4.4.1. Service-Level Monitoring Configuration
When using the @ftp:ServiceConfig annotation, each service can specify its own monitoring path and file patterns:
listener ftp:Listener ftpListener = check new({ host: "ftp.example.com", pollingInterval: 30 }); // Service monitoring /incoming/orders for CSV files @ftp:ServiceConfig { path: "/incoming/orders", fileNamePattern: "order_.*\\.csv", fileDependencyConditions: [ { targetPattern: "order_(\\d+)\\.csv", requiredFiles: ["order_$1.marker"], matchingMode: ALL } ] } service on ftpListener { remote function onFileCsv(record {}[] content, ftp:FileInfo fileInfo, ftp:Caller caller) returns error? { // Process order CSV files only when marker file exists check caller->move(fileInfo.path, "/processed/orders/" + fileInfo.name); } } // Service monitoring /incoming/configs for JSON files @ftp:ServiceConfig { path: "/incoming/configs", fileNamePattern: ".*\\.json" } service on ftpListener { remote function onFileJson(json content, ftp:FileInfo fileInfo) returns error? { // Process configuration JSON files } }
Each service operates independently, receiving events only for files in its configured path that match its pattern.
4.4.2. Format-Specific Listener Callbacks
In addition to the generic onFileChange() callback, the listener supports specialized format-specific callbacks that automatically parse files into structured data formats. These callbacks simplify handling files of specific types.
File extension routing: Files are automatically routed to handlers based on their extensions. .txt → onFileText(), .json → onFileJson(), .xml → onFileXml(), .csv → onFileCsv(). Other extensions use onFile(). Routing can be customized using the @ftp:FunctionConfig annotation.
onFileJson()- Triggered when a JSON file (.json) is added. Supports two overloads for different content types:
# JSON content overload remote function onFileJson(json content, ftp:FileInfo fileInfo, ftp:Caller caller) returns error? { // Process parsed JSON content directly // fileInfo contains metadata about the file // caller allows you to perform FTP operations } # Data-bound record overload remote function onFileJson(record {} content, ftp:FileInfo fileInfo, ftp:Caller caller) returns error? { // Process JSON automatically data-bound to your record type }
onFileXml()- Triggered when an XML file (.xml) is added. Supports two overloads:
# XML content overload remote function onFileXml(xml content, ftp:FileInfo fileInfo, ftp:Caller caller) returns error? { // Process parsed XML content directly } # Data-bound record overload remote function onFileXml(record {} content, ftp:FileInfo fileInfo, ftp:Caller caller) returns error? { // Process XML automatically data-bound to your record type }
onFileCsv()- Triggered when a CSV file (.csv) is added with RFC4180 defaults. Supports four overloads for different processing modes:
# String array overload (in-memory, all rows) remote function onFileCsv(string[][] content, ftp:FileInfo fileInfo, ftp:Caller caller) returns error? { // content contains all rows as arrays of strings // First row contains column headers } # Record array overload (in-memory, type-safe) remote function onFileCsv(record {} [] content, ftp:FileInfo fileInfo, ftp:Caller caller) returns error? { // content automatically data-bound to record array // Headers automatically mapped to record fields } # Stream of string arrays (streaming, large files) remote function onFileCsv(stream<string[], error> content, ftp:FileInfo fileInfo, ftp:Caller caller) returns error? { // Process rows one at a time from stream // Memory-efficient for large files } # Stream of records (streaming, type-safe) remote function onFileCsv(stream<record {}, error> content, ftp:FileInfo fileInfo, ftp:Caller caller) returns error? { // Process rows as records from stream // Data-bound and memory-efficient }
onFileText()- Triggered when a text file (.txt) is added:
remote function onFileText(string content, ftp:FileInfo fileInfo, ftp:Caller caller) returns error? { // Process entire file content as UTF-8 text }
onFile()- Triggered when any other file type is added (generic binary handling). Supports two overloads:
# In-memory byte array overload remote function onFile(byte[] content, ftp:FileInfo fileInfo, ftp:Caller caller) returns error? { // content contains entire file as bytes } # Streaming overload for large files remote function onFile(stream<byte[], error> content, ftp:FileInfo fileInfo, ftp:Caller caller) returns error? { // Process file chunks from stream // Memory-efficient for large files }
onFileDelete()- Triggered when files are deleted from the monitored directory:
remote function onFileDelete(string deletedFile, ftp:Caller caller) returns error? { // Handle file deletion // deletedFile contains the deleted file path }
onError()- Triggered when a content binding error occurs during file processing. This allows centralized error handling for data binding failures:
remote function onError(ftp:Error err, ftp:Caller caller) returns error? { // Check if it's a content binding error to access details if err is ftp:ContentBindingError { string? filePath = err.detail().filePath; byte[]? content = err.detail().content; log:printError("Failed to process file: " + (filePath ?: "unknown"), err); if filePath is string { check caller->move(filePath, "/error/failed_file"); } } }
The onError handler receives:
err: The baseftp:Errortype. For content binding failures, this will be aContentBindingError. Users should type-check withif err is ftp:ContentBindingErrorto access the detail record containingfilePathandcontent.caller(optional): FTP caller for performing recovery operations
Supported parameter signatures:
onError(error err)- Using Ballerina's error type descriptoronError(ftp:Error err)- Using the FTP module's Error typeonError(ftp:Error err, ftp:Caller caller)- With caller for recovery operations
If onError is not defined, binding errors are logged and the file is skipped.
Optional parameters: The caller parameter can be omitted if not needed in your implementation.
All format-specific callbacks receive fileInfo (metadata about the file) and optionally caller
(to perform additional FTP operations). The data is automatically parsed based on the callback type.
4.4.3. Post-Processing Actions
The @ftp:FunctionConfig annotation supports automatic file actions after processing completes. This enables common patterns like moving processed files to an archive directory or deleting files after successful processing.
Configuration Types:
DELETE- Delete the file after processing
public const DELETE = "DELETE";
Move- Move the file to a specified directory
# Type alias for Move record, used in union types for post-processing actions. public type MOVE Move; # Configuration for moving a file after processing. # # + moveTo - Destination directory path where the file will be moved # + preserveSubDirs - If true, preserves the subdirectory structure relative to the # listener's root path. Defaults to true. public type Move record {| string moveTo; boolean preserveSubDirs = true; |};
Updated FtpFunctionConfig:
public type FtpFunctionConfig record {| string fileNamePattern?; MOVE|DELETE afterProcess?; MOVE|DELETE afterError?; |};
Action Fields:
afterProcess- Action to perform after successful processing. If not specified, no action is taken.afterError- Action to perform after the handler returns an error or panics. If not specified, no action is taken.
Prerequisites:
- When using
MOVEaction withpreserveSubDirs: true, the destination directory structure must be pre-created on the FTP server. The move operation will fail if the destination parent directories do not exist.
Behavior:
- After Successful Processing: If the handler returns successfully (no error), the
afterProcessaction is executed. - After Error: If the handler returns an error, the
afterErroraction is executed. - Subdirectory Preservation: When using
MOVEwithpreserveSubDirs: true(default), the subdirectory structure relative to the listener's monitored path is preserved. For example, if listening to/input/and processing/input/orders/2024/file.csvwithmoveTo: "/archive/", the file is moved to/archive/orders/2024/file.csv.
Examples:
Delete after successful processing:
service on ftpListener { @ftp:FunctionConfig { fileNamePattern: ".*\\.json", afterProcess: ftp:DELETE } remote function onFileJson(json content, ftp:FileInfo fileInfo) returns error? { // Process JSON - file is automatically deleted after successful return processJson(content); } }
Move to archive after processing:
service on ftpListener { @ftp:FunctionConfig { fileNamePattern: ".*\\.csv", afterProcess: { moveTo: "/archive/processed/" } } remote function onFileCsv(Employee[] content, ftp:FileInfo fileInfo) returns error? { // Process CSV - file is moved to archive after success saveEmployees(content); } }
Different actions for success and error:
service on ftpListener { @ftp:FunctionConfig { fileNamePattern: ".*\\.xml", afterProcess: { moveTo: "/archive/success/" }, afterError: { moveTo: "/archive/failed/" } } remote function onFileXml(xml content, ftp:FileInfo fileInfo) returns error? { // On success: moved to /archive/success/ // On error: moved to /archive/failed/ check processXml(content); } }
Move without preserving subdirectories:
service on ftpListener { @ftp:FunctionConfig { afterProcess: { moveTo: "/archive/flat/", preserveSubDirs: false } } remote function onFile(byte[] content, ftp:FileInfo fileInfo) returns error? { // All files moved directly to /archive/flat/ regardless of source subdirectory } }
The Listener has following functions to manage a service.
attach()- can be used to bind a service to theftp:Listener.
# Binds a service to the `ftp:Listener`. # ```ballerina # error? response = listener.attach(service1); # ``` # # + ftpService - Service to be detached from the listener # + name - Name of the service to be detached from the listener # + return - `()` or else an `error` upon failure to register the listener public isolated function attach(Service ftpService, string[]|string? name = ()) returns error?;
detach()- can be used to detach a service from theftp:Listener.
# Stops consuming messages and detaches the service from the `ftp:Listener`. # ```ballerina # error? response = listener.detach(service1); # ``` # # + ftpService - Service to be detached from the listener # + return - `()` or else an `error` upon failure to detach the service public isolated function detach(Service ftpService) returns error?;
start()- needs to be called to start the listener.
# Starts the `ftp:Listener`. # ```ballerina # error? response = listener.'start(); # ``` # # + return - `()` or else an `error` upon failure to start the listener public isolated function 'start() returns error?;
gracefulStop()- can be used to gracefully stop the listener.
# Stops the `ftp:Listener` gracefully. # ```ballerina # error? response = listener.gracefulStop(); # ``` # # + return - `()` or else an `error` upon failure to stop the listener public isolated function gracefulStop() returns error?;
immediateStop()- can be used to forcefully stop the listener.
# Stops the `ftp:Listener` forcefully. # ```ballerina # error? response = listener.immediateStop(); # ``` # # + return - `()` or else an `error` upon failure to stop the listener public isolated function immediateStop() returns error?;
poll()- can be used to poll new files from the FTP server.
# Poll new files from a FTP server. # ```ballerina # error? response = listener.poll(); # ``` # # + return - An `error` if failed to establish communication with the FTP # server public isolated function poll() returns error?
register()can be used to register an FTP service in anftp:listener.
# Register a FTP service in an FTP listener server. # ```ballerina # error? response = listener.register(ftpService, name); # ``` # # + ftpService - The FTP service # + name - Name of the FTP service # + return - An `error` if failed to establish communication with the FTP # server public isolated function register(Service ftpService, string? name) returns error?
4.5. Distributed Coordination
The FTP listener supports distributed coordination for high availability deployments. When multiple listener instances are deployed across different nodes, coordination ensures that only one instance actively polls the FTP server while others act as warm standby nodes. This prevents duplicate file processing and provides automatic failover.
4.5.1. Coordination Configuration
CoordinationConfigrecord represents the configuration for distributed task coordination.
public type CoordinationConfig record {| # The database configuration for task coordination task:DatabaseConfig databaseConfig; # The interval (in seconds) to check the liveness of the active node. Default is 30 seconds. int livenessCheckInterval = 30; # Unique identifier for the current member. Must be distinct for each node in the distributed system. string memberId; # The name of the coordination group of FTP listeners that coordinate together. # It is recommended to use a unique name for each group. string coordinationGroup; # The interval (in seconds) for the node to update its heartbeat status. Default is 1 second. int heartbeatFrequency = 1; |};
- The
databaseConfigfield accepts eithertask:MysqlConfigortask:PostgresqlConfig:
# MySQL configuration public type MysqlConfig record { string host = "localhost"; string? user = (); string? password = (); int port = 3306; string? database = (); }; # PostgreSQL configuration public type PostgresqlConfig record { string host = "localhost"; string? user = (); string? password = (); int port = 5432; string? database = (); };
4.5.2. Coordination Mechanism
- Leader election: Members in the same
coordinationGroupcoordinate via the database to elect an active member - Heartbeat: The active member updates its heartbeat at
heartbeatFrequencyintervals - Liveness monitoring: Standby members check the active member's heartbeat every
livenessCheckIntervalseconds - Failover: If the active member's heartbeat becomes stale, a standby member takes over as the new active member
- Polling: Only the active member's
poll()function executes; standby members skip polling
5. Caller
ftp:Caller is like a wrapper on the ftp:Client. It has an ftp:Client defined inside and contains all the APIs of ftp:Client like get(), put(), append() etc. However, ftp:Caller can only be created internally to be passed to the onFileChange method.
ftp:Caller is created in the runtime when the ftp:Listener gets attached to a ftp:Service by checking whether the user has added ftp:Caller as a parameter in the onFileChange method.
5.1. Initialization
ftp:Caller can be both secure and insecure and this depends on the type of ftp:Listener. If the ftp:Listener is a secure type, ftp:Caller will also be secure since the wrapping ftp:Client is created using a subset of the ftp:ListenerConfiguration.
# Gets invoked during object initialization. # # + 'client - The `ftp:Client` which is used to interact with the Ftp server # + return - `ftp:Error` in case of errors or `()` otherwise isolated function init(Client 'client) { self.'client = 'client; }
5.2. Functions
put()method can be used to put files on the server.
# Adds a file to FTP server. # ```ballerina # ftp:Error? response = caller->put(path, channel); # ``` # # + path - The resource path # + content - Content to be written to the file in server # + compressionType - Type of the compression to be used, if # the file should be compressed before # uploading # + return - `()` or else an `ftp:Error` if failed to establish # the communication with the FTP server remote isolated function put(string path, stream<byte[] & readonly, io:Error?>|string|xml|json content, Compression compressionType=NONE) returns Error?;
append()can be used to append the content to an existing file in FTP server.
# Appends the content to an existing file in FTP server. # ```ballerina # ftp:Error? response = caller->append(path, content); # ``` # # + path - The resource path # + content - Content to be written to the file in server # + return - `()` or else an `ftp:Error` if failed to establish # the communication with the FTP server remote isolated function append(string path, stream<byte[] & readonly, io:Error?>|string|xml|json content) returns Error?;
putBytes()can be used to write bytes to a file.
# Write bytes to a file. # ```ballerina # ftp:Error? response = caller->putBytes(path, content, option); # ``` # # + path - File location on the server # + content - Binary data to write # + option - Replace or add to the file? # + return - An error if the operation fails remote isolated function putBytes(string path, byte[] content, FileWriteOption option = OVERWRITE) returns Error?;
putText()can be used to write text to a file.
# Write text to a file. # ```ballerina # ftp:Error? response = caller->putText(path, content, option); # ``` # # + path - File location on the server # + content - Text to write # + option - Replace or add to the file? # + return - An error if the operation fails remote isolated function putText(string path, string content, FileWriteOption option = OVERWRITE) returns Error?;
putJson()can be used to write JSON data to a file.
# Write JSON data to a file. # ```ballerina # ftp:Error? response = caller->putJson(path, content, option); # ``` # # + path - File location on the server # + content - JSON data to write # + option - Replace or add to the file? # + return - An error if the operation fails remote isolated function putJson(string path, json|record {} content, FileWriteOption option = OVERWRITE) returns Error?;
putXml()can be used to write XML data to a file.
# Write XML data to a file. # ```ballerina # ftp:Error? response = caller->putXml(path, content, option); # ``` # # + path - File location on the server # + content - XML data to write # + option - Replace or add to the file? # + return - An error if the operation fails remote isolated function putXml(string path, xml|record {} content, FileWriteOption option = OVERWRITE) returns Error?;
putCsv()can be used to write CSV data to a file.
# Write CSV data to a file. # ```ballerina # ftp:Error? response = caller->putCsv(path, content, option); # ``` # # + path - File location on the server # + content - CSV data (table or records) to write # + option - Replace or add to the file? # + return - An error if the operation fails remote isolated function putCsv(string path, string[][]|record {}[] content, FileWriteOption option = OVERWRITE) returns Error?;
putBytesAsStream()can be used to write bytes from a stream to a file.
# Write bytes from a stream to a file. # Useful for processing and uploading large files without loading everything into memory. # ```ballerina # ftp:Error? response = caller->putBytesAsStream(path, content, option); # ``` # # + path - File location on the server # + content - Stream of byte chunks to write # + option - Replace or add to the file? # + return - An error if the operation fails remote isolated function putBytesAsStream(string path, stream<byte[], error?> content, FileWriteOption option = OVERWRITE) returns Error?;
putCsvAsStream()can be used to write CSV data from a stream to a file.
# Write CSV data from a stream to a file. # Useful for processing and uploading large CSV files without loading everything into memory. # ```ballerina # ftp:Error? response = caller->putCsvAsStream(path, content, option); # ``` # # + path - File location on the server # + content - Stream of CSV rows to write # + option - Replace or add to the file? # + return - An error if the operation fails remote isolated function putCsvAsStream(string path, stream<string[]|record {}, error?> content, FileWriteOption option = OVERWRITE) returns Error?;
- To retrieve file content from FTP server,
get()can be used.
# Retrieves the file content from a remote resource. # ```ballerina # stream<byte[] & readonly, io:Error?>|ftp:Error channel = caller->get(path); # ``` # # + path - The resource path # + return - A byte stream from which the file can be read or `ftp:Error` in case of errors remote isolated function get(string path) returns stream<byte[] & readonly, io:Error?>|Error;
getBytes()can be used to read file content as bytes.
# Read file content as bytes (raw binary data). # ```ballerina # byte[] content = check caller->getBytes(path); # ``` # # + path - File or folder location on the server # + return - File content as bytes, or an error if the operation fails remote isolated function getBytes(string path) returns byte[]|Error;
getText()can be used to read file content as text.
# Read file content as text. # ```ballerina # string content = check caller->getText(path); # ``` # # + path - File or folder location on the server # + return - File content as text, or an error if the operation fails remote isolated function getText(string path) returns string|Error;
getJson()can be used to read a file as JSON data.
# Read a file as JSON data. # ```ballerina # json content = check caller->getJson(path); # ``` # # + path - Location of the file on the server # + targetType - What format should the data have? (JSON, structured data, or a custom format) # + return - The file content as JSON or an error if the operation fails remote isolated function getJson(string path, typedesc<json|record {}> targetType = <>) returns targetType|Error;
getXml()can be used to read a file as XML data.
# Read a file as XML data. # ```ballerina # xml content = check caller->getXml(path); # ``` # # + path - Location of the file on the server # + targetType - What format should the data have? (XML, structured data, or a custom format) # + return - The file content as XML or an error if the operation fails remote isolated function getXml(string path, typedesc<xml|record {}> targetType = <>) returns targetType|Error;
getCsv()can be used to read a CSV file from the server.
# Read a CSV (comma-separated) file from the server. # The first row of the CSV file should contain column names (headers). # ```ballerina # string[][] content = check caller->getCsv(path); # ``` # # + path - Location of the CSV file on the server # + targetType - What format should the data have? (Table or structured records) # + return - The CSV file content as a table or records, or an error if the operation fails remote isolated function getCsv(string path, typedesc<string[][]|record {}[]> targetType = <>) returns targetType|Error;
getBytesAsStream()can be used to read file content as a stream of byte chunks.
# Read file content as a stream of byte chunks. # Useful for processing large files without loading the entire file into memory. # ```ballerina # stream<byte[], error?> response = check caller->getBytesAsStream(path); # ``` # # + path - File or folder location on the server # + return - A continuous stream of byte chunks, or an error if the operation fails remote isolated function getBytesAsStream(string path) returns stream<byte[], error?>|Error;
getCsvAsStream()can be used to read a CSV file as a continuous stream of rows.
# Read a CSV file as a continuous stream of rows. # Useful for processing very large files one row at a time. # The first row of the CSV file should contain column names (headers). # ```ballerina # stream<string[], error?> response = check caller->getCsvAsStream(path); # ``` # # + path - Location of the CSV file on the server # + targetType - What format should each row have? (Row values or structured record) # + return - A stream of rows from the CSV file, or an error if the operation fails remote isolated function getCsvAsStream(string path, typedesc<string[]|record {}> targetType = <>) returns stream<targetType, error?>|Error;
mkdir()can be used to create a new directory in FTP server.
# Creates a new directory in FTP server. # ```ballerina # ftp:Error? response = caller->mkdir(path); # ``` # # + path - The directory path # + return - `()` or else an `ftp:Error` if failed to establish # the communication with the FTP server remote isolated function mkdir(string path) returns Error?;
rmdir()can be used to remove an empty directory in the server.
# Deletes an empty directory in FTP server. # ```ballerina # ftp:Error? response = caller->rmdir(path); # ``` # # + path - The directory path # + return - `()` or else an `ftp:Error` if failed to establish # the communication with the FTP server remote isolated function rmdir(string path) returns Error?;
- To delete a file,
delete()can be used.
# Deletes a file from FTP server. # ```ballerina # ftp:Error? response = caller->delete(path); # ``` # # + path - The resource path # + return - `()` or else an `ftp:Error` if failed to establish # the communication with the FTP server remote isolated function delete(string path) returns Error?;
- To rename a file or move it to another directory,
rename()can be used.
# Renames a file or moves it to a new location within the same FTP server. # ```ballerina # ftp:Error? response = caller->rename(origin, destination); # ``` # # + origin - The source file location # + destination - The destination file location # + return - `()` or else an `ftp:Error` if failed to establish # the communication with the FTP server remote isolated function rename(string origin, string destination) returns Error?;
move()can be used to move a file to a different location on the file server.
# Move a file to a different location on the file server. # ```ballerina # ftp:Error? response = caller->move(sourcePath, destinationPath); # ``` # # + sourcePath - The current file location # + destinationPath - The new file location # + return - An error if the operation fails remote isolated function move(string sourcePath, string destinationPath) returns Error?;
copy()can be used to copy a file to a different location on the file server.
# Copy a file to a different location on the file server. # ```ballerina # ftp:Error? response = caller->copy(sourcePath, destinationPath); # ``` # # + sourcePath - The file to copy # + destinationPath - Where to create the copy # + return - An error if the operation fails remote isolated function copy(string sourcePath, string destinationPath) returns Error?;
exists()can be used to check if a file or folder exists on the file server.
# Check if a file or folder exists on the file server. # ```ballerina # boolean|ftp:Error response = caller->exists(path); # ``` # # + path - File or folder location to check # + return - True if it exists, false if it doesn't, or an error if the check fails remote isolated function exists(string path) returns boolean|Error;
size()function can be used to get the size of a file.
# Gets the size of a file resource. # ```ballerina # int|ftp:Error response = caller->size(path); # ``` # # + path - The resource path # + return - The file size in bytes or an `ftp:Error` if # failed to establish the communication with the FTP server remote isolated function size(string path) returns int|Error;
- To get the file list in a directory,
list()can be used.
# Gets the file name list in a given folder. # ```ballerina # ftp:FileInfo[]|ftp:Error response = caller->list(path); # ``` # # + path - The direcotry path # + return - An array of file names or an `ftp:Error` if failed to # establish the communication with the FTP server remote isolated function list(string path) returns FileInfo[]|Error;
- To check if a resource is a directory,
isDirectory()can be used.
# Checks if a given resource is a directory. # ```ballerina # boolean|ftp:Error response = caller->isDirectory(path); # ``` # # + path - The resource path # + return - `true` if given resource is a directory or an `ftp:Error` if # an error occurred while checking the path remote isolated function isDirectory(string path) returns boolean|Error;
6. Samples
6.1. Sending Files
import ballerina/ftp; import ballerina/io; import ballerina/lang.'string as strings; ftp:AuthConfiguration authConfig = { credentials: {username: "wso2", password: "wso2123"}, privateKey: { path: "resources/keys/sftp.private.key", password: "password" } }; ftp:ClientConfiguration sftpClientConfig = { protocol: ftp:SFTP, host: "ftp.example.com", port: 21, auth: authConfig, userDirIsRoot: true }; public function main() returns error? { ftp:Client clientEp = check new(sftpClientConfig); stream<byte[] & readonly, io:Error?> fileStream = check clientEp->get("/server/book.txt"); check fileStream.forEach(isolated function(byte[] & readonly fileContent) { io:println("File content received: " + checkpanic strings:fromBytes(fileContent)); }); stream<io:Block, io:Error?> bStream = check io:fileReadBlocksAsStream("/local/logFile.txt", 1024); check clientEp->put("/server", bStream); check fileStream.close(); }
6.2. Listening to file changes
Recommended approach using @ftp:ServiceConfig:
import ballerina/ftp; import ballerina/io; listener ftp:Listener remoteServer = check new({ protocol: ftp:SFTP, host: "ftp.example.com", auth: { credentials: { username: "wso2", password: "wso2123" }, privateKey: { path: "resources/keys/sftp.private.key", password: "password" } }, port: 21, pollingInterval: 2, userDirIsRoot: true }); @ftp:ServiceConfig { path: "/home/in", fileNamePattern: "(.*).txt" } service on remoteServer { remote function onFileText(string content, ftp:FileInfo fileInfo, ftp:Caller caller) returns error? { io:println("Processing file: " + fileInfo.path); io:println("Content: " + content); check caller->delete(fileInfo.path); } remote function onFileDelete(string deletedFile) { io:println("Deleted file path: " + deletedFile); } }
Legacy approach (deprecated):
import ballerina/ftp; import ballerina/io; listener ftp:Listener remoteServer = check new({ protocol: ftp:SFTP, host: "ftp.example.com", auth: { credentials: { username: "wso2", password: "wso2123" }, privateKey: { path: "resources/keys/sftp.private.key", password: "password" } }, port: 21, path: "/home/in", // Deprecated pollingInterval: 2, fileNamePattern: "(.*).txt", // Deprecated userDirIsRoot: true }); service on remoteServer { remote function onFileChange(ftp:Caller caller, ftp:WatchEvent & readonly event) { foreach ftp:FileInfo addedFile in event.addedFiles { io:println("Added file path: " + addedFile.path); check caller->delete(addedFile.path); } foreach string deletedFile in event.deletedFiles { io:println("Deleted file path: " + deletedFile); } } }