DevOps

Getting Started With NCache Java Edition (Using Docker)

If you’ve ever watched a high-traffic Java service crawl under database load, you’ve already felt the problem that distributed caching solves. NCache is an in-memory, distributed cache built by Alachisoft that supports Java, .NET, Python, and Node.js clients. What makes it especially appealing for getting started is that the entire server runs as a Docker container — no host installation, no native packages, no network configuration headaches on your dev machine.

In this article we’ll go from zero to a working Java application that connects to an NCache server container, adds objects to the cache, reads them back, and removes them. Along the way, we’ll cover Docker setup, Maven dependency management, the NCache native Java API, and the JCache (JSR-107) API. Everything here runs on a standard developer workstation.

Prerequisites: Docker Desktop (or Docker Engine on Linux), Java 11 or later, and Maven 3.8+. That’s the entire list — no NCache install on your host required.

What Is NCache, and Why Use It?

NCache is a distributed, in-memory key-value store that sits between your Java application and your database. When a request arrives, the application checks the cache first. If the data is there — a cache hit — it returns immediately without touching the database. If not — a cache miss — the application fetches the data, stores it in the cache, and returns it. Subsequent requests for the same data hit the cache instead.

Furthermore, NCache scales horizontally. You can add more cache server nodes to a cluster, and the data is distributed and replicated automatically. This matters as soon as you move beyond a single application instance. Additionally, NCache ships with a full JCache (JSR-107) provider, meaning your application can use the standard JCache API while NCache handles the distributed layer underneath.

Typical Response Time: Cache Hit vs. Database Query

Representative latencies at moderate load. Cache hits stay flat; database calls grow under concurrency.

Choosing Your NCache Edition

Before pulling a Docker image it’s worth understanding the three editions NCache ships, because they map to different Docker image tags and different Maven artifacts.

EditionDocker tagMaven artifactBest for
Community (Open Source)latest-javancache-community-clientEvaluation, dev/QA, open-source projects
Professionalprofessional-javancache-client (Professional)Production, single-region deployments
Enterpriseenterprise-javancache-client (Enterprise)Multi-region, WAN replication, full feature set

For this walkthrough we’ll use the Community edition, which is freely available and fully functional for development. It runs on Ubuntu 22.04 inside the container and supports Java clients over the standard TCP port. The Enterprise and Professional editions offer a 60-day free trial with no feature restrictions if you want to test advanced functionality such as WAN replication or data protection.

Step 1 — Pull and Run the NCache Container

Open a terminal and pull the latest NCache Java Linux image from Docker Hub:

docker pull alachisoft/ncache:latest-java

Next, run the container. For a local development setup, the simplest approach is port-forwarding mode, which exposes the management and client ports to your host machine. This is the recommended approach on macOS and Windows, where Docker Desktop uses a Linux VM internally.

docker run --name ncache -itd \
  -p 8251:8251 \
  -p 9800:9800 \
  -p 8300:8300 \
  -p 8301:8301 \
  alachisoft/ncache:latest-java

Here’s what each port does:

PortPurpose
8251NCache Management Center (web UI)
9800NCache client-to-server communication
8300Cluster inter-node communication (TCP)
8301Cluster inter-node communication (UDP)

Verify the container is running:

docker ps --filter "name=ncache"

You should see the container listed with status Up. If you’re on a native Linux host, you can also run with --network host instead of explicit port mappings — that’s the configuration recommended by NCache for Linux production deployments since it avoids NAT overhead.

Static IPs matter in multi-node setups. For a single-node dev container, dynamic IPs are fine. However, if you ever add a second container to form a cluster, Docker’s default bridge network reassigns IPs on restart and breaks inter-node communication. At that point, create a custom Docker network with a fixed subnet — as detailed in the NCache Docker Linux docs.

Step 2 — Register the Container and Create a Cache

NCache requires a licence registration before it will serve clients. Navigate to the Management Center in your browser at http://localhost:8251. On first launch you’ll see a registration modal. Click Start Free Trial and fill in your name, company, and email address.

Alternatively, you can register directly from the command line without opening a browser:

docker exec -it ncache \
  /opt/ncache/bin/tools/register-ncacheevaluation \
  -firstname Jane \
  -lastname Dev \
  -company MyCompany \
  -email jane@mycompany.com \
  -key YOUR_EVAL_KEY

Once registered, return to the Management Center and create a new cache. From the left panel choose Caches → Create New Cache. Name it demoCache, leave the topology as Replicated for now, and click Finish. Then start it using the Start button. You should see its status change to Running.

Tip: The cache name you set here must match exactly what you use in your Java client code. It is case-sensitive. demoCache and democache are different caches.

Step 3 — Set Up Your Maven Project

Create a standard Maven project with Java 11 as the compiler target. Add the NCache Community client dependency to your pom.xml. The latest release on Maven Central is 5.3.6:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
             http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>ncache-demo</artifactId>
  <version>1.0-SNAPSHOT</version>

  <properties>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
  </properties>

  <dependencies>

    <!-- NCache Community Java client -->
    <dependency>
      <groupId>com.alachisoft.ncache</groupId>
      <artifactId>ncache-community-client</artifactId>
      <version>5.3.6</version>
    </dependency>

  </dependencies>
</project>

If you need the Enterprise or Professional client instead, swap the artifactId to ncache-client — those JARs require a licence key but expose additional APIs such as continuous queries and data groups.

Step 4 — Define a Serializable Domain Class

All objects stored in NCache must implement java.io.Serializable. This is how the cache serialises your objects to bytes for storage and transmission across the wire. Let’s define a simple Product class:

package com.example.model;

import java.io.Serializable;

public class Product implements Serializable {

    private static final long serialVersionUID = 1L;

    private String productId;
    private String name;
    private double price;

    public Product() {}

    public Product(String productId, String name, double price) {
        this.productId = productId;
        this.name      = name;
        this.price     = price;
    }

    // Getters
    public String getProductId() { return productId; }
    public String getName()      { return name; }
    public double getPrice()     { return price; }

    @Override
    public String toString() {
        return "Product{id='" + productId + "', name='" + name
               + "', price=" + price + "}";
    }
}

The serialVersionUID field is particularly important here. Without it, Java generates one dynamically based on the class structure. If you change the class fields after objects are already in the cache, the generated UID changes and deserialisation will throw an InvalidClassException. Declaring it explicitly keeps that under your control.

Step 5 — Connect and Perform CRUD Operations (NCache API)

NCache provides two Java APIs: its own native Cache / CacheManager API, and the standard JCache (JSR-107) API. We’ll cover both, starting with the native API since it exposes the full NCache feature set including expiration, locking, and dependency tracking.

The entry point is CacheManager.getCache(), which returns a Cache instance connected to the server. Create a main class as follows:

package com.example;

import com.alachisoft.ncache.client.Cache;
import com.alachisoft.ncache.client.CacheManager;
import com.alachisoft.ncache.client.CacheConnectionOptions;
import com.alachisoft.ncache.client.ServerInfo;
import com.alachisoft.ncache.runtime.caching.CacheItem;
import com.alachisoft.ncache.runtime.util.TimeSpan;
import com.example.model.Product;

import java.util.Collections;

public class NCacheDemo {

    public static void main(String[] args) throws Exception {

        // --- 1. Configure the connection ---
        CacheConnectionOptions options = new CacheConnectionOptions();
        // Point the client at our Docker container host + client port
        options.setServerList(Collections.singletonList(
            new ServerInfo("localhost", 9800)
        ));

        // --- 2. Open the cache ---
        Cache cache = CacheManager.getCache("demoCache", options);
        System.out.println("Connected. Cache count before: " + cache.getCount());

        // --- 3. INSERT ---
        Product laptop = new Product("P001", "Laptop Pro 15", 1299.99);
        String key = "Product:" + laptop.getProductId();

        CacheItem item = new CacheItem(laptop);
        // Optional: set a 10-minute absolute expiration
        item.setAbsoluteExpiration(TimeSpan.fromMinutes(10));

        cache.insert(key, item);
        System.out.println("Inserted: " + key);

        // --- 4. GET ---
        Product fetched = cache.get(key, Product.class);
        if (fetched != null) {
            System.out.println("Retrieved: " + fetched);
        } else {
            System.out.println("Cache miss for key: " + key);
        }

        // --- 5. UPDATE (insert overwrites) ---
        Product updated = new Product("P001", "Laptop Pro 15 Max", 1499.99);
        CacheItem updatedItem = new CacheItem(updated);
        cache.insert(key, updatedItem);
        System.out.println("Updated: " + cache.get(key, Product.class));

        // --- 6. REMOVE ---
        cache.remove(key, Product.class);
        System.out.println("Removed. Cache count after: " + cache.getCount());

        // --- 7. Close the connection ---
        cache.close();
    }
}

A few things worth noting here. First, cache.insert() handles both add and update — it overwrites any existing value at that key. If you want a strict add-only operation that throws if the key already exists, use cache.add() instead. Second, TimeSpan.fromMinutes(10) sets an absolute expiration, meaning the item expires exactly ten minutes after insertion regardless of access. NCache also supports sliding expiration, where the expiry window resets on each access.

Step 6 — The Same Operations via the JCache API

If you’d rather write vendor-neutral code that could theoretically swap to a different JCache provider later, NCache ships a full JCache (JSR-107) provider. The API is slightly different — notably, Cache.put() returns void rather than the previous value — but the semantics are equivalent for basic operations.

package com.example;

import javax.cache.Cache;
import javax.cache.CacheManager;
import javax.cache.Caching;
import javax.cache.configuration.MutableConfiguration;
import javax.cache.expiry.CreatedExpiryPolicy;
import javax.cache.expiry.Duration;
import javax.cache.spi.CachingProvider;
import com.example.model.Product;

import java.util.concurrent.TimeUnit;

public class JCacheDemo {

    public static void main(String[] args) {

        // NCache auto-discovers as the JCache provider on the classpath
        CachingProvider provider = Caching.getCachingProvider();
        CacheManager manager    = provider.getCacheManager();

        // Configure: typed cache with 5-minute creation expiry
        MutableConfiguration config =
            new MutableConfiguration()
                .setTypes(String.class, Product.class)
                .setExpiryPolicyFactory(
                    CreatedExpiryPolicy.factoryOf(
                        new Duration(TimeUnit.MINUTES, 5)
                    )
                );

        // Get or create the cache
        Cache cache = manager.getCache("demoCache");
        if (cache == null) {
            cache = manager.createCache("demoCache", config);
        }

        // PUT
        Product phone = new Product("P002", "SmartPhone X", 899.00);
        cache.put("Product:P002", phone);
        System.out.println("Put: Product:P002");

        // GET
        Product result = cache.get("Product:P002");
        System.out.println("Got: " + result);

        // REMOVE
        boolean removed = cache.remove("Product:P002");
        System.out.println("Removed: " + removed);

        manager.close();
    }
}

The JCache API uses manager.getCache() to retrieve a pre-existing cache, or manager.createCache() to create one programmatically. Notice that when using JCache, NCache auto-registers itself as the CachingProvider via the standard Java ServiceLoader mechanism — there’s nothing extra to configure. As long as the ncache-community-client JAR is on the classpath, the Caching.getCachingProvider() call finds it automatically.

Step 7 — Build and Run

Make sure your NCache container is still running, then build and execute from your project root:

# Build the project
mvn clean package -q

# Run the native API demo
mvn exec:java -Dexec.mainClass="com.example.NCacheDemo"

# Or run the JCache demo
mvn exec:java -Dexec.mainClass="com.example.JCacheDemo"

For the exec:java goal to work, add the exec-maven-plugin to your pom.xml under <build><plugins>:

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>exec-maven-plugin</artifactId>
  <version>3.2.0</version>
</plugin>

If the connection to localhost:9800 is refused, confirm the container is running with docker ps and that the port mapping is active with docker inspect ncache | grep -A5 Ports. A common mistake on macOS is running the container with --network host, which works on Linux but has no effect on macOS — use the explicit -p port flags instead.

What to Explore Next

Getting CRUD operations working is only the starting point. Once you’re comfortable with the basics, the next areas worth exploring are expiration policies, cache topologies, and Spring integration — each one unlocks significantly more value from the cache layer.

TopicWhy it mattersWhere to look
Sliding expirationKeeps hot objects alive; evicts cold ones automaticallyitem.setSlidingExpiration(TimeSpan.fromMinutes(5))
Cache topologiesReplicated vs Partitioned vs Partition-Replica — each trades latency for redundancy differentlyNCache Docs
Spring integrationUse @Cacheable@CachePut@CacheEvict with NCache as the providerSpring Data Cache
Read-Through / Write-ThroughNCache can read from or write to your database automatically on miss/updateCaching Strategies
Docker Compose clusterRun two containers as a distributed cache on a single machine for testingDocker Deployment Overview

Note on production sizing: NCache recommends at least SO-16 (16 GB RAM, 8 vCPU) for a production cache node handling moderate traffic. For a dev container on your laptop, the defaults are fine, but keep this in mind before sizing your cloud instances.

What We Learned

We pulled the NCache Java Linux image from Docker Hub and started a cache server container with a single command, registered it for free evaluation, and created a named cache through the Management Center web UI. From there, we set up a Maven project with the ncache-community-client 5.3.6 dependency, built a serializable domain class, and walked through full CRUD operations using both the native NCache Java API and the standard JCache (JSR-107) API.

We also covered the key practical details that trip people up — the static IP requirement for multi-node clusters, the distinction between insert() and add(), the JCache void put() behaviour, and the difference between absolute and sliding expiration. With the server running in Docker and the client connected via localhost:9800, the path from here to a Spring-integrated, production-grade caching layer is a straightforward next step.

Eleftheria Drosopoulou

Eleftheria is an Experienced Business Analyst with a robust background in the computer software industry. Proficient in Computer Software Training, Digital Marketing, HTML Scripting, and Microsoft Office, they bring a wealth of technical skills to the table. Additionally, she has a love for writing articles on various tech subjects, showcasing a talent for translating complex concepts into accessible content.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Back to top button