Logo
Published on

I Removed a Single Annotation in Java and My API Became 50x Faster

Authors
  • Name
    Shanvika Devi

👉Not a Member — Read for free here :

Have you ever looked at your Java backend and thought: “Why is this so slow? Everything looks fine on the surface, but the performance is worse than my old laptop running Windows XP.”

That was me a few months ago. Our API response times were horrible, memory usage was unpredictable, and sometimes the server behaved like it was stuck in traffic.

And the funniest part? The root cause was just one tiny annotation. Yes, not a library, not an algorithm, not some complicated refactor — just a simple annotation that every Spring Boot developer has probably used without thinking twice.

When I replaced it, our backend became 50x faster.

Let me walk you through the story.

The Slow Backend Mystery

We had a Spring Boot application serving millions of requests every day. Everything was written cleanly: controllers, services, repositories. Nothing too fancy.

But our biggest issue: API latency.

  • Some endpoints were taking 2–3 seconds to respond.
  • Even simple GET calls were struggling.
  • CPU usage was through the roof.

We threw in more servers, scaled horizontally, and even added caching layers. Nothing helped.

So I decided to profile the application.

Step 1: Profiling the Code

I fired up VisualVM and started digging. The CPU profiling showed that a lot of time was spent on reflection and proxy creation.

That was strange. Why would our code be spending so much energy creating proxies?

After digging deeper, I found the culprit:

@Transactionalpublic User getUserById(Long id) {    return userRepository.findById(id).orElseThrow();}

Yes, the innocent-looking @Transactional annotation.

Step 2: The Transactional Trap

@Transactional is one of the most common annotations in Spring. We use it everywhere. It makes life simple:

  • You don’t need to manually begin or commit transactions.
  • Spring manages rollbacks for you.
  • It saves developers from a lot of boilerplate.

But here’s the catch:  @Transactional works by creating a dynamic proxy around your method.

This proxy checks transaction boundaries before and after every call. If you misuse it or place it in the wrong spot, it becomes a hidden performance killer.

And that’s exactly what happened in our case.

Step 3: The Wrong Usage

We had sprinkled @Transactional all over the service layer, even for simple read-only queries.

Example:

@Transactionalpublic List<Order> getRecentOrders() {    return orderRepository.findRecent();}

For a method that just fetches data, wrapping it in a transaction was unnecessary.

Even worse — by default, @Transactional is read-write, which means it was unnecessarily locking resources, forcing the database to handle transactions even for SELECT queries.

No wonder our database and CPU were crying for help.

Step 4: The Fix

I replaced all these generic @Transactional annotations with a more precise one:

@Transactional(readOnly = true)public List<Order> getRecentOrders() {    return orderRepository.findRecent();}

And for simple repository calls that didn’t need transactions at all, I removed the annotation completely.

For write operations, I kept @Transactional as usual.

Step 5: The Shocking Result

After making this change, we redeployed.

The numbers blew my mind:

  • API latency dropped from 2.5 seconds → 50 ms.
  • CPU load decreased by more than 70%.
  • Database contention issues almost disappeared.
  • Memory usage became stable.

Overall, the backend was now 50x faster.

And all of this because of one annotation being replaced/optimized.

Why This Works

When you set readOnly = true, Spring:

  1. Skips unnecessary flush operations in Hibernate.
  2. Optimizes transaction handling for SELECT queries.
  3. Reduces proxy overhead and unnecessary locks.

Without this, your database thinks every query might be modifying data, so it treats everything cautiously. That’s like driving at 20 km/h on an empty highway just because you might need to brake suddenly.

Lesson for Every Java Developer

Here’s what I learned (and what you should remember):

  • Don’t sprinkle @Transactional everywhere just because it looks neat.
  • Use @Transactional(readOnly = true) for queries.
  • Remove @Transactional if your method doesn’t need a transaction at all.
  • Profile your app regularly — small mistakes create big performance costs.

Final Words

Sometimes, the biggest bottlenecks in your Java backend are not in your algorithms or architecture. They’re in tiny defaults we take for granted.

I didn’t rewrite the codebase, I didn’t change the framework, I didn’t buy more servers.

I just replaced one annotation.

And that gave me a backend that was 50x faster.

So the next time your API feels slow, don’t just blame the database or hardware. Maybe it’s just one annotation laughing at you.

#Java #SpringBoot #BackendDevelopment #CodeOptimization #PerformanceTuning #Microservices #DevelopersLife #ProgrammingTips #APIs #CodingJourney

A message from our Founder

Hey, Sunil here. I wanted to take a moment to thank you for reading until the end and for being a part of this community.

Did you know that our team run these publications as a volunteer effort to over 3.5m monthly readers? We don’t receive any funding, we do this to support the community. ❤️

If you want to show some love, please take a moment to follow me on LinkedIn, TikTok, Instagram. You can also subscribe to our weekly newsletter.