Of course! Here is a comprehensive guide to using a Java client for Memcached, covering the most popular client, its setup, basic operations, and best practices.

The Most Popular Java Client: Spymemcached
For a long time, SpyMemcached was the de-facto standard Java client for Memcached. It's known for being lightweight, simple, and asynchronous.
However, it's important to note that SpyMemcached is no longer actively maintained. The last release was in 2025. While it still works perfectly fine with modern Memcached servers, it does not receive updates or bug fixes.
A more modern and actively maintained alternative is Xmemcached. We will cover both, but the examples will primarily focus on SpyMemcached due to its widespread historical use and simplicity, with a note on how to transition to Xmemcached.
Setup with SpyMemcached (Maven)
First, you need to add the SpyMemcached dependency to your pom.xml file.

<dependency>
<groupId>net.spy</groupId>
<artifactId>spymemcached</artifactId>
<version>2.12.3</version> <!-- Use a recent version from the 2.12.x line -->
</dependency>
Connecting to Memcached
The entry point to the client is the MemcachedClient class. You'll need to provide it with a list of Memcached server addresses.
import net.spy.memcached.AddrUtil;
import net.spy.memcached.MemcachedClient;
public class MemcachedConnectionExample {
public static void main(String[] args) {
try {
// Connect to a Memcached server on localhost (port 11211)
MemcachedClient mcc = new MemcachedClient(
AddrUtil.getAddresses("localhost:11211")
);
System.out.println("Successfully connected to Memcached!");
// Don't forget to shut down the client when you're done
mcc.shutdown();
} catch (Exception e) {
System.err.println("Error connecting to Memcached: " + e.getMessage());
e.printStackTrace();
}
}
}
Basic Operations (CRUD)
SpyMemcached uses a Future-based API for all operations, which is a key part of its asynchronous nature.
Setting a Key-Value Pair
The set operation adds a key-value pair to the cache. You can also specify an expiration time.
import net.spy.memcached.MemcachedClient;
import net.spy.memcached.internal.OperationFuture;
import java.net.InetSocketAddress;
import java.util.concurrent.TimeUnit;
// ... inside your class
MemcachedClient mcc = new MemcachedClient(new InetSocketAddress("localhost", 11211));
try {
// Set a key "user:1001" with value "Alice" to expire in 3600 seconds (1 hour)
OperationFuture<Boolean> setFuture = mcc.set("user:1001", 3600, "Alice");
// The get() method blocks until the operation completes
boolean success = setFuture.get();
if (success) {
System.out.println("Set operation was successful.");
} else {
System.out.println("Set operation failed.");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
mcc.shutdown();
}
Getting a Value by Key
The get operation retrieves the value for a given key.

// ... after setting the value as above
try {
// Get the value for the key "user:1001"
Object myObject = mcc.get("user:1001");
if (myObject != null) {
// The result is an Object, so you need to cast it
String userName = (String) myObject;
System.out.println("Retrieved user name: " + userName);
} else {
System.out.println("Key 'user:1001' not found in cache.");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
mcc.shutdown();
}
Deleting a Key
The delete operation removes a key-value pair from the cache.
// ... after setting the value
try {
OperationFuture<Boolean> deleteFuture = mcc.delete("user:1001");
boolean success = deleteFuture.get();
if (success) {
System.out.println("Key 'user:1001' was successfully deleted.");
} else {
System.out.println("Failed to delete key 'user:1001'. It might not exist.");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
mcc.shutdown();
}
Adding and Replacing (Atomic Operations)
add: Adds a key-value pair only if the key does not already exist. If it exists, the operation fails.replace: Replaces a key-value pair only if the key already exists. If it doesn't exist, the operation fails.
try {
// Add will succeed because the key doesn't exist
mcc.add("new_key", 300, "new_value").get();
// Add will fail this time because the key now exists
mcc.add("new_key", 300, "another_value").get(); // This will return false
// Replace will succeed because the key exists
mcc.replace("new_key", 300, "replaced_value").get();
// Replace will fail this time because we deleted the key
mcc.delete("new_key").get();
mcc.replace("new_key", 300, "will_fail").get(); // This will return false
} catch (Exception e) {
e.printStackTrace();
}
Working with Custom Java Objects
By default, SpyMemcached uses Java's built-in serialization. For custom objects, your class must implement the Serializable interface.
import java.io.Serializable;
public class User implements Serializable {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User{name='" + name + "', age=" + age + "}";
}
}
Now you can store and retrieve this object directly:
MemcachedClient mcc = new MemcachedClient(AddrUtil.getAddresses("localhost:11211"));
User user = new User("Bob", 30);
// Store the custom object
mcc.set("user:1002", 3600, user);
// Retrieve the custom object
User retrievedUser = (User) mcc.get("user:1002");
System.out.println("Retrieved user: " + retrievedUser); // Will call the toString() method
mcc.shutdown();
Advanced Features
CAS (Check-And-Set) for Atomic Operations
CAS is a powerful feature to prevent lost updates. It uses a unique 64-bit value (the "cas id") for each item. You can get the cas id when you retrieve an item and then use it in an update operation to ensure you're modifying the exact version you read.
// Set a value first
mcc.set("counter", 0, 10).get();
// Get the value and its CAS id
CASValue<Object> casValue = mcc.gets("counter");
long casId = casValue.getCas();
Integer counterValue = (Integer) casValue.getValue();
System.out.println("Initial counter: " + counterValue + " with CAS ID: " + casId);
// Atomically increment the counter if it hasn't changed since we read it
// The 'cas' method will only succeed if the casId matches the one on the server
OperationFuture<CASResponse> casResult = mcc.cas("counter", casId, counterValue + 1);
CASResponse response = casResult.get();
if (response == CASResponse.OK) {
System.out.println("CAS operation successful. Counter incremented.");
} else {
System.out.println("CAS operation failed. Counter was modified by another process.");
}
mcc.shutdown();
Alternative: Xmemcached
Xmemcached is a more modern, actively maintained client with a feature-rich API, including support for the binary protocol and better performance in some scenarios.
Setup (Maven)
<dependency>
<groupId>com.googlecode.xmemcached</groupId>
<artifactId>xmemcached</artifactId>
<version>2.4.7</version> <!-- Check for the latest version -->
</dependency>
Basic Example with Xmemcached
The API is slightly different but conceptually similar.
import net.rubyeye.xmemcached.MemcachedClient;
import net.rubyeye.xmemcached.MemcachedClientBuilder;
import net.rubyeye.xmemcached.XMemcachedClientBuilder;
import net.rubyeye.xmemcached.utils.AddrUtil;
public class XmemcachedExample {
public static void main(String[] args) {
MemcachedClientBuilder builder = new XMemcachedClientBuilder(
AddrUtil.getAddresses("localhost:11211")
);
try (MemcachedClient mcc = builder.build()) { // The try-with-resources statement ensures shutdown
System.out.println("Successfully connected to Memcached with Xmemcached!");
// Set
mcc.set("user:2001", 3600, "Charlie");
System.out.println("Set 'user:2001' to Charlie."); 