Spring Boot Makes Development Easy — Until It Doesn’t

 The Hidden Cost of Abstraction in Spring Boot

I used to think Spring Boot was removing complexity. Now I realize it was just relocating it.

From my code to places I wasn’t looking.

Every abstraction removes code you write and adds code you don’t see.

Let’s expose that hidden code 👇No fluff. Just practical examples.

1️⃣ The N+1 Query You Didn’t Write

You wrote this:

@GetMapping("/orders")
public List<Order> getOrders() {
return orderRepository.findAll();
}

Entity:

@Entity
public class Order {

@Id
private Long id;
@OneToMany(mappedBy = "order", fetch = FetchType.LAZY)
private List<OrderItem> items;
}

Looks innocent 😇

Now enable SQL logging:

spring.jpa.show-sql=true
logging.level.org.hibernate.SQL=DEBUG

Hit the endpoint.

What actually runs:

select * from orders;
select * from order_items where order_id=1;
select * from order_items where order_id=2;
select * from order_items where order_id=3;
select * from order_items where order_id=4;
select * from order_items where order_id=5;

⚠️ 1 + N queries.

Why?

Because:

  • Default fetch = LAZY
  • Serialization triggers lazy loading
  • Hibernate fires queries per order

✅ Fix (Explicit Fetch)

@Query("SELECT o FROM Order o JOIN FETCH o.items")
List<Order> findAllWithItems();

Now it executes:

select o.*, i.* 
from orders o
join order_items i on o.id = i.order_id;

🧠 Lesson:

  • Abstraction hides SQL.
  • Logging exposes truth.

2️⃣ @Transactional Isn’t Magic

You wrote:

@Service
public class PaymentService {

@Transactional
public void process() {
save();
}

@Transactional
public void save() {
repository.save(new Payment());
}
}

You think:
 ✔ Both methods transactional

Reality:
 ❌ save() is NOT transactional here.

Why?

Spring uses proxies (AOP).

Internal calls bypass proxy.

Under the hood:

PaymentService proxy = new TransactionalProxy(new PaymentService());
proxy.process(); // transaction starts

But:

this.save();  // direct call → no proxy

✅ Correct Pattern

Split services:

@Service
public class PaymentInternalService {

@Transactional
public void save() {
repository.save(new Payment());
}
}

Inject it:

@Service
public class PaymentService {

private final PaymentInternalService internal;
public PaymentService(PaymentInternalService internal) {
this.internal = internal;
}
public void process() {
internal.save();
}
}

💡 Abstraction cost:

  • Proxy-based logic
  • Hidden execution path
  • Surprising bugs

3️⃣ Reflection Overhead (Yes, It Exists)

Spring builds beans using reflection.

Let’s compare.

Direct call

calculator.add(1, 2);

Reflection call

Method method = Calculator.class
.getMethod("add", int.class, int.class);

method.invoke(calculator, 1, 2);

⏱ Rough benchmark (1M calls):

  • Direct → ~5ms
  • Reflection → ~50ms

Now multiply that by:

  • Bean initialization
  • AOP proxies
  • Validation interceptors

Abstraction = startup time tax.

4️⃣ Dirty Checking: Hibernate’s Hidden Work

Code:

@Transactional
public void update(Long id) {
Order order = orderRepository.findById(id).get();
order.setCustomerName("Updated");
}

You didn’t call save().

Still works.

Why?

Hibernate tracks entity snapshot:

  • Stores original state
  • Compares before commit
  • Generates UPDATE query

Behind the scenes:

update orders set customer_name = 'Updated' where id = 1;

⚠ Hidden costs:

  • Extra memory
  • Comparison CPU cycles
  • Harder debugging

If you load 10,000 entities:

💣 Dirty checking becomes expensive.

✅ Explicit Update Alternative

@Modifying
@Query("UPDATE Order o SET o.customerName = :name WHERE o.id = :id")
void updateName(@Param("id") Long id,
@Param("name") String name)
;

No comparisons.
Direct SQL.

5️⃣@Autowired Everywhere = Invisible Dependencies

Field injection:

@Autowired
private UserRepository repository;

Hidden dependency.

Constructor injection:

private final UserRepository repository;

public UserService(UserRepository repository) {
this.repository = repository;
}

-------------------- OR ---------------------

@RequiredArgsConstructor

private final UserRepository repository;

Why better?

✅ Explicit
✅ Immutable
✅ Easy unit testing
✅ No reflection field injection

Abstraction convenience often reduces clarity.

6️⃣ Thread Pool Bottleneck Nobody Talks About

Spring MVC = blocking. Each request consumes a thread.

Default Tomcat threads ≈ 200.

If your DB is slow, adding more app instances may just amplify DB pressure:

  • Threads block
  • New requests wait
  • CPU idle

You didn’t configure it. Abstraction did.

👉 Check:

server.tomcat.threads.max=200

If you don’t understand this, horizontal scaling won’t fix a saturated blocking resource.

7️⃣ Fat JAR & Memory Reality

Simple REST app:

mvn clean package

Result:

  • 20MB+ jar
  • 120–180MB RAM usage at runtime

Because Spring Boot includes:

  • Embedded server
  • Auto configs
  • Class path scanning

👉 Compare minimal HTTP server using:

  • Netty

Memory can drop below 50MB.

Abstraction trades:

🟢 Dev speed
🔴 Runtime efficiency

8️⃣ What I Do in Real Projects

Here’s my practical checklist:

🔎 Always:

  • Enable SQL logs in dev
  • Profile memory under load
  • Inspect generated queries
  • Use DTOs instead of exposing entities

🧱 Avoid:

  • Returning lazy entities from controller
  • Overusing @Transactional
  • Blindly trusting defaults
  • FetchType.EAGER everywhere

⚡ Optimize:

  • Use projections
  • Use explicit queries
  • Measure before caching

🎯 Final Reality

Spring Boot is powerful.

But:

  • It hides SQL.
  • It hides memory cost.

Abstraction isn’t free. It’s just prepaid.

You pay later:

  • In performance debugging
  • In production surprises

Thanks for reading. As always, feel free to drop a comment, be sure to clap 👏

Follow the author for update: https://medium.com/@pramod.er90

I keep my writing free for anyone who wants to read it. If this helped you in any way, you can fuel my next idea with a coffee ☕ Buy me a coffee? — It’s completely Optional — but always Appreciated.

Writer : Swaraj Verma


— Bhuwan Chettri
Editor, CodeToDeploy

CodeToDeploy Is a Tech-Focused Publication Helping Students, Professionals, And Creators Stay Ahead with AI, Coding, Cloud, Digital Tools, And Career Growth Insights.

Post a Comment

Previous Post Next Post