---
title: Replay
---

import { Tabs, TabItem } from '@astrojs/starlight/components';

A reactor sees the same event twice for different reasons: once as it happens, and again whenever its observer
is replayed. Those often call for different work — a confirmation that should go out once when an order is
placed has no business going out again while a read model is being rebuilt.

Mark a second handler for the same event type with the `Replay` attribute and it takes over for the duration
of the replay.

<Tabs syncKey="chronicle-client">
<TabItem label="C#">

```csharp
using Cratis.Chronicle.Events;
using Cratis.Chronicle.Reactors;

[EventType]
public record ReplayAwareOrderPlaced(string OrderId);

public class ReplayAwareOrderReactor : IReactor
{
    public void SendConfirmation(ReplayAwareOrderPlaced @event)
    {
        // Runs as the event happens.
    }

    [Replay]
    public void RebuildProjectionCache(ReplayAwareOrderPlaced @event)
    {
        // Runs instead of SendConfirmation while the observer is replaying.
    }
}
```

</TabItem>
<TabItem label="Kotlin">

```kotlin
import io.cratis.chronicle.events.EventType
import io.cratis.chronicle.observation.Reactor
import io.cratis.chronicle.observation.Replay

@EventType(id = "replay-aware-order-placed")
data class ReplayAwareOrderPlaced(val orderId: String)

@Reactor
class ReplayAwareOrderReactor {
    fun sendConfirmation(event: ReplayAwareOrderPlaced) {
        // Runs as the event happens.
    }

    @Replay
    fun rebuildProjectionCache(event: ReplayAwareOrderPlaced) {
        // Runs instead of sendConfirmation while the observer is replaying.
    }
}
```

</TabItem>
<TabItem label="Java">

```java
import io.cratis.chronicle.events.EventType;
import io.cratis.chronicle.observation.Reactor;
import io.cratis.chronicle.observation.Replay;

@EventType(id = "replay-aware-order-placed")
record ReplayAwareOrderPlaced(String orderId) {}

@Reactor
class ReplayAwareOrderReactor {
    void sendConfirmation(ReplayAwareOrderPlaced event) {
        // Runs as the event happens.
    }

    @Replay
    void rebuildProjectionCache(ReplayAwareOrderPlaced event) {
        // Runs instead of sendConfirmation while the observer is replaying.
    }
}
```

</TabItem>
<TabItem label="TypeScript">

```typescript
import { EventContext, eventType, ICanBeNotifiedWhenReplay, reactor } from '@cratis/chronicle';

@eventType()
class ReplayAwareOrderPlaced {
    constructor(readonly orderId: string = '') {}
}

// Implement ICanBeNotifiedWhenReplay to be told when a full replay of this reactor's
// observation begins and ends - useful for suppressing side effects (e.g.
// notifications) while historical events are being reprocessed. A throwing hook marks
// the batch Failed, the same as a handler that throws.
@reactor()
class ReplayAwareOrderReactor implements ICanBeNotifiedWhenReplay {
    private _isReplaying = false;

    async beginReplay(): Promise<void> {
        this._isReplaying = true;
    }

    async endReplay(): Promise<void> {
        this._isReplaying = false;
    }

    async replayAwareOrderPlaced(event: ReplayAwareOrderPlaced, context: EventContext): Promise<void> {
        if (this._isReplaying) {
            // Runs during replay too - skip side effects that must not repeat.
            return;
        }

        // Runs as the event happens for the first time.
    }
}
```

</TabItem>
</Tabs>

The rules are:

- With a `Replay` handler, **only** it runs during a replay — the regular handler does not also run.
- Without one, the regular handler runs during a replay exactly as it always has, so adding this to one event
  type changes nothing for the others.
- An event type handled **only** by a `Replay` handler is still subscribed to, so the replay it exists for
  delivers it.

Reach for [OnceOnly](/chronicle/reactors/once-only/) instead when the side effect should simply not happen again. Use `Replay` when
a replay needs to do something *different* rather than nothing. Neither covers the re-delivery that follows a
failed partition being recovered — for that, see [Delivery identity](/chronicle/reactors/delivery-identity/).

:::note[Client coverage]
Elixir and TypeScript have no equivalent marker, so a replay always re-runs the regular handler there.
:::
