1. Introduction
In this tutorial, we’re going to take a deeper look into using the JSch library to perform SFTP operations.
In particular, we’ll look at the com.github.mwiede:jsch fork of the original com.jcraft:jsch library, since this is still actively maintained, whereas the original no longer is.
2. Dependencies
To use JSch, we first need to include the latest version in our build, which is 2.27.5 at the time of writing.
If we’re using Maven, we can include this dependency in our pom.xml file:
<dependency>
<groupId>com.github.mwiede</groupId>
<artifactId>jsch</artifactId>
<version>2.27.5</version>
</dependency>
At this point, we’re ready to start using it in our application.
3. Connecting to an SFTP Server
Now that we’ve got JSch set up in our application, we’re ready to connect to our remote service.
Before we can do that, we need to create a new JSch session:
JSch jSch = new JSch();
Session session = jSch.getSession(USERNAME, HOSTNAME, PORT);
This will tell JSch to connect to the SSH server running on the provided host and port, and to connect as the provided username.
We also need to provide a UserInfo instance. This interface helps us handle all the interactive aspects of our SSH connection – including answering any prompts or providing any credentials that might be needed:
session.setUserInfo(new UserInfo() {
@Override
public String getPassphrase() {
return null;
}
@Override
public String getPassword() {
return null;
}
@Override
public boolean promptPassword(String message) {
return false;
}
@Override
public boolean promptPassphrase(String message) {
return false;
}
@Override
public boolean promptYesNo(String message) {
return false;
}
@Override
public void showMessage(String message) {
}
});
If we’re building an interactive application, this class will act to pass the prompts to the user. If we’re building a non-interactive one, we can handle the prompts directly ourselves.
At this point, we can attempt to connect to our server:
session.connect();
However, at this point, this will likely fail for a few reasons.
3.1. Host Keys
The first problem we’ll encounter is managing the SSH server host key.
Host keys exist so that the SSH client knows that the server is one it can trust. If the host key exactly matches what’s expected for this server, the connection can proceed. If it doesn’t match, the connection is rejected as unsafe. And if we’ve never connected to the server before, the host key is unknown, and the user needs to decide if it’s safe or not.
If we do nothing to support this, we’ll get a JSchUnknownHostKeyException exception thrown on connecting to our server:
com.jcraft.jsch.JSchUnknownHostKeyException: UnknownHostKey: [localhost]:50022. EDDSA key fingerprint is SHA256:VVDqlx5nL9fqS/Wzq87zX1Ze/FCQEmZKiXk5AV2G2jI
JSch allows us to use the same known_hosts format that other SSH clients use. We can specify one of these to use if we wish:
jSch.setKnownHosts("/users/baeldung/.ssh/known_hosts");
Alternatively, we can configure our UserInfo object to answer the prompts to accept the host key:
@Override
public boolean promptYesNo(String message) {
if (message.startsWith("The authenticity of host")) {
return true;
}
return false;
}
The exact prompt that we receive will contain the server hostname and port, and the host key itself. We should then do whatever checks we need on these to ensure they’re valid, or we can simply accept any key as shown above.
3.2. Password Authentication
Once our application can trust the SSH server we’re connecting to, we still get an error connecting to our server:
com.jcraft.jsch.JSchException: Auth cancel for methods 'publickey,password,keyboard-interactive'
The next thing we need to be able to support is authentication. The simplest form of this is providing a password for the user we’re connecting as. We can handle this by implementing two more methods in our UserInfo object – promptPassword() and getPassword().
The first of these is to respond to the password prompt, indicating if we’re going to provide a password or not:
@Override
public boolean promptPassword(String message) {
return true;
}
The message here will indicate the user, host and port that we’re connecting to, so we can decide if we have a password for this connection or not.
The second method to implement is to provide the actual password to use:
@Override
public String getPassword() {
return "te5tPa55word;
}
At this point, assuming our password is correct, we’ll successfully connect to the server.
3.3. Public/Private Key Authentication
As an alternative to password authentication, we can also use public/private key authentication. As usual with SSH connections, this requires that we’ve already generated our keys and correctly added the public key to our SSH server.
JSch supports RSA, DSA, ECDSA and Ed25519 keys, all of which can be generated by standard SSH tools such as ssh-keygen. The keys themselves can be provided in a variety of formats, including the standard OpenSSH format, PKCS#8, OpenSSL PEM format and PuTTY PPK. Which of these we want to use depends on our exact use cases.
Once we’ve done this, we can configure JSch to know about the private key:
jSch.addIdentity("ssh_keys/id_rsa");
Once we’ve done this, our connection will automatically attempt to use this key to connect to any servers. If the server accepts the key, our connection will be successful.
In some cases, though, our keys need a passphrase to unlock them. We can handle this by implementing the promptPassphrase() and getPassphrase() methods in our UserInfo object:
@Override
public String getPassphrase() {
return "te5tPa55word";
}
@Override
public boolean promptPassphrase(String message) {
return true;
}
This works exactly the same as for passwords, only we’re providing the passphrase for our local key instead of for the remote user.
4. SFTP Connections
Once we’ve connected to our SSH server, we can open an SFTP channel. This is done using the Session.openChannel() call:
Channel channel = session.openChannel("sftp");
channel.connect();
ChannelSftp sftp = (ChannelSftp) channel;
If our SSH connection supports SFTP, this will return a ChannelSftp instance that we can use to perform SFTP activities. If not, this will throw a JSchException indicating this.
4.1. Changing Directory
Our SFTP connection maintains an active directory on both the local and remote sides of the connection. These are used for all of the file and directory-based commands that we can perform.
We can determine the current directory using the pwd() and lpwd() methods on the ChannelSftp instance:
String pwd = sftp.pwd();
String lpwd = sftp.lpwd();
The lpwd() method returns the local working directory, whereas the pwd() method returns the current working directory on the remote server.
We can change these using the cd() and lcd() methods:
sftp.cd("/tmp");
sftp.lcd("/tmp");
Once done, all commands will now act based on the new directory instead.
4.2. Listing Files
We can use the ls() method to get a list of all the files and directories on the remote server:
List<ChannelSftp.LsEntry> remoteLs = sftp.ls(".");
This takes a directory to list the files of, which can either be an absolute directory on the server or a directory relative to the current one. If we specify a value of “.”, this will be the current working directory itself.
This will return a list of LsEntry types. These contain the filename within the target directory, and the attributes of the file – including permissions, file size, access dates, and so on:
for (ChannelSftp.LsEntry file : remoteLs) {
String filename = file.getFilename();
SftpATTRS attrs = upload.getAttrs();
}
If we know the name of a file or directory, we can also use the stat() method to get the attributes for it directly without needing to list everything:
SftpATTRS attrs = sftp.stat("upload");
If the file or directory exists, this will return the details of it. If it doesn’t, this will throw an SftpException instead to indicate this fact.
Note that there’s no local version of these methods. Since the local version would simply work with files on the local file system, we can already do that with standard Java IO methods.
4.3. Transferring Files
Now that we can connect to our SSH server and discover which files are available, we need to be able to transfer files to and from the server.
We can send files to the remote server using the put() method. We have several variations of this, all of which take the filename to write to, but can provide the actual file contents in different ways:
// Copy the local file to the remote file.
sftp.put("localFile.txt", "remoteFile.txt");
// Send the contents of the input stream to the remote file.
sftp.put(inputStream, "remoteFile.txt");
// Open an output stream onto the remote file.
OutputStream outputStream = sftp.put("remoteFile.txt");
The InputStream version can consume an input stream from any source, and the OutputStream version gives us an output stream that we can write to in any way.
Filenames can either be relative to the appropriate working directory or absolute filenames anywhere on the file system.
We can also transfer in the opposite direction – fetching files from the remote server. We do this using the get() method. Again, we have the equivalent variations of this that we can use:
// Copy the remote file to the local file.
sftp.get("remoteFile.txt", "localFile.txt");
// Send the contents of the remote file to the output stream.
sftp.get("remoteFile.txt", outputStream);
// Open an input stream onto the remote file.
InputStream inputStream = sftp.get("remoteFile.txt");
As we’d expect, the OutputStream version writes the remote file to the output stream, and the InputStream version gives us an input stream to the remote file that we can consume however we wish.
As before, the filenames provided can either be relative to the appropriate working directory absolute filenames anywhere on the appropriate filesystem.
7. Summary
In this article, we took a closer look at the JSch library and how to use it for SFTP operations. There’s more that can be done with this, so next time you need to transfer files between systems, why not give it a try?
As usual, all of the examples from this article are available over on GitHub.