Backend Development 9 min read

Boost Spring Boot 3.2 Performance: CDS, Virtual Threads, GraalVM & Observability

This article explains how Spring Boot 3.2 can be accelerated with Class Data Sharing, virtual threads, GraalVM native images, ProblemDetail error handling, and new observability features, providing practical commands and code examples for faster startup, lower memory usage, and richer monitoring.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Boost Spring Boot 3.2 Performance: CDS, Virtual Threads, GraalVM & Observability

1. Class Data Sharing (CDS)

CDS is a JVM optimization that creates a snapshot of application classes and stores it in a shared archive file. When a Spring Boot application starts, the JVM can reference this archive, dramatically speeding up class loading, which is especially useful for large Spring Boot applications.

Use Cases

Microservices and cloud‑native apps: Frequent start‑stop of many small services benefits from reduced startup overhead.

Containerized environments: CDS shortens the time a container needs to become ready to serve traffic.

Long‑running applications: Although initial start‑up gain is less visible, each redeployment still enjoys faster class loading.

Resource‑constrained environments: Multiple JVMs can share the same class metadata, lowering memory consumption.

Enabling CDS

Add the following JVM option when the application exits to generate the archive:

<code>java -jar -XX:ArchiveClassesAtExit=app.jsa -Xlog:cds springboot-cds-1.0.0.jar</code>

The process exits after a full start and produces app.jsa . You can also generate the archive earlier with:

<code>java -jar -XX:ArchiveClassesAtExit=app.jsa -Dspring.context.exit=onRefresh -Xlog:cds springboot-cds-1.0.0.jar</code>

When the archive is available, start the application with:

<code>java -Xshare:on -XX:SharedArchiveFile=app.jsa -Xlog:class+load:file=cds.log -jar springboot-cds-1.0.0.jar</code>

Using CDS can cut startup time by roughly 50% compared with a regular launch.

2. Virtual Threads

Virtual threads are a highly anticipated feature that dramatically changes high‑concurrency programming. They are lightweight enough to allow thousands or even millions of concurrent tasks.

3. Native Image Compilation (GraalVM)

GraalVM native image compiles Java code into a standalone executable, delivering lightning‑fast startup and minimal memory usage. The trade‑off is loss of portability, requiring separate builds for each target platform.

<code>native-image -jar app.jar</code>

4. ProblemDetail HTTP Exception Details

Spring Boot 3 supports RFC 7807 ProblemDetail to return structured error information from REST APIs.

<code>@RestControllerAdvice
public class GlobalExceptionHandler {
  @ExceptionHandler(Exception.class)
  ProblemDetail handleException(Exception ex) {
    return ProblemDetail.forStatusAndDetail(HttpStatus.INTERNAL_SERVER_ERROR, ex.getMessage());
  }
}</code>

5. Observability Enhancements

Spring Boot 3 adds new metrics, tracing, and integration with observability tools. Example code shows how to create low‑cardinality tags and record observations.

<code>private final ObservationRegistry registry;
public ObservationController(ObservationRegistry registry) {
  this.registry = registry;
}
@GetMapping("/get")
public Object get() {
  Observation.start("obs", this.registry)
    .lowCardinalityKeyValue("method", "get")
    .observe(() -> {
      try { TimeUnit.MILLISECONDS.sleep(new Random().nextInt(1000)); } catch (InterruptedException e) {}
    });
  return "Observation GET";
}
@PostMapping("/post")
public Object post() {
  Observation.start("obs", this.registry)
    .lowCardinalityKeyValue("method", "post")
    .observe(() -> {
      try { TimeUnit.MILLISECONDS.sleep(new Random().nextInt(1000)); } catch (InterruptedException e) {}
    });
  return "Observation POST";
}</code>

High‑cardinality tags can be added for detailed tracing, viewable via Zipkin.

<code>@GetMapping("/{id}")
public Object get(@PathVariable Long id) {
  Observation.start("obs", this.registry)
    .lowCardinalityKeyValue("method", "get")
    .highCardinalityKeyValue("userId", String.valueOf(id))
    .observe(() -> {
      try { TimeUnit.MILLISECONDS.sleep(new Random().nextInt(1000)); } catch (InterruptedException e) {}
    });
  return "Observation Index";
}</code>
JVMperformanceObservabilitySpring BootVirtual ThreadsGraalVMcds
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.