Class KeyValueEntity<S>

Object
akka.javasdk.keyvalueentity.KeyValueEntity<S>
Type Parameters:
S - The type of the state for this entity

public abstract class KeyValueEntity<S> extends Object
Key Value Entities are stateful components that persist their complete state on every change. Unlike Event Sourced Entities, only the latest state is stored without access to historical changes.

Key Value Entities provide strong consistency guarantees through entity sharding, where each entity instance is identified by a unique id and distributed across the service cluster. Only one instance of each entity exists in the cluster at any time, ensuring sequential message processing without concurrency concerns.

The entity state is kept in memory while active and can serve read requests or command validation without additional reads from durable storage. Inactive entities are passivated and recover their state from durable storage when accessed again.

Implementation Steps

  1. Define the entity's state
  2. Implement behavior in command handlers

Command Handlers

Command handlers are methods that return an KeyValueEntity.Effect and define how the entity responds to commands. The Effect API allows you to:
  • Update the entity state and send a reply to the caller
  • Reply directly without state changes
  • Return an error message
  • Delete the entity

Example Implementation


 @ComponentId("counter")
 public class CounterEntity extends KeyValueEntity<Counter> {
   
   public CounterEntity(KeyValueEntityContext context) {
     // Constructor can accept KeyValueEntityContext and custom dependency types
   }
   
   @Override
   public Counter emptyState() {
     return new Counter(0);
   }
   
   public Effect<Counter> set(int value) {
     Counter newCounter = new Counter(value);
     return effects()
         .updateState(newCounter)
         .thenReply(newCounter);
   }
   
   public ReadOnlyEffect<Counter> get() {
     return effects().reply(currentState());
   }
 }
 

Concrete classes can accept the following types in the constructor:

Concrete classes must be annotated with ComponentId with a stable, unique identifier that cannot be changed after production deployment.

Multi-region Replication

Key Value Entities support multi-region replication for resilience and performance. Write requests are handled by the primary region, while read requests can be served from any region. Use KeyValueEntity.ReadOnlyEffect for read-only operations that can be served from replicas.

Immutable state record

It is recommended to use immutable state objects, such as Java records, for the entity state. Immutable state ensures thread safety and prevents accidental modifications that could lead to inconsistent state or concurrency issues.

While mutable state classes are supported, they require careful handling:

  • Mutable state should not be shared outside the entity
  • Mutable state should not be passed to other threads, such as in CompletionStage operations
  • Any modifications to mutable state must be done within the entity's command handlers

Collections in State: Collections (such as List, Set, Map) are typically mutable even when contained within immutable objects. When updating state that contains collections, you should create copies of the collections rather than modifying them in place. This ensures that the previous state remains unchanged and prevents unintended side effects.

Performance Considerations: Defensive copying of collections can introduce performance overhead, especially for large collections or frequent updates. In performance-critical scenarios, this recommendation can be carefully tuned by using mutable state with strict adherence to the safety guidelines mentioned above.

Using immutable records with defensive copying of collections eliminates concurrency concerns and is the preferred approach for state modeling in most cases.

  • Constructor Details

    • KeyValueEntity

      public KeyValueEntity()
  • Method Details

    • emptyState

      public S emptyState()
      Returns the initial empty state object for this entity. This state is used when the entity is first created and before any commands have been processed.

      Also known as "zero state" or "neutral state". This method is called when the entity is instantiated for the first time or when recovering from storage without any persisted state.

      The default implementation returns null. Override this method to provide a more meaningful initial state for your entity.

      Returns:
      the initial state object, or null if no initial state is defined
    • commandContext

      protected final CommandContext commandContext()
      Provides access to additional context and metadata for the current command being processed. This includes information such as the command name, entity id, and tracing context.

      This method can only be called from within a command handler method. Attempting to access it from the constructor or outside of command processing will result in an exception.

      Returns:
      the command context for the current command
      Throws:
      IllegalStateException - if accessed outside a command handler method
    • currentState

      protected final S currentState()
      Returns the current state of this entity as stored in memory. This represents the latest persisted state and any updates made during the current command processing.

      Important: Modifying the returned state object directly will not persist the changes. To save state changes, you must use effects().updateState(newState) in your command handler's return value.

      This method can only be called from within a command handler method. Attempting to access it from the constructor or outside of command processing will result in an exception.

      Returns:
      the current state of the entity, which may initially be null if emptyState() has not been defined
      Throws:
      IllegalStateException - if accessed outside a command handler method
    • isDeleted

      protected boolean isDeleted()
      Returns whether this entity has been marked for deletion. When an entity is deleted using effects().deleteEntity(), it will still exist with an empty state for some time before being completely removed.

      After deletion, the entity can still handle read requests but will have an empty state. No further state changes are allowed after deletion. The entity will be completely cleaned up after a default period of one week.

      Returns:
      true if the entity has been deleted, false otherwise
    • effects

      protected final KeyValueEntity.Effect.Builder<S> effects()