Caching with Micronaut: Superheroes superquick
Caching with Micronaut

I am a developer who loves Java, Spring, Quarkus, Micronaut, Open source, Microservices, Cloud
Search for a command to run...
Caching with Micronaut

I am a developer who loves Java, Spring, Quarkus, Micronaut, Open source, Microservices, Cloud
No comments yet. Be the first to comment.
Micronaut is one of the promising java based frameworks in the Microservices space. Here is a series that I have started on Microservices framework based on Micronaut.
Call Superheroes via Command Center
Spring AI Tool Calling: From Chatbot to AI Agent with @Tool Your AI is smart. It knows an enormous amount. But it's frozen. It doesn't know what time it is right now. It doesn't know what's on your ca
Spring AI Advisors API Explained Series: Spring AI Complete Course — Lecture 4 of 12Reading Time: 8 minutesLevel: Intermediate Most developers stop at ChatClient. That is enough for demos. It is not
Working with Multiple AI Models in Spring AI Spring AI Complete Course — Lecture 3 of 12Previous: Lecture 2 — ChatClient API | Next: Lecture 4 — Advisors API In production AI applications, you rarel
Spring AI ChatClient API: The Fluent Heart of AI Integration Introduction If you've ever tried integrating AI models into a Java application, you know the pain. HTTP clients, API keys scattered everywhere, vendor-specific SDKs that never quite fit. W...
What is Spring AI? — Why Java Developers Need This in 2026 Every AI tutorial you see is in Python. LangChain, LlamaIndex, OpenAI SDK — all Python. But here's the uncomfortable truth: 80% of enterprise backends run Java. So who's building AI into thos...
Any framework needs to be fast enough to deliver content , Micronaut is no different. In order to deliver content faster, we depend on caching. Micronaut has inbuild support for caching with diffferent cache provider such as Redis, EhCache , Hazelcast etc. Here, we will explore caching implementation with Micronaut. If you wan't the full source code , you can find it below.
Let's explore micronaut caching.
The goal of this article is simple. We need a service that demonstrates caching capabilities.
We have created the application with a reactive database (postgres) . We will use same for adding caching capabilities to our projects. You can check the previous where we added testcontains at https://blog.pallav.dev/realtime-testing-micronaut-postgres-and-testcontainer You can also check the entire series at https://blog.pallav.dev/series/micronaut
Add below dependencies to pom.xml
<!-- 05 Cache Configs -->
<dependency>
<groupId>io.micronaut.cache</groupId>
<artifactId>micronaut-cache-caffeine</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.micronaut.cache</groupId>
<artifactId>micronaut-cache-management</artifactId>
</dependency>
<!-- 05 cache Configs ends-->
Let us add the properties to application yml ,which will tell the caches the system will configure
#cache related
micronaut:
caches:
superheroes:
charset: 'UTF-8'
superhero:
charset: 'UTF-8'
endpoints:
caches:
enabled: true
sensitive: false
Till now we have been injecting DAO to controller , now it's a good use case to create a service layer which will do the talking to cache & db . It act as bridge between controllers and others. Let us add the service layer . Create a Service class as following with Repository and Cache Injected to it
@Singleton
@CacheConfig("superheroes")//1
public class SuperheroService {
private static final Logger logger = LoggerFactory.getLogger(SuperheroService.class);
protected ReactiveSuperheroRepository reactiveSuperheroRepository;
@Inject
private CacheManager cacheManager;
SuperheroService(ReactiveSuperheroRepository superheroRepository) {
this.reactiveSuperheroRepository = superheroRepository;
}
public Flux<Superhero> superheroes() {
logger.info("Fetching all of the superheroes in universe ");
return reactiveSuperheroRepository.findAll();
}
@Cacheable( cacheNames = {"superheroes"}, parameters ={"id"} )//1
public Mono<Superhero> superheroesById(Long id) {
logger.info("Finding the saviour id {} ", id);
return reactiveSuperheroRepository.findById(id);
}
public Publisher<Superhero> create(Superhero superhero) {
logger.info("Adding a new saviour {} ", superhero);
var created = reactiveSuperheroRepository.save(superhero);
return created;
}
@CachePut( cacheNames = "superheroes", parameters = "id")//2
public Publisher<Superhero> update(Superhero superhero, Long id) {
logger.info("Updating the old saviour {} ", superhero);
var updated=reactiveSuperheroRepository.update(superhero);
return updated;
}
@CacheInvalidate(cacheNames = {"superheroes"},parameters = "id" ,async = true)//4
public Publisher<Long> delete(Long id) {
var deleted= reactiveSuperheroRepository.deleteById(id);
return deleted;
}
}
CacheConfig: It is optional to add if we use @Cacheable but added as just to show@Cacheable: As the name suggests, tells that this method can be cached cacheName stores names of the caches and parameters tells the id with which cache will be created. If the method has only one parameter, it can be avoided. POST method will add the superhero and once it will be retrieved from the database on GET. As the method is annotated with Cachaeable, next time whenever the same superhero will be called it will be given from cache rather than DB CachePut: Will update the cache with the given parameter (key), It will help us when there is a change in superhero properties. It will automatically cache the newer version with the same key. CacheInvalidate: removes the cache all together with parameter (key). This will invalidate the superhero so when it will be asked to give the same superhero again, we will not be getting the value from cache and a DB hit will happen.Till now we have been calling the database from Controller but now we will call the Service layer, so we will inject the same and remove the repository (which is moved to service)
@Controller("rx/")
public class ReactiveSuperHeroController {
private static final Logger logger = LoggerFactory.getLogger(SuperHeroController.class);
private SuperheroService service;
public ReactiveSuperHeroController(SuperheroService service) {
this.service = service;
}
@Get("superheroes")
public Flux<Superhero> superheroes() {
return service.superheroes();
}
@Get("superhero/{id}")
public Mono<Superhero> superheroesById(Long id) {
return service.superheroesById(id);
}
@Post("/superhero")
Single<Superhero> create(@Valid Superhero superhero) {
logger.info("Call to add a new saviour {} ", superhero);
return Single.fromPublisher(service.create(superhero));
}
@Put("superhero")
Single<Superhero> update( @Valid Superhero superhero) {
return Single.fromPublisher(service.update(superhero, superhero.id()));
}
@Delete("superhero/{id}")
Single<HttpResponse<?>> delete(@NotNull Long id) {
return Single
.fromPublisher(service.delete(id))
.map(deleted->deleted>0?HttpResponse.noContent():HttpResponse.notFound());
}
}
Now we can run the project and Create few Superheroes and do CRUD operations. We will keep an eye on logs to see if the method (GET) is being called multiple times. It is expected that the Cachable method will be called only when the call for the superhero is for the first time.
Below is the CURL request to create (POST)
curl --location --request POST 'http://localhost:8080/rx/superhero' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Justice Hammer",
"prefix": "Captain",
"suffix": "Walker",
"power": "Lightening Thunder"
}'
Now we will hit GET multiple times but logs will print only once from the service layer
curl --location --request GET 'http://localhost:8080/rx/superhero/1'
curl --location --request GET 'http://localhost:8080/rx/superhero/1'
curl --location --request GET 'http://localhost:8080/rx/superhero/1'
We can see in the logs below that the service layer is called once, post that it is not called as the values have been returned from the cache.
We can also update and check still, the GET log will not print as an update on the same hero will be also updated in the cache. Once deleted it will try again to check from DB as the cache is invalidated.
The logs of the above operation will be
our Superhero[id=null, name=Justice Hammer, prefix=Captain, suffix=Walker, power=Lightening Thunder]
00:42:06.300 [default-nioEventLoopGroup-1-3] INFO c.c.service.SuperheroService - Adding a new saviour Superhero[id=null, name=Justice Hammer, prefix=Captain, suffix=Walker, power=Lightening Thunder]
00:44:44.132 [default-nioEventLoopGroup-1-3] INFO c.c.service.SuperheroService - Finding the saviour id 1
We saw that caching has reduced the load on DB and significantly improved performance. If you have not noticed performance benefits, check time taken with the first call and the subsequent (Cacheable) method calls