Take a look at the TransactionalIndexedCollection[1].
Its JavaDoc provides guidance on atomically replacing objects.
It will require though, that you actually produce a new distinct version of your object for each mutation.
Using the TransactionalIndexedCollection will ensure that your individual changes will be made visible to reading threads atomically, and no query will see 2 versions of the same object in its results. It will also protect internal indexes from becoming inconsistent due to concurrent modification.
However be aware that for the use case you mentioned, that alone won't be enough to ensure thread safety.
For example, if 2 updates were received on your message bus around the same time, and processed in parallel by different threads, it would be possible that each thread would read the same version and attempt to race each other to store 2 different versions which lack each others updates.
To solve that you could look at using a Striped<Lock> or similar (e.g. from Google Guava).
When you receive an update from your message bus, read some unique identifier (or a key object which implements equals and hashCode) from the update. This would uniquely identify the object you are going to modify. Use that unique key or identifier to acquire a lock from the striped lock. Then read the current version of your object from the collection, update the collection with a new version of it, then release the lock.
This should ensure that if such updates arrive at the same time, there won't be any lost updates.
Hope that helps!
Niall