Sitemap

Ensuring Thread Safety with Synchronized Blocks for webSessionId in a Java Spring Boot REST API

4 min readMay 16, 2025

In a Java Spring Boot REST API, managing concurrent access to shared resources is critical to prevent race conditions and ensure data consistency. One common scenario involves updating a webSessionId field, which might represent a unique session identifier for a web request. Without proper synchronization, concurrent threads could overwrite or corrupt this field, leading to unpredictable behavior. In this article, we’ll explore how to use synchronized (lock) blocks to ensure thread safety when updating this.webSessionId in a Spring Boot REST API.

The Problem: Concurrent Access to webSessionId

Imagine a Spring Boot REST API that handles user sessions. A service class maintains a webSessionId field to track the current session. Multiple threads (e.g., handling simultaneous HTTP requests) may attempt to update this field concurrently. Without synchronization, this can lead to race conditions, where one thread’s update might be overwritten by another, causing session mismatches or data corruption.

Here’s an example of a problematic implementation:

@Service
public class SessionService {
private String webSessionId;

public void updateSessionId(String newSessionId) {
this.webSessionId = newSessionId; // Not thread-safe!
}

public String getSessionId() {
return this.webSessionId;
}
}

In this code, if two threads call updateSessionId simultaneously, the final value of webSessionId is unpredictable. This is where Java’s synchronization mechanisms, such as synchronized blocks, come to the rescue.

Solution: Using Synchronized Blocks for Thread Safety

Java provides the synchronized keyword to ensure that only one thread can execute a block of code at a time. By using a synchronized block, we can protect the critical section where this.webSessionId is updated, ensuring thread safety.

Here’s an updated version of the SessionService class with a synchronized block:

@Service
public class SessionService {
private String webSessionId;
private final Object lock = new Object(); // Dedicated lock object

public void updateSessionId(String newSessionId) {
synchronized (lock) {
this.webSessionId = newSessionId; // Thread-safe update
}
}

public String getSessionId() {
synchronized (lock) {
return this.webSessionId; // Thread-safe read
}
}
}

Key Points About the Implementation

  • Dedicated Lock Object: We use a private Object (lock) as the synchronization monitor. This is preferred over synchronizing on this or the class instance, as it avoids potential interference from other code that might lock the same object.
  • Synchronized Block for Updates: The synchronized (lock) block ensures that only one thread can update webSessionId at a time. Other threads attempting to enter the block will wait until the lock is released.
  • Synchronized Read: To ensure consistent reads, we also synchronize the getSessionId method. This prevents a thread from reading webSessionId while another thread is updating it.
  • Minimal Critical Section: The synchronized block only encloses the critical section (the read or write operation), minimizing the performance impact of locking

Integrating with a Spring Boot REST API

Let’s see how this fits into a Spring Boot REST API. Below is an example of a REST controller that uses the SessionService to manage webSessionId.

@RestController
@RequestMapping("/api/session")
public class SessionController {
private final SessionService sessionService;

@Autowired
public SessionController(SessionService sessionService) {
this.sessionService = sessionService;
}

@PostMapping("/update")
public ResponseEntity<String> updateSession(@RequestBody String newSessionId) {
sessionService.updateSessionId(newSessionId);
return ResponseEntity.ok("Session ID updated successfully");
}

@GetMapping
public ResponseEntity<String> getSession() {
return ResponseEntity.ok(sessionService.getSessionId());
}
}

How It Works

  • POST /api/session/update: A client sends a new session ID in the request body. The controller calls sessionService.updateSessionId, which updates webSessionId in a thread-safe manner.
  • GET /api/session: The client retrieves the current webSessionId, which is read safely using the synchronized getSessionId method.
  • Thread Safety: The synchronized blocks in SessionService ensure that concurrent requests (e.g., multiple clients updating the session ID simultaneously) don’t cause race conditions.

Alternative Approaches

While synchronized blocks are effective, there are alternative ways to achieve thread safety in this scenario:

  1. Synchronized Methods: Instead of synchronized blocks, you could mark the updateSessionId and getSessionId methods as synchronized. However, this locks the entire method, which might be overkill if the method contains non-critical code.
public synchronized void updateSessionId(String newSessionId) {
this.webSessionId = newSessionId;
}

ReentrantLock: Java’s ReentrantLock provides more flexibility than synchronized blocks, such as the ability to try acquiring a lock without blocking. However, it’s more verbose and may be unnecessary for simple use cases.

private final ReentrantLock lock = new ReentrantLock();

public void updateSessionId(String newSessionId) {
lock.lock();
try {
this.webSessionId = newSessionId;
} finally {
lock.unlock();
}
}

Atomic Variables: If webSessionId is a simple value, you could use AtomicReference<String> to avoid explicit locking. However, this is less suitable if the update involves complex logic.

private final AtomicReference<String> webSessionId = new AtomicReference<>();

public void updateSessionId(String newSessionId) {
webSessionId.set(newSessionId);
}

Spring’s @Transactional: If webSessionId is stored in a database, you could use Spring’s @Transactional annotation to ensure atomic updates. This is more relevant for persistence-layer thread safety.

Best Practices

  • Use a Dedicated Lock Object: Avoid synchronizing on this or the class to prevent unintended lock contention.
  • Minimize the Critical Section: Only synchronize the code that accesses shared resources to reduce performance overhead.
  • Consider Thread-Safe Alternatives: If the use case allows, explore thread-safe data structures or atomic variables to simplify the code.
  • Test Under Load: Simulate concurrent requests using tools like JMeter or Gatling to verify thread safety.
  • Avoid Over-Synchronization: Excessive locking can lead to performance bottlenecks, so synchronize only when necessary.

I hope this was helpful :)

Thanks,

Kailash

JavaCharter!

Javacharter
Javacharter

Written by Javacharter

At Java Charter you can get evolve with Java and technologies like Spring , Spring Boot , REST APIs, Microservices, Various Tools, Azure Cloud etc…

No responses yet