The session itself is thread-safe. But that doesn't mean that using it without any kind of synchronization leads to correct behavior. It only means that the session will behave correctly if accessed by multiple threads.
For example, assume you have the following code executed concurrently:
Integer visitCount = (Integer) session.getAttribute("visitCount");
visitCount = Integer.valueOf(visitCount.intValue() + 1);
session.setAttribute("visitCount", visitCount);
And assume the original value of visitCount
is 0. The end result could be 2 (the expected value), or it could be 1. Indeed, both threads could read the current value conncurrently (0), then both increment it to 1, and both store it in the session.
Similarly, if you store a thread-unsafe object in the session (like a HashMap for example), and two threads use this HashMap concurrently, you could see erratic behavior.
As with every multi-threaded program, you must use appropriate synchronization mechanisms when necessary, and/or make your objects thread-safe. But this is the subject for a whole book. I would recommend Java Concurrency in practice.