Dragonfly

Caching with Dragonfly and Java in 5 Minutes

Boost Java app performance with Dragonfly as a Redis-compatible cache. Learn to integrate it with Spring Boot for scalable caching.

July 31, 2025

Caching with Dragonfly and Java in 5 Minutes Cover

Introduction: The Need for Speed without the Lock-In

In modern software development, caching is often the difference between a sluggish application and one that feels instantaneous. While Redis has long been the go-to choice for in-memory data stores, newer solutions like Dragonfly offer compelling alternatives with the same API and more powerful architecture without forcing developers to rethink their entire stack. The best tools strike a balance: they deliver better performance while respecting familiar paradigms. In this guide, we’ll explore how Dragonfly, with its Redis compatibility, slots seamlessly into Java applications, offering speed, scalability, and, critically, the ability to migrate or innovate without starting from scratch.

The Tooling Stack

Our project relies on a few key technologies as described below.

Spring Boot is the de facto choice for Java application development for many teams, providing a convention-over-configuration approach that eliminates boilerplate while maintaining flexibility. Spring Boot’s auto-configuration and starter dependencies represent a mature ecosystem where standards have evolved through community consensus and real-world usage.

Spring Data JPA provides a standardized abstraction over database operations, allowing developers to work with entities rather than raw SQL. This abstraction layer means you can switch between different database providers (PostgreSQL, MySQL, etc.) with minimal code changes, demonstrating the value of standardized interfaces.

Spring Cache Abstraction offers a standardized caching abstraction that works with multiple cache providers. The beauty of this approach is that your caching logic remains the same regardless of the choice of your cache implementation.

Dragonfly is a fully Redis-compatible, multi-threaded, high-performance in-memory data store built for the most demanding workloads. The Redis serialization protocol (RESP) represents one of those cases where a popular API becomes the de facto standard. Dragonfly honors this reality by maintaining full Redis API compatibility while completely rearchitecting the underlying engine. The result is a drop-in replacement that requires zero code changes but delivers dramatically better performance through its modern multi-threaded, shared-nothing architecture.

Last but not least, we will pair one of the most advanced open-source relational databases, PostgreSQL, with Spring Data JPA to build the storage layer. We will also utilize development tools like Docker and Maven, along with other libraries within the Java ecosystem.


Building a Dictionary API with Caching

A dictionary API can be a great example demonstrating the power of a caching layer in the real world. While simple in concept, its traffic patterns create unique challenges. For instance, popular words can generate thousands of lookups in minutes when the API is shared across multiple applications. So even in this straightforward system, a proper caching layer can reduce database load by orders of magnitude while improving response times and system stability.

Now let’s walk through the dictionary API we are about to build. If you want to follow along, all the code snippets in this tutorial are available in our example repository.

Prerequisites

Before we begin, ensure you have the following tools installed:

  • Java 17+: We use Java 24 for this project, but any version 17 or higher will work.
  • Maven: A popular build tool for Java projects, used for dependency management and building.
  • Docker: A platform used to develop, ship, and run applications inside containers. A container is a lightweight, standalone executable that includes everything needed to run a piece of software—code, runtime, system tools, libraries, and settings.

Running PostgreSQL and Dragonfly

First, let’s make sure we have PostgreSQL and Dragonfly server instances running locally in Docker.

# docker-compose.yaml
services:
  dragonfly:
    image: docker.dragonflydb.io/dragonflydb/dragonfly
    container_name: dictionary-app-dragonfly
    ports:
      - "6380:6379"
 
  postgres:
    image: postgres:17
    container_name: dictionary-app-postgres
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: dictionarydb
    ports:
      - "5432:5432"
    volumes:
      - ./db-init:/docker-entrypoint-initdb.d

Start the services:

$> docker-compose up -d

This will start both services. The PostgreSQL database will be automatically seeded with initial dictionary data using the db-init/init.sql script.

-- db-init/init.sql
CREATE TABLE IF NOT EXISTS dictionary_entry (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    word VARCHAR(255) UNIQUE NOT NULL,
    definition TEXT NOT NULL,
    updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);

INSERT INTO dictionary_entry(word, definition, updated_at) VALUES
('cache', 'A component that transparently stores data for faster retrieval.', NOW()),
('dragonfly', 'A fully Redis-compatible, high-performance in-memory data store.', NOW()),
('java', 'A versatile, object-oriented programming language widely used for building enterprise applications.', NOW()),
('spring', 'An opinionated framework simplifying the development of Java-based applications.', NOW()),
('redis', 'An open-source, in-memory data structure store used as a cache and message broker.', NOW()),
('database', 'An organized collection of structured data typically stored electronically.', NOW()),
('docker', 'A platform used to build, ship, and run software inside lightweight, portable containers.', NOW()),
('api', 'A set of definitions and protocols for building and integrating application software.', NOW()),
('json', 'A lightweight data-interchange format that is easy for humans to read and write.', NOW()),
('yaml', 'A human-friendly, serialization language commonly used for configuration files.', NOW());

Create a Spring Boot Project

To set up our Spring Boot project, we’ll use Spring Initializr. Set your project details as shown below, choose the dependencies listed, then click “Generate” to download your starter project.

Java Spring Initializr

Spring Initializr | A Quickstart Generator for Spring Projects

Here’s what the project structure will look like:

src/
├── main/
│   ├── java/
│   │   └── com/
│   │       └── dragonfly/
│   │           └── dictionary/
│   │               ├── DictionaryApplication.java
│   │               ├── controller/
│   │               │   └── DictionaryController.java
│   │               ├── model/
│   │               │   ├── DictionaryEntry.java
│   │               │   └── DictionaryResponse.java
│   │               ├── repository/
│   │               │   └── DictionaryRepository.java
│   │               └── service/
│   │                   └── DictionaryService.java
│   └── resources/
│       └── application.yml

Service Connections Setup

Before actually running our application server, let’s take a look at a few code snippets to have a better understanding. The code below initializes the database and cache connections for our dictionary service.

// src/main/java/com/dragonfly/dictionary/DictionaryApplication.java
package com.dragonfly.dictionary;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@SpringBootApplication
@EnableCaching
public class DictionaryApplication {

    public static void main(String[] args) {
        SpringApplication.run(DictionaryApplication.class, args);
    }
}
# src/main/resources/application.yml
spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/dictionarydb
    username: postgres
    password: postgres
  jpa:
    hibernate:
      ddl-auto: update
  data:
    # Using the Redis client to connect to and manipulate data in Dragonfly.
    # By using Docker, Dragonfly is running locally on port 6380.
    redis:
      host: localhost
      port: 6380

server:
  port: 8080

First, we enable caching in our Spring Boot application using the @EnableCaching annotation. The application configuration connects to PostgreSQL for persistent storage and Dragonfly for caching. Note that we are using the Redis client here talking to our Dragonfly server running on port 6380. For production deployments, you would replace these localhost URLs with your actual service endpoints, ideally fetched from a secure location, while keeping the same connection logic.

Database Schema

The code below defines our dictionary_entry table schema using JPA annotations. Note that you can also pluralize or customize your table names by using the @Table(name = "XXX") annotation. In this example, we keep it simple with the default. Each dictionary record contains a UUID primary key, the word itself, and its definition. The timestamps track when the entry was last updated. The @Column constraints ensure data integrity by preventing null values in all fields.

// src/main/java/com/dragonfly/dictionary/model/DictionaryEntry.java
package com.dragonfly.dictionary.model;

import jakarta.persistence.*;
import java.time.LocalDateTime;
import java.util.UUID;

@Entity
public class DictionaryEntry {

    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;

    @Column(unique = true, nullable = false)
    private String word;

    @Column(nullable = false, length = 4096)
    private String definition;

    @Column(nullable = false)
    private LocalDateTime updatedAt = LocalDateTime.now();

    // Constructors.
    public DictionaryEntry() {}

    public DictionaryEntry(String word, String definition) {
        this.word = word;
        this.definition = definition;
    }

    // Getters and setters.
    public UUID getId() {
        return id;
    }

    public void setId(UUID id) {
        this.id = id;
    }

    public String getWord() {
        return word;
    }

    public void setWord(String word) {
        this.word = word;
    }

    public String getDefinition() {
        return definition;
    }

    public void setDefinition(String definition) {
        this.definition = definition;
    }

    public LocalDateTime getUpdatedAt() {
        return updatedAt;
    }

    public void setUpdatedAt(LocalDateTime updatedAt) {
        this.updatedAt = updatedAt;
    }
}

A few deliberate design choices warrant explanation: The word field uses a unique constraint, ensuring no duplicate words can exist in our dictionary. For the definition field, we use a 4096-character limit, which should cover most dictionary definitions. The updatedAt timestamp helps track when entries were last modified, useful for cache invalidation strategies.

Response Model

We also have a response model that includes cache details. The cached field will help us easily verify if our caching layer is working correctly when we run the application later.

// src/main/java/com/dragonfly/dictionary/model/DictionaryResponse.java
package com.dragonfly.dictionary.model;

public class DictionaryResponse {
    private String word;
    private String definition;
    private boolean cached;

    public DictionaryResponse() {}

    public DictionaryResponse(String word, String definition, boolean cached) {
        this.word = word;
        this.definition = definition;
        this.cached = cached;
    }

    // Getters and setters.
    public String getWord() {
        return word;
    }

    public void setWord(String word) {
        this.word = word;
    }

    public String getDefinition() {
        return definition;
    }

    public void setDefinition(String definition) {
        this.definition = definition;
    }

    public boolean isCached() {
        return cached;
    }

    public void setCached(boolean cached) {
        this.cached = cached;
    }
}

Repository Layer

The code below creates the repository interface using Spring Data JPA. This provides a type-safe abstraction over database operations, allowing us to work with entities rather than raw SQL:

// src/main/java/com/dragonfly/dictionary/repository/DictionaryRepository.java
package com.dragonfly.dictionary.repository;

import com.dragonfly.dictionary.model.DictionaryEntry;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.UUID;
import java.util.Optional;

public interface DictionaryRepository extends JpaRepository<DictionaryEntry, UUID> {
    Optional<DictionaryEntry> findByWordIgnoreCase(String word);
}

This repository interface extends JpaRepository, which provides standard CRUD operations. The findByWordIgnoreCase method demonstrates Spring Data JPA’s query method feature, automatically generating a case-insensitive query based on the method name.

Backend Server API

Now we have both the database and repository layer defined. Let’s look at the code for the API endpoint for our Spring Boot server. The endpoint below takes in a word and returns its definition with cache information. It showcases a complete cache-first optimization flow for dictionary lookups.

The handler below implements the lookup logic with cache-first optimization. When a request hits our dictionary endpoint (e.g., /dictionary/cache), it first checks the caching layer using the word as the key. If cached (hot path), it returns the definition immediately. On cache misses (cold path), it falls back to PostgreSQL. Whenever there’s a cache miss, it reloads the word back into the cache. This ensures commonly requested words are quickly available again. Many backend systems use this two-layered approach, combining the fast responses of caching with the reliability of a database. This setup helps maintain consistent performance and low response times for popular words.

// src/main/java/com/dragonfly/dictionary/service/DictionaryService.java
package com.dragonfly.dictionary.service;

import com.dragonfly.dictionary.repository.DictionaryRepository;
import com.dragonfly.dictionary.model.DictionaryResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;

@Service
public class DictionaryService {

    @Autowired
    private DictionaryRepository dictionaryRepository;

    @Autowired
    private CacheManager cacheManager;

    public DictionaryResponse getDefinitionWithCacheInfo(String word) {
        String cacheKey = word.toLowerCase();
        Cache cache = cacheManager.getCache("dictionaryCache");
        boolean cached = false;
        String definition = null;

        if (cache != null && cache.get(cacheKey) != null) {
            definition = (String) cache.get(cacheKey).get();
            cached = true;
        } else {
		        // Cache miss: read from database, and then cache the record 
            definition = dictionaryRepository.findByWordIgnoreCase(word)
                .map(entry -> entry.getDefinition())
                .orElse("Word not found.");
            if (cache != null) {
                cache.put(cacheKey, definition);
            }
        }

        return new DictionaryResponse(word, definition, cached);
    }
}

Controller Layer

And finally, here’s the simple code for our REST controller:

// src/main/java/com/dragonfly/dictionary/controller/DictionaryController.java
package com.dragonfly.dictionary.controller;

import com.dragonfly.dictionary.service.DictionaryService;
import com.dragonfly.dictionary.model.DictionaryResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/dictionary")
public class DictionaryController {

    @Autowired
    private DictionaryService dictionaryService;

    @GetMapping("/{word}")
    public DictionaryResponse getDefinition(@PathVariable String word) {
       return dictionaryService.getDefinitionWithCacheInfo(word);
    }
}

Running the Backend Server

Now that you understand all the different parts of the application, it’s finally time to try it out!

Run mvn clean install to install dependencies, then start the server with mvn spring-boot:run. Your app will launch at http://localhost:8080.


Testing the API

Let’s test our dictionary API by making a request. Since this is the first request, it will retrieve the definition directly from the database.

$> curl http://localhost:8080/dictionary/dragonfly

You’ll get this response:

{
  "word": "dragonfly",
  "definition": "A fully Redis-compatible, high-performance in-memory data store.",
  "cached": false
}

Now, let’s repeat the request for the same word to check if it’s served from the cache:

$> curl http://localhost:8080/dictionary/cache

The response this time:

{
  "word": "dragonfly",
  "definition": "A fully Redis-compatible, high-performance in-memory data store.",
  "cached": true
}

Notice how the cached value changes from false to true after the first request, indicating the response is now served directly from the cache.

Inspecting the Cache

You can also directly inspect your cache using Redis CLI.

# Connect to Dragonfly:
$> redis-cli -p 6380

# Inspect cached keys in Dragonfly:
dragonfly$> SCAN 0
1) "0"
2) 1) "dictionaryCache::dragonfly"

dragonfly$> GET dictionaryCache::dragonfly
"\xac\xed\x00\x05t\x00@A fully Redis-compatible, high-performance in-memory data store."

This confirms our dictionary API is correctly caching common lookups to ensure faster responses.


Same API, More Performance

At Dragonfly, the team embraces the same philosophy as many robust Java projects do: we don’t break compatibility for the sake of change. By preserving the Redis API while rebuilding the underneath architecture for modern multi-core hardware, Dragonfly delivers drop-in compatibility with both vertical (multi-core) and horizontal (multi-node) scalability, without forcing developers to rewrite their apps.

Ready to experience the difference? Get started with Dragonfly locally, or deploy a fully managed data store on Dragonfly Cloud, which will provision and be ready to use in under 5 minutes as well!

Dragonfly Wings

Stay up to date on all things Dragonfly

Join our community for unparalleled support and insights

Join

Switch & save up to 80%

Dragonfly is fully compatible with the Redis ecosystem and requires no code changes to implement. Instantly experience up to a 25X boost in performance and 80% reduction in cost