In modern microservice architectures, performance and scalability are not optional — they are the foundation. When APIs serve hundreds of requests per second, even milliseconds add up. Every database call, if repeated, can drain response time and system resources. That’s where caching comes in.
\ Caching means temporarily storing frequently accessed data in memory so that future requests can be served much faster without hitting the database.
\ Among various caching solutions, Redis stands out as:
\ Here, we’ll build a real-world API using Spring Boot 3.2 and integrate Redis caching to achieve high performance with minimal code.
Imagine you’re building a Service for an e-commerce platform.
\ This approach reduces:
Generate from https://start.spring.io
\ Dependencies
\ Use
\ Folder Structure
springboot-redis-cache/ │ ├── src/main/java/com/example/redis/ │ ├── controller/ │ │ └── ProductController.java │ ├── entity/ │ │ └── Product.java │ ├── repository/ │ │ └── ProductRepository.java │ ├── service/ │ │ └── ProductService.java │ ├── RedisCacheApplication.java │ └── resources/ ├── application.yml └── data.sql
package com.example.rediscache.model; import jakarta.persistence.*; import lombok.*; @Entity @Table(name = "product") @Data @NoArgsConstructor @AllArgsConstructor public class Product { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private Double price; /** * @return the id */ // Default constructor (required for JPA and deserialization) public Product() {} // All-arguments constructor public Product(Long id, String name, double price) { this.id = id; this.name = name; this.price = price; } // Getters and setters public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } }
\ This simple entity represents a product stored in the database.
package com.example.rediscache.repository; import com.example.rediscache.model.Product; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface ProductRepository extends JpaRepository<Product, Long> { }
package com.example.rediscache.service; import com.example.rediscache.model.Product; import com.example.rediscache.repository.ProductRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.*; import org.springframework.stereotype.Service; import java.util.List; @Service @CacheConfig(cacheNames = {"product", "allProducts"}) public class ProductService { @Autowired private ProductRepository repo; @Cacheable(value = "product", key = "#id") public Product getProductById(Long id) { System.out.println("[DB] Fetching product by id: " + id); return repo.findById(id).orElse(null); } @Cacheable(value = "allProducts") public List<Product> getAllProducts() { System.out.println("[DB] Fetching all products"); return repo.findAll(); } @Caching(put = {@CachePut(value = "product", key = "#result.id")}, evict = {@CacheEvict(value = "allProducts", allEntries = true)}) public Product saveOrUpdate(Product product) { Product p = repo.save(product); System.out.println("[DB] Saved product: " + p.getId()); return p; } @CacheEvict(value = "product", key = "#id") public void deleteProduct(Long id) { repo.deleteById(id); System.out.println("[DB] Deleted product: " + id); } }
Key annotations explained
| Annotation | Purpose | |----|----| | @Cacheable | First checks Redis; if not found, calls DB and stores in cache | | @CachePut | Updates both the DB and the cache simultaneously | | @CacheEvict | Removes the entry from the cache when a record is deleted | | @CacheConfig | Sets a common cache name for the service |
package com.example.rediscache.controller; import com.example.rediscache.model.Product; import com.example.rediscache.service.ProductService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.net.URI; import java.util.List; @RestController @RequestMapping("/api/products") public class ProductController { @Autowired private ProductService service; @GetMapping("/{id}") public ResponseEntity<Product> getProduct(@PathVariable Long id) { Product p = service.getProductById(id); if (p == null) return ResponseEntity.notFound().build(); return ResponseEntity.ok(p); } @GetMapping public ResponseEntity<List<Product>> getAll() { return ResponseEntity.ok(service.getAllProducts()); } @PostMapping public ResponseEntity<Product> create(@RequestBody Product product) { Product p = service.saveOrUpdate(product); return ResponseEntity.created(URI.create("/api/products/" + p.getId())).body(p); } @PutMapping("/{id}") public ResponseEntity<Product> update(@PathVariable Long id, @RequestBody Product product) { product.setId(id); Product p = service.saveOrUpdate(product); return ResponseEntity.ok(p); } @DeleteMapping("/{id}") public ResponseEntity<String> delete(@PathVariable Long id) { service.deleteProduct(id); return ResponseEntity.ok("Deleted"); } }
spring: cache: type: redis redis: host: localhost port: 6379 datasource: url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE driver-class-name: org.h2.Driver username: sa password: jpa: hibernate: ddl-auto: update show-sql: false logging: level: root: INFO com.example.rediscache: DEBUG
package com.example.rediscache; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCaching; @SpringBootApplication @EnableCaching public class SpringbootRedisCacheApplication { public static void main(String[] args) { SpringApplication.run(SpringbootRedisCacheApplication.class, args); } }
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.32</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
Run
redis-server
\ Keep it running. Verify in CMD
redis-cli ping
\ Output
PONG
Run As → Java Application
POST → http://localhost:8080/api/products { "name": "MacBook", "price": 1699.99 }
Console log
Fetching from DB for ID: 1
\ RedisInsight now shows a key:
products::4
GET → http://localhost:8080/products/1
\ Output (same as before), but console log doesn’t show “Fetching from DB” — because it’s now served directly from Redis.
\ Cache Hit Successful!
DELETE → http://localhost:8080/products/1
\ Then check RedisInsight again — the key products::1 disappears.
\ Cache Eviction Successful!
Open RedisInsight (https://redis.io/insight)
\ Steps:
Connect → localhost:6379
Go to “Keys” tab. You’ll see cached entries like products::1.
Double-click to view serialized data. You’ll see cached object details, including ID, name, and price.
On deletion, it disappears immediately — proving cache eviction.
You can set cache expiry to automatically refresh stale data
spring: cache: redis: time-to-live: 60000 # 60 seconds
| Operation Type | Without Cache (MySQL) | With Redis Cache | |----|----|----| | First Fetch | ~120 ms | ~120 ms | | Subsequent | ~5 ms | ~2 ms | | Delete Product | 90 ms | 90 ms + Evict |
Result: ~60x faster on repeated reads!
| Benefit | Description | |----|----| | Blazing Fast | Data served from memory, not DB | | Cost Efficient | Reduces DB reads & compute load | | Smart Expiration | TTLs prevent stale data | | Reusability | Cache layer works across services | | Reliable | Redis supports clustering & persistence |
By integrating Redis caching into your Spring Boot application
| Concept | Description | |----|----| | @Cacheable | Fetch from cache or DB if missing | | @CachePut | Update cache with new value | | @CacheEvict | Remove entry from cache | | Redis TTL | Expire cache automatically |
One design pattern, one configuration — massive performance gains.


