Of course! Here is a comprehensive guide to using Java with Memcached, covering the most popular APIs, setup, code examples, and best practices.

What is Memcached?
Memcached is a high-performance, distributed memory caching system. It's used to speed up dynamic database-driven websites by caching data and objects in RAM to reduce the number of times an external data source (like a database or API) must be read.
Choosing a Java Memcached Client
There are two primary, well-maintained Java clients for Memcached:
- Xmemcached: A fast, feature-rich, and thread-safe client. It's often preferred for new projects due to its high performance and active development. It uses a non-blocking I/O model (Netty).
- SpyMemcached: The original and very popular Java client. It's robust and stable but can be slightly slower than Xmemcached as it uses a blocking I/O model.
For this guide, we will focus on Xmemcached because it's generally the recommended choice for modern applications.
Setting Up Your Project (Maven)
The easiest way to add a Memcached client to your Java project is using a build tool like Maven or Gradle.

For Xmemcached (Recommended)
Add this dependency to your pom.xml:
<dependency>
<groupId>com.googlecode.xmemcached</groupId>
<artifactId>xmemcached</artifactId>
<version>2.4.7</version> <!-- Check for the latest version on Maven Central -->
</dependency>
For SpyMemcached
If you prefer SpyMemcached, add this dependency:
<dependency>
<groupId>spy</groupId>
<artifactId>spymemcached</artifactId>
<version>2.12.3</version> <!-- Check for the latest version -->
</dependency>
Core Operations with Xmemcached
Let's dive into the most common operations using the Xmemcached API.
Step 1: Create a Memcached Client
First, you need to create and configure a MemcachedClient instance. It's best practice to manage this as a singleton in your application.

import net.rubyeye.xmemcached.MemcachedClient;
import net.rubyeye.xmemcached.MemcachedClientBuilder;
import net.rubyeye.xmemcached.XMemcachedClientBuilder;
import net.rubyeye.xmemcached.utils.AddrUtil;
public class MemcachedManager {
private static MemcachedClient memcachedClient;
static {
try {
// The server address is "host:port"
// For multiple servers, separate them with a space, e.g., "host1:port1 host2:port2"
MemcachedClientBuilder builder = new XMemcachedClientBuilder(
AddrUtil.getAddresses("127.0.0.1:11211")
);
// Optional: Set connection pool size
builder.setConnectionPoolSize(10);
memcachedClient = builder.build();
} catch (Exception e) {
e.printStackTrace();
// Handle initialization error appropriately
}
}
public static MemcachedClient getMemcachedClient() {
return memcachedClient;
}
}
Step 2: Basic CRUD Operations
Now you can use the client instance to interact with Memcached.
import net.rubyeye.xmemcached.MemcachedClient;
import net.rubyeye.xmemcached.exception.MemcachedException;
import java.util.concurrent.TimeoutException;
public class MemcachedExample {
public static void main(String[] args) {
MemcachedClient client = MemcachedManager.getMemcachedClient();
if (client == null) {
System.err.println("Memcached client is not initialized.");
return;
}
try {
// --- SET (Add or Update) ---
// set(key, expirationTimeInSeconds, value)
// Expiration 0 means never expire.
client.set("user:1001:profile", 3600, "{\"name\":\"Alice\",\"email\":\"alice@example.com\"}");
System.out.println("Set 'user:1001:profile'");
// --- GET (Retrieve) ---
// get(key) returns the object, so you need to cast it.
String userProfile = client.get("user:1001:profile");
System.out.println("Get 'user:1001:profile': " + userProfile);
// --- ADD (Only if key does not exist) ---
// This will succeed because the key doesn't exist yet.
client.add("session:abc123", 1800, "user_id:1001");
System.out.println("Added 'session:abc123'");
// This will FAIL because the key now exists.
boolean addResult = client.add("session:abc123", 1800, "some_other_value");
System.out.println("Trying to add 'session:abc123' again. Result: " + addResult); // false
// --- REPLACE (Only if key exists) ---
// This will succeed.
client.replace("session:abc123", 1800, "user_id:1002");
System.out.println("Replaced 'session:abc123'");
System.out.println("New value: " + client.get("session:abc123")); // user_id:1002
// This will FAIL because the key doesn't exist.
boolean replaceResult = client.replace("nonexistent:key", 1800, "some_value");
System.out.println("Trying to replace 'nonexistent:key'. Result: " + replaceResult); // false
// --- DELETE ---
client.delete("session:abc123");
System.out.println("Deleted 'session:abc123'");
System.out.println("Value after delete: " + client.get("session:abc123")); // null
} catch (InterruptedException | MemcachedException | TimeoutException e) {
e.printStackTrace();
}
}
}
Step 3: Working with Expiration Times
The expiration time is a crucial parameter. It's specified in seconds.
0: The item never expires. It will only be removed if the server runs out of memory (using the LRU - Least Recently Used - algorithm).< 604800(1 week): The value is treated as an absolute Unix timestamp (seconds since the epoch) for expiration. For example,1672531200represents January 1, 2025.>= 604800: The value is treated as a number of seconds from the current time, up to 30 days (2,592,000 seconds).
Step 4: Increment and Decrement (Counter Operations)
Memcached is excellent for maintaining counters like "page views" or "shopping cart item counts".
// ... inside the try block from the previous example ...
// --- INCREMENT ---
// If the key doesn't exist, it's created with a value of 0 before incrementing.
client.set("counter:product_views:42", 0, "100");
long newCount = client.incr("counter:product_views:42", 5);
System.out.println("Incremented counter. New value: " + newCount); // 105
// --- DECREMENT ---
long decreasedCount = client.decr("counter:product_views:42", 2);
System.out.println("Decremented counter. New value: " + decreasedCount); // 103
Step 5: Using a Transcoder (Serialization)
By default, Xmemcached uses a StringTranscoder, which means all your values must be Strings. For complex objects like User, List, etc., you need to use a SerializingTranscoder which handles Java serialization.
import net.rubyeye.xmemcached.transcoders.SerializingTranscoder;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
class User implements Serializable {
private String name;
private int age;
// ... constructors, getters, setters
}
// ... in your MemcachedManager setup or when getting the client ...
// You can configure the transcoder globally or per operation.
// For simplicity, let's do it per operation for demonstration.
public class MemcachedTranscoderExample {
public static void main(String[] args) throws Exception {
MemcachedClient client = MemcachedManager.getMemcachedClient();
User user = new User("Bob", 30);
List<String> tags = Arrays.asList("java", "memcached", "caching");
// --- Storing a serialized object ---
// The set() method can take a Transcoder as an argument.
client.set("user:1002", 3600, user, new SerializingTranscoder());
System.out.println("Stored User object.");
// --- Retrieving a serialized object ---
// The get() method will automatically use the correct transcoder if configured.
User retrievedUser = client.get("user:1002");
System.out.println("Retrieved User: " + retrievedUser.getName() + ", " + retrievedUser.getAge());
// --- Storing a serialized list ---
client.set("post:101:tags", 3600, tags, new SerializingTranscoder());
System.out.println("Stored List of tags.");
// --- Retrieving a serialized list ---
@SuppressWarnings("unchecked")
List<String> retrievedTags = client.get("post:101:tags");
System.out.println("Retrieved Tags: " + retrievedTags);
}
}
Best Practices
- Connection Management: Create a single, shared
MemcachedClientinstance for your entire application (a singleton). Do not create a new client for every request, as this is very expensive. - Error Handling: Always wrap your Memcached operations in a
try-catchblock forInterruptedException,MemcachedException, andTimeoutException. Your application should be able to handle a Memcached outage gracefully (e.g., by falling back to the primary database). - Key Naming Convention: Use a consistent and descriptive key naming scheme to avoid collisions. A common pattern is , e.g.,
user:1234:profile,session:abc123:data. - Expiry Times: Always set a sensible expiration time. Never set keys to "never expire" (
0) unless you have a very specific reason and understand the memory implications. - Use
addandreplace: Useaddwhen you want to ensure a key is only created if it doesn't exist (e.g., for a new session). Usereplacewhen you want to ensure a key is only updated if it already exists. - Think of Memcached as a Cache, Not a Database: Memcached is a volatile cache. Data can be evicted at any time if the server runs out of memory. Your application logic must always be able to fetch the original data from the "source of truth" (e.g., a database).
