Of course! Setting up an FTP server in Java can be done in a few ways, ranging from using a powerful, pre-built library to writing a basic one from scratch for learning purposes.

Here’s a comprehensive guide covering:
-
The Recommended Approach: Using Apache Commons Net
- Why it's the best choice for most applications.
- A complete, runnable example of an FTP server.
- How to use it with a common FTP client like FileZilla.
-
The Learning Approach: Building a Simple Server from Scratch
- The key concepts and RFCs involved.
- A basic implementation to demonstrate the core mechanics.
-
Alternative Libraries
(图片来源网络,侵删)A quick look at other popular options.
The Recommended Approach: Using Apache Commons Net
For any real-world application, you should use a well-tested, robust library. Apache Commons Net is the de-facto standard for network protocols in Java, including FTP. Its FTPServer class makes creating a server incredibly straightforward.
Why Apache Commons Net?
- Robust & Stable: It's been around for years and handles many edge cases.
- Feature-Rich: Supports FTP, FTPS (FTP over SSL/TLS), SFTP (often confused, but different), and other protocols.
- Easy to Use: The API is relatively simple and well-documented.
- Actively Maintained: Part of the Apache Software Foundation.
Step 1: Add the Dependency
You need to include the Apache Commons Net library in your project. If you're using Maven, add this to your pom.xml:
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.9.0</version> <!-- Check for the latest version -->
</dependency>
If you're using Gradle:

implementation 'commons-net:commons-net:3.9.0' // Check for the latest version
Step 2: Create the FTP Server Logic
You need to implement the org.apache.commons.net.ftp.FTPServerFactory and provide a custom org.apache.commons.net.ftp.FTPSvrSessionHandler to define what happens when a user connects.
This example will create a server that listens on port 2221 and allows anonymous login with read-only access.
MyFTPServer.java
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPCmd;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
import org.apache.commons.net.ftp.FTPServer;
import org.apache.commons.net.ftp.FTPServerFactory;
import org.apache.commons.net.ftp.FTPSession;
import org.apache.commons.net.ftp.FTPSessionAdapter;
import java.io.File;
import java.io.IOException;
import java.util.Properties;
public class MyFTPServer {
public static void main(String[] args) {
// 1. Create the server factory
FTPServerFactory serverFactory = new FTPServerFactory();
// 2. Configure the server
Properties props = new Properties();
props.setProperty("server.address", "127.0.0.1"); // Listen only on localhost
props.setProperty("server.port", "2221"); // Use a non-standard port
serverFactory.setServerProperties(props);
// 3. Define the root directory for the FTP server
// This directory will be created if it doesn't exist.
File ftpRootDir = new File("ftp_root");
if (!ftpRootDir.exists()) {
ftpRootDir.mkdirs();
}
// 4. Create a listener and bind it to the factory
// We are using the default listener for simplicity.
// For more control, you can create a custom listener.
serverFactory.addListener("default", serverFactory.createListener());
// 5. Create the FTP server instance
FTPServer ftpServer = serverFactory.createServer();
// 6. Add a session listener to handle user interactions
ftpServer.addSessionListener(new FTPSessionAdapter() {
@Override
public void onLogin(FTPSession session, String username, String password) {
System.out.println("User logged in: " + username);
// For this example, we'll make all users anonymous and read-only.
// In a real app, you would authenticate against a database or file.
if ("anonymous".equals(username)) {
// You can set user-specific properties here
session.setAttribute("user", "anonymous");
}
}
@Override
public void onCommand(FTPSession session, FTPCmd command, String argument) {
// This is where you can implement custom logic for commands.
// For example, to prevent file uploads:
if (command == FTPCmd.STOR || command == FTPCmd.STOU) {
// Check if user is anonymous and deny the command
if ("anonymous".equals(session.getAttribute("user"))) {
try {
session.sendResponse(FTPReply.ACTION_NOT_TAKEN, "Permission denied.");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
});
// 7. Start the server
try {
ftpServer.start();
System.out.println("FTP Server started on 127.0.0.1:2221");
System.out.println("Root directory: " + ftpRootDir.getAbsolutePath());
System.out.println("Press Enter to stop the server...");
System.in.read(); // Wait for user input to stop
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("Stopping FTP server...");
ftpServer.stop();
System.out.println("FTP server stopped.");
}
}
}
Step 3: Run the Server and Test with a Client
- Run the Java code: Execute the
MyFTPServerclass. - Open an FTP Client: Use a client like FileZilla, Cyberduck, or even the command line.
- Connect to the Server:
- Host:
0.0.1(orlocalhost) - Port:
2221 - Username:
anonymous - Password: (leave blank or use your email)
- Host:
You should now be able to browse the ftp_root directory that was created in your project's root. You can download files, but the code above prevents uploads for the anonymous user.
The Learning Approach: Building a Simple Server from Scratch
This is a great exercise to understand the underlying protocol. The FTP protocol works over two connections:
- Control Connection (Port 21): For sending commands (e.g.,
USER,PASS,LIST,RETR). - Data Connection (Port 20 or a random high port): For transferring files and directory listings.
This simplified example will only handle the LIST command to show directory contents.
SimpleFTPServer.java
import java.io.*;
import java.net.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SimpleFTPServer {
private static final int SERVER_PORT = 2121;
private static final String SERVER_DIR = "simple_ftp_root";
private static final String USERNAME = "admin";
private static final String PASSWORD = "password";
public static void main(String[] args) {
// Create the server directory if it doesn't exist
Path serverPath = Paths.get(SERVER_DIR);
if (!Files.exists(serverPath)) {
try {
Files.createDirectories(serverPath);
} catch (IOException e) {
System.err.println("Could not create server directory: " + e.getMessage());
return;
}
}
ExecutorService threadPool = Executors.newCachedThreadPool();
try (ServerSocket serverSocket = new ServerSocket(SERVER_PORT)) {
System.out.println("Simple FTP Server started on port " + SERVER_PORT);
System.out.println("Serving files from: " + serverPath.toAbsolutePath());
while (true) {
Socket clientSocket = serverSocket.accept();
System.out.println("New client connected: " + clientSocket.getInetAddress());
threadPool.execute(new ClientHandler(clientSocket, serverPath));
}
} catch (IOException e) {
System.err.println("Server exception: " + e.getMessage());
} finally {
threadPool.shutdown();
}
}
static class ClientHandler implements Runnable {
private final Socket clientSocket;
private final Path serverPath;
private BufferedReader in;
private PrintWriter out;
private boolean isAuthenticated = false;
public ClientHandler(Socket socket, Path serverPath) {
this.clientSocket = socket;
this.serverPath = serverPath;
}
@Override
public void run() {
try {
in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
out = new PrintWriter(clientSocket.getOutputStream(), true);
// Send welcome message
sendResponse("220 Simple FTP Server Ready");
String command;
while ((command = in.readLine()) != null) {
System.out.println("Received command: " + command);
String[] parts = command.split(" ");
String cmd = parts[0].toUpperCase();
switch (cmd) {
case "USER":
handleUser(parts.length > 1 ? parts[1] : "");
break;
case "PASS":
handlePass(parts.length > 1 ? parts[1] : "");
break;
case "LIST":
handleList();
break;
case "QUIT":
sendResponse("221 Goodbye!");
return;
default:
sendResponse("502 Command not implemented.");
break;
}
}
} catch (IOException e) {
System.out.println("Error handling client: " + e.getMessage());
} finally {
try {
clientSocket.close();
} catch (IOException e) {
System.err.println("Error closing client socket: " + e.getMessage());
}
System.out.println("Client disconnected.");
}
}
private void handleUser(String username) {
if (USERNAME.equals(username)) {
sendResponse("331 Username OK, need password.");
} else {
sendResponse("530 Not logged in.");
}
}
private void handlePass(String password) {
if (USERNAME.equals(USERNAME) && PASSWORD.equals(password)) {
isAuthenticated = true;
sendResponse("230 User logged in, proceed.");
} else {
sendResponse("530 Not logged in.");
}
}
private void handleList() throws IOException {
if (!isAuthenticated) {
sendResponse("530 Please login with USER and PASS.");
return;
}
sendResponse("150 Opening data connection.");
// Create a new socket for data transfer (passive mode is more complex)
try (Socket dataSocket = new Socket(clientSocket.getInetAddress(), 2122);
PrintWriter dataOut = new PrintWriter(dataSocket.getOutputStream(), true)) {
File[] files = serverPath.toFile().listFiles();
if (files != null) {
for (File file : files) {
dataOut.println(file.getName() + (file.isDirectory() ? "/" : ""));
}
}
}
sendResponse("226 Transfer complete.");
}
private void sendResponse(String message) {
out.println(message);
}
}
}
How to Test the Simple Server:
- Run the
SimpleFTPServer.java. - Use a command-line FTP client like
ftp(on Linux/macOS) or WinSCP/FileZilla. - Connect to
localhost:2121. - Enter
USER admin, thenPASS password. - Enter
LIST. You should see the files in yoursimple_ftp_rootdirectory.
Limitations of this Simple Server:
- No file transfers (
RETR,STOR). - No passive mode support (required for most clients to work behind firewalls).
- No security.
- Very limited command set.
Alternative Libraries
While Apache Commons Net is excellent, other options exist:
- Mina FTP Server: A more modern, event-driven server built on Apache MINA. It's highly performant and scalable but has a steeper learning curve.
- NanoFTPd: A very lightweight, single-file FTP server. Good for embedded systems or extremely simple needs where you don't want a large dependency.
- SFTP vs. FTPS:
- FTP (File Transfer Protocol): The original, unencrypted protocol. Not recommended for any modern use due to security risks.
- FTPS (FTP Secure): FTP over SSL/TLS. This is what you get with
FTPServerin Commons Net. It's the standard, secure way to do FTP. - SFTP (SSH File Transfer Protocol): This is NOT FTP. It runs over an SSH connection (port 22). It's a completely different protocol, more secure and feature-rich. For SFTP in Java, you'd use a library like JSch.
Summary & Recommendation
| Feature | Apache Commons Net | Simple DIY Server |
|---|---|---|
| Ease of Use | Excellent | Hard |
| Features | Full FTP, FTPS, more | Very Basic (LIST only) |
| Reliability | High | Low (for production) |
| Security | Good (FTPS support) | None |
| Use Case | Production applications, quick setups | Learning, academic exercises |
For any practical purpose, use Apache Commons Net. It saves you from reinventing the wheel and provides a secure, stable, and feature-rich solution. The DIY server is only useful for understanding the raw protocol mechanics.
