Core Java

Java 21 Virtual Threads: The Future of Concurrency

📅 March 14, 2025 ⏱ 8 min read ✍️ Suravi Instructor

What Are Virtual Threads?

Java 21 officially graduates Virtual Threads (JEP 444) as a production-ready feature. Unlike platform (OS) threads, virtual threads are lightweight threads managed by the JVM itself — you can create millions of them without worrying about memory or scheduling overhead.

Key Insight: Virtual threads don't replace OS threads — they sit on top of them. The JVM multiplexes many virtual threads onto a small pool of carrier (OS) threads automatically.

Creating Virtual Threads

The API is deliberately familiar. Here are three ways to spin up a virtual thread:

// 1. Thread.ofVirtual()
Thread vt = Thread.ofVirtual()
    .name("vt-worker")
    .start(() -> System.out.println("Hello!"));

// 2. Executors.newVirtualThreadPerTaskExecutor()
try (var exec = Executors
        .newVirtualThreadPerTaskExecutor()) {
  exec.submit(() -> processRequest());
}

// 3. Thread.startVirtualThread()
Thread.startVirtualThread(() -> fetchData());

Spring Boot 3.2 Integration

Spring Boot 3.2 added first-class support. A single property enables virtual threads for the embedded Tomcat:

# application.properties
spring.threads.virtual.enabled=true

That's it. Your Spring MVC controllers now handle each request on a virtual thread — no WebFlux, no reactive operators, no callback hell.

Performance Benchmarks

In benchmarks simulating 10,000 concurrent HTTP connections with I/O blocking (database + external API calls):

When NOT to Use Virtual Threads

Virtual threads excel at I/O-bound workloads. For CPU-bound tasks (image processing, encryption), platform threads are still the right choice — you don't want millions of threads fighting for CPU cores.

Pinning Warning: Avoid holding monitors (synchronized blocks) during blocking I/O inside virtual threads — this "pins" the carrier thread. Use ReentrantLock instead.

Conclusion

Virtual threads are arguably the most impactful Java feature in a decade. They let you write simple, blocking-style code and get near-reactive throughput — the best of both worlds. Migrate today by adding a single property to your Spring Boot 3.2+ app.

Java 21Project Loom ConcurrencySpring Boot 3Performance
More Articles Below ↓ ← Back to Blog
Spring Boot

Building REST APIs with Spring Boot 3 & Spring Security 6

📅 February 28, 2025 ⏱ 10 min read ✍️ Suravi Instructor

Why Spring Security 6 Changed Everything

Spring Security 6 (shipped with Spring Boot 3) dropped the deprecated WebSecurityConfigurerAdapter. You now configure security entirely through SecurityFilterChain beans — a more composable and testable approach that makes your security config a proper Spring bean.

Project Setup (pom.xml)

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt-api</artifactId>
  <version>0.12.3</version>
</dependency>

The SecurityFilterChain Bean

@Configuration
@EnableWebSecurity
public class SecurityConfig {

  @Bean
  public SecurityFilterChain filterChain(
      HttpSecurity http) throws Exception {

    return http
      .csrf(csrf -> csrf.disable())
      .sessionManagement(s -> s
        .sessionCreationPolicy(STATELESS))
      .authorizeHttpRequests(auth -> auth
        .requestMatchers("/api/auth/**").permitAll()
        .anyRequest().authenticated())
      .addFilterBefore(jwtFilter,
        UsernamePasswordAuthenticationFilter.class)
      .build();
  }
}

JWT Token Generation

public String generateToken(UserDetails userDetails) {
  return Jwts.builder()
    .subject(userDetails.getUsername())
    .issuedAt(new Date())
    .expiration(new Date(
      System.currentTimeMillis() + 86_400_000))
    .signWith(getSigningKey())
    .compact();
}

JWT Filter — Validating Each Request

@Component
public class JwtAuthFilter
    extends OncePerRequestFilter {

  @Override
  protected void doFilterInternal(
      HttpServletRequest req,
      HttpServletResponse res,
      FilterChain chain) throws Exception {

    String header = req.getHeader("Authorization");
    if (header != null && header.startsWith("Bearer ")) {
      String token = header.substring(7);
      String username = jwtService.extractUsername(token);
    }
    chain.doFilter(req, res);
  }
}

Testing the Secured Endpoint

# 1. Login and get token
curl -X POST http://localhost:8080/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"pass"}'

# 2. Call protected endpoint
curl http://localhost:8080/api/products \
  -H "Authorization: Bearer <your-token>"
Best Practice: Store JWTs in HttpOnly cookies rather than localStorage to protect against XSS attacks. Never store sensitive tokens in sessionStorage either.

Summary

Spring Security 6 modernises the configuration model significantly. The move to SecurityFilterChain beans makes your security setup more testable, composable, and easier to understand. Combine it with JWT for a robust stateless REST API.

Spring Boot 3Spring Security 6 JWTREST APIAuthentication
More Articles Below ↓ ← Back to Blog
Hibernate & JPA

Hibernate N+1 Problem: Detection & Fix with Spring Data JPA

📅 February 10, 2025 ⏱ 7 min read ✍️ Suravi Instructor

What Is the N+1 Problem?

Imagine loading 100 Orders, each with a list of OrderItems. Without care, Hibernate fires 1 query for orders, then 100 more to fetch each order's items — 101 queries total. On production data sets this is catastrophic for performance.

// This triggers N+1 silently!
List<Order> orders = orderRepo.findAll();
orders.forEach(o ->
  o.getItems().size()); // lazy load — 1 query each

Detecting It with Hibernate Statistics

First, enable statistics to count queries per request:

# application.properties
spring.jpa.properties.hibernate
  .generate_statistics=true
logging.level.org.hibernate.stat=DEBUG

The log will show StatisticsImpl entries revealing total query counts. Any number far above expected confirms a problem.

Fix 1 — JPQL Fetch Join

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

Fix 2 — Entity Graph

@EntityGraph(attributePaths = {"items"})
List<Order> findAll();

Fix 3 — @BatchSize

@BatchSize(size = 25)
@OneToMany(mappedBy = "order")
private List<OrderItem> items;
Recommendation: Use fetch joins for guaranteed eager loading. Use @BatchSize as a fallback. Never use FetchType.EAGER globally — it solves N+1 but creates worse problems.

Quick Comparison

Conclusion

The N+1 problem is one of the most common Hibernate pitfalls. Enable statistics in development, profile your queries, identify the hotspots, and apply the right fix. Always measure before and after to confirm improvement in production-like conditions.

HibernateJPA N+1 QueryPerformanceSpring Data
More Articles Below ↓ ← Back to Blog

Article Not Found

Please select an article from the blog section.

← Back to Blog