Back to Spark Streaming & Structured Streaming
Q19Question 19 of 22

What are the window components in Flink

Key Concepts
  • The window assigner decides which window(s) each event belongs to: tumbling (one), sliding (many), session (gap-based), global (all).
  • The trigger decides when a window fires and returns CONTINUE, FIRE, PURGE, or FIRE_AND_PURGE.
  • Event-time windows default to EventTimeTrigger (fires on watermark); global windows require an explicit trigger or they never emit.
  • The evictor optionally removes elements before or after the window function, with built-in CountEvictor, TimeEvictor, and DeltaEvictor.
  • Using an evictor disables incremental aggregation because Flink must buffer every raw element, increasing memory from O(1) to O(window_size).
  • Components interact in strict order: assigner routes, trigger decides, evictor prunes, window function computes.

Definition

The assigner puts events into windows, the trigger decides when each window fires, and the optional evictor trims elements around the firing, and they interact in a strict assign-then-trigger-then-evict order.
  • Window assigner – Decides, for each incoming element, which window or windows it belongs to (tumbling, sliding, session, or global).
  • Trigger – Decides when a window's computation fires and emits a result; returns CONTINUE, FIRE, PURGE, or FIRE_AND_PURGE.
  • Evictor – Optional component that removes elements from the window buffer before or after the window function runs.
  • Default triggers – Event-time windows default to EventTimeTrigger (fires on watermark), processing-time to ProcessingTimeTrigger, global window has no useful default.
  • Evictor cost – Using an evictor forces Flink to buffer every raw element, disabling incremental aggregation and increasing memory usage.

Think of the National Bank of India sorting transaction slips into labeled trays. The window assigner is the clerk who reads each slip's time and drops it into the correct minute-labeled tray. The trigger is the supervisor who decides the moment to say "process this tray now." The evictor is an optional helper who, just before the supervisor processes a tray, removes certain slips, for example keeping only the last ten so the computation reflects only recent activity.

Understanding the Concepts

What is a window assigner?

The window assigner decides which window or windows each incoming element belongs to. You set it with the .window(...) call. Different assigners produce different window shapes.

JAVA
import org.apache.flink.streaming.api.windowing.assigners.*;
import org.apache.flink.streaming.api.windowing.time.Time;

// Tumbling: each element in exactly one 1-minute window
stream.keyBy(txn -> txn.getAccountId())
    .window(TumblingEventTimeWindows.of(Time.minutes(1)));

// Sliding: each element in multiple overlapping windows
stream.keyBy(txn -> txn.getAccountId())
    .window(SlidingEventTimeWindows.of(Time.minutes(1), Time.seconds(10)));

// Session: elements grouped by gap
stream.keyBy(txn -> txn.getAccountId())
    .window(EventTimeSessionWindows.withGap(Time.minutes(5)));

// Global: all elements in one window, needs custom trigger
stream.keyBy(txn -> txn.getAccountId())
    .window(GlobalWindows.create());

What is a trigger?

The trigger decides when the window function actually runs. It returns a TriggerResult: CONTINUE (do nothing), FIRE (emit but keep window), PURGE (clear window), or FIRE_AND_PURGE (emit then clear).

JAVA
import org.apache.flink.streaming.api.windowing.triggers.*;

// EventTimeTrigger: fires when watermark passes window end (default)
stream.window(TumblingEventTimeWindows.of(Time.minutes(1)))
    .trigger(EventTimeTrigger.create())
    .sum("amount");

// CountTrigger: fires every N elements
stream.window(GlobalWindows.create())
    .trigger(CountTrigger.of(100))
    .sum("amount");

// ProcessingTimeTrigger: fires on wall clock
stream.window(TumblingProcessingTimeWindows.of(Time.minutes(1)))
    .trigger(ProcessingTimeTrigger.create())
    .sum("amount");
DIAGRAM
TriggerResult values:
  CONTINUE       → do nothing, keep accumulating
  FIRE           → run window function, keep window contents
  PURGE          → clear window contents, do not emit
  FIRE_AND_PURGE → run window function, then clear contents

How do you build a custom trigger?

You can override the default trigger to fire early for speculative results and then finally on the watermark.

JAVA
import org.apache.flink.streaming.api.windowing.triggers.Trigger;
import org.apache.flink.streaming.api.windowing.triggers.TriggerResult;
import org.apache.flink.streaming.api.windowing.windows.TimeWindow;

public class EarlyFiringTrigger
        extends Trigger<Transaction, TimeWindow> {

    private final int earlyCount;

    public EarlyFiringTrigger(int earlyCount) {
        this.earlyCount = earlyCount;
    }

    @Override
    public TriggerResult onElement(Transaction txn, long timestamp,
            TimeWindow window, TriggerContext ctx) {
        // Register event-time timer for final firing
        ctx.registerEventTimeTimer(window.maxTimestamp());
        // Count elements, fire early every earlyCount
        long count = ctx.getPartitionedState(
            new ValueStateDescriptor<>("count", Long.class, 0L)).value();
        count++;
        if (count >= earlyCount) {
            ctx.getPartitionedState(
                new ValueStateDescriptor<>("count", Long.class, 0L))
                .update(0L);
            return TriggerResult.FIRE;  // speculative result
        }
        return TriggerResult.CONTINUE;
    }

    @Override
    public TriggerResult onEventTime(long time, TimeWindow window,
            TriggerContext ctx) {
        return TriggerResult.FIRE_AND_PURGE;  // final result
    }

    @Override
    public TriggerResult onProcessingTime(long time, TimeWindow window,
            TriggerContext ctx) {
        return TriggerResult.CONTINUE;
    }

    @Override
    public void clear(TimeWindow window, TriggerContext ctx) {}
}

What is an evictor?

The evictor is optional and removes elements from the window buffer before or after the window function runs. Built-in evictors include CountEvictor, TimeEvictor, and DeltaEvictor.

JAVA
import org.apache.flink.streaming.api.windowing.evictors.*;

// CountEvictor: keep only the last 10 elements
stream.keyBy(txn -> txn.getAccountId())
    .window(TumblingEventTimeWindows.of(Time.minutes(1)))
    .evictor(CountEvictor.of(10))
    .sum("amount");

// TimeEvictor: keep only elements within the last 30 seconds
stream.keyBy(txn -> txn.getAccountId())
    .window(TumblingEventTimeWindows.of(Time.minutes(1)))
    .evictor(TimeEvictor.of(Time.seconds(30)))
    .sum("amount");

How do the three components interact?

The interaction has a strict order: for each element, the assigner selects its windows and adds it to each window's buffer, the trigger is consulted and may say fire, and if an evictor is present it prunes the buffer before and after the window function runs.

JAVA
// Full composition: assigner + custom trigger + evictor
stream.keyBy(txn -> txn.getAccountId())
    .window(TumblingEventTimeWindows.of(Time.minutes(1))) // assigner
    .trigger(new EarlyFiringTrigger(50))  // fire every 50 elements + final
    .evictor(CountEvictor.of(10))          // keep only last 10
    .apply(windowFunction);

// Execution order:
// 1. Assigner: event lands in [10:00, 10:01) window
// 2. Trigger: consulted -- CONTINUE or FIRE?
// 3. If FIRE and evictor present:
//    a. Evictor.evictBefore: prune buffer
//    b. WindowFunction.apply: compute over survivors
//    c. Evictor.evictAfter: prune again (rarely used)

Why does using an evictor disable incremental aggregation?

Attaching an evictor forces Flink to buffer every raw element in the window rather than incrementally aggregating, because the evictor needs access to individual elements to decide which to remove. This disables the memory-efficient incremental path.

JAVA
// WITHOUT evictor: incremental aggregation
stream.window(TumblingEventTimeWindows.of(Time.minutes(1)))
    .reduce((a, b) -> merge(a, b));  // only one accumulated value in memory

// WITH evictor: full buffer
stream.window(TumblingEventTimeWindows.of(Time.minutes(1)))
    .evictor(CountEvictor.of(10))
    .reduce((a, b) -> merge(a, b));  // ALL elements buffered in memory!
// Memory usage: O(window_size) instead of O(1)

What are common mistakes?

Using an evictor without understanding the memory cost is the most common mistake. Another is forgetting to set a trigger on a global window, which means it never fires.

JAVA
// WRONG -- global window without trigger, never fires
stream.window(GlobalWindows.create())
    .sum("amount");  // Never emits! No trigger defined.

// CORRECT -- global window with explicit trigger
stream.window(GlobalWindows.create())
    .trigger(CountTrigger.of(100))
    .sum("amount");  // Fires every 100 elements

What should a senior engineer know?

Only reach for an evictor when you genuinely need element-level pruning, because it costs memory. Otherwise prefer a plain reduce or aggregate which Flink can run incrementally. A custom trigger gives speculative early results and a final correct one, which is the standard pattern for real-time dashboards that want both speed and eventual correctness.

Visual Overview

DIAGRAM
  Window Component Interaction Order

  Event arrives
       │
       ▼
  ┌─────────────┐
  │  Assigner   │  Decides which window(s) the event belongs to
  │  Tumbling /  │
  │  Sliding /   │
  │  Session     │
  └──────┬──────┘
         │ event added to window buffer
         ▼
  ┌─────────────┐
  │  Trigger    │  Should the window fire now?
  │  CONTINUE   │  → wait for more events
  │  FIRE       │  → proceed to window function
  │  PURGE      │  → clear window
  │  FIRE+PURGE │  → emit then clear
  └──────┬──────┘
         │ if FIRE:
         ▼
  ┌─────────────┐
  │  Evictor    │  (optional) prune buffer
  │  evictBefore│  → remove elements before function
  └──────┬──────┘
         │
         ▼
  ┌─────────────┐
  │  Window     │  Compute aggregation over surviving elements
  │  Function   │
  └──────┬──────┘
         │
         ▼
  ┌─────────────┐
  │  Evictor    │  (optional) prune after function
  │  evictAfter │
  └─────────────┘

Key Takeaway

A Flink window is built from three cooperating components that interact in a strict order. The window assigner decides, for every incoming element, which window or windows it belongs to. A tumbling assigner puts an element into exactly one window, a sliding assigner can put one element into several overlapping windows, a session assigner groups bursts, and the global assigner puts everything into a single window.

The trigger decides when a window fires. Event-time windows default to firing when the watermark passes the window end, processing-time windows fire on the wall clock, and global windows need an explicit trigger like CountTrigger. A trigger returns CONTINUE, FIRE, PURGE, or FIRE_AND_PURGE, and you can build custom triggers that fire early for speculative results.

The evictor is optional and runs around the window function, removing elements from the buffer before or after computation. The crucial point is that attaching an evictor forces Flink to retain every raw element instead of aggregating incrementally, so it disables the memory-efficient path. Only reach for an evictor when you truly need element-level pruning.

At the National Bank of India Rajesh used a one-minute tumbling window with a custom trigger that fired every 50 transactions for speculative dashboard updates, and a count evictor that kept only the last 10 for a rolling risk average. He justified the evictor's memory cost because the use case genuinely required element-level access.

Remember one sentence: The assigner routes events to windows, the trigger decides when to fire, and the optional evictor prunes elements, but attaching an evictor disables incremental aggregation and increases memory, so use it only when needed.

---

Share
Use ← → arrow keys to navigate