Version Matrix

FS2 with SFTP - FTP / FTPS

fs2 ftp client built on top of Cats Effect, Fs2 and the sftp java client sshj and ftp/ftps client commons-net

Build Status codecov Maven Central Cats friendly


//support scala 2.12 / 2.13

libraryDependencies += "com.github.regis-leray" %% "fs2-ftp" % "<version>"

How to use it ?


import cats.effect.IO
import fs2.ftp.UnsecureFtp._
import fs2.ftp.FtpSettings._

// FTP
val settings = UnsecureFtpSettings("", 21, FtpCredentials("foo", "bar"))
val settings = UnsecureFtpSettings.ssl("", 21, FtpCredentials("foo", "bar"))



Password authentication

import fs2.ftp.SecureFtp._
import fs2.ftp.FtpSettings._
import cats.effect.IO

val settings = SecureFtpSettings("", 22, FtpCredentials("foo", "bar"))


private key authentication

import fs2.ftp.SecureFtp._
import fs2.ftp.FtpSettings._
import java.nio.file.Paths._
import cats.effect.IO

// Provide a SftpIdentity implementation

val keyFile = KeyFileSftpIdentity(Paths.get("privateKeyStringPath"))

val settings = SecureFtpSettings("", 22, FtpCredentials("foo", ""), keyFile)


Required ContextShift

Since all (s)ftp command are IO bound task , it will be executed on specific blocking executionContext More information here

Create a FtpClient[F[_], +A] by using connect() it is required to provide an implicit ContextShift[F]

Here how to provide an ContextShift

  • you can use the default one provided by IOApp
import cats.effect.{ExitCode, IO}
import fs2.ftp._
import fs2.ftp.FtpSettings._

object MyApp extends cats.effect.IOApp {
  //by default an implicit ContextShift[IO] is available as an implicit variable   
  //F[_] Effect will be set as cats.effect.IO
  val settings = SecureFtpSettings("", 22, FtpCredentials("foo", "bar"))
  //print all files/directories
  def run(args: List[String]): IO[ExitCode] ={
      .evalTap(r => IO(println(r)))
      .redeem(_ => ExitCode.Error, _ => ExitCode.Success)          

Or create your own ContextShift

import cats.effect.IO
import cats.effect.ContextShift

implicit val blockingIO = ExecutionContext.fromExecutor(Executors.newCachedThreadPool())
implicit val cs: ContextShift[IO] = IO.contextShift(blockingIO)

Support any commands ?

The underlying client is safely exposed and you have access to all possible ftp commands

import cats.effect.IO
import fs2.ftp.SecureFtp._
import fs2.ftp.FtpSettings._

val settings = SecureFtpSettings("", 22, FtpCredentials("foo", "bar"))


Support any effect (IO, Monix, ZIO)

Since the library support polymorphic in the effect type F[_] (as long as it is compatible with cats-effect typeclasses), fs2-ftp can be used with other effect libraries such as Monix / ZIO.

The library is by default bringing with cats-effect dependency as the default effect system implementation.

exemple for monix

You will need to use add in build.sbt monix-eval

libraryDependencies += "io.monix" %% "monix-eval" % "<version>"
import fs2.ftp.FtpSettings._
import fs2.ftp._
import monix.eval.Task
import Task.contextShift

val settings = SecureFtpSettings("", 22, FtpCredentials("foo", "bar"))

val _: monix.Task[List[FtpResource]] = connect(settings).use {"/").compile.toList

exemple for zio

You will need to use add in build.sbt zio-cats-interop

libraryDependencies += "dev.zio" %% "zio-interop-cats" % "<version>"
import fs2.ftp.FtpSettings._
import zio.interop.catz._
import zio.ZIO

val settings = SecureFtpSettings("", 22, FtpCredentials("foo", "bar")) { implicit r: zio.Runtime[Any] =>
  implicit val CE: ConcurrentEffect[zio.Task] = implicitly
  implicit val CS: ContextShift[zio.Task] = implicitly

  val _: zio.Task[List[FtpResource]] = connect(settings).use {"/").compile.toList

How to release

  1. How to create a key to signed artifact
# generate key
$ gpg --gen-key

# list the keys
$ gpg --list-keys


pub   rsa4096 2018-08-22 [SC]
uid           [ultimate] your name <>
sub   rsa4096 2018-08-22 [E]

#send key to server
$> gpg --keyserver hkp:// --send-keys $LONG_ID

# declare in travis (settings) PGP_SECRET in base64 (with no return carriage), dont put "" around the value !
gpg --armor --export-secret-keys $LONG_ID | base64 -w0 | pbcopy

# declare in travis (settings) PGP_PASSPHRASE in plain text
The randomly generated password you used to create a fresh gpg key
  1. create a tag and push

more information here =>


Licensed under the Apache License, Version 2.0 (the "License"); you may not use this project except in compliance with the License. You may obtain a copy of the License at

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.