Spark Structured Streaming Output Modes: Append vs Update vs Complete
Databricks Spark Developer · Trade-off

Spark Structured Streaming Output Modes: Append vs Update vs Complete

An Apache Spark Structured Streaming output mode decides which rows of the result table reach the sink on each trigger. Append, the default, writes only new rows, each exactly once. Update writes only rows that changed since the last trigger. Complete rewrites the entire result every time and works only for aggregation queries.

Last updated October 2026.

The short version

No aggregation: use append. A running aggregate going into a sink that can upsert: use update. A small aggregate the consumer replaces wholesale: use complete. Windowed aggregates going into files: append plus a watermark, because the file sink accepts nothing else.

What an output mode controls

Structured Streaming treats a stream as an unbounded result table that grows with every micro-batch. The output mode, set with writeStream.outputMode(), answers one question: on each trigger, which rows of that result table does the sink receive?

Whether the query aggregates decides which modes are legal. An unsupported combination fails at start() with an AnalysisException naming the output mode, so the mistake surfaces before any data moves.

Append: new rows, each written once

outputMode("append") is the default. Only rows added to the result table since the last trigger are written, and each row is written exactly once and never revised. That suits queries whose rows never change: select, filter, withColumn, stream-static joins, and stream-stream joins.

An aggregation can use append only on an event-time window with a watermark. Spark emits a window's row once the watermark passes the window's end, when the count can no longer change. Without a watermark, Spark cannot know when a window is finished, so the query is rejected.

from pyspark.sql.functions import window

counts = (clicks
    .withWatermark("ts", "15 minutes")
    .groupBy(window("ts", "10 minutes"))
    .count())

counts.writeStream.outputMode("append").format("parquet") \
    .option("path", "/out/clicks").option("checkpointLocation", "/chk/clicks").start()

Update: only the rows that changed

outputMode("update") writes only the result rows inserted or changed in this trigger. If Germany's total moved and France's did not, only Germany is written. Aggregates appear immediately, with or without a watermark, and with a watermark old state is cleaned up. For a query with no aggregation, update behaves exactly like append.

The catch is the sink. It receives the same key again and again, so it must be able to overwrite or upsert. The file sink cannot, which leaves Kafka, foreachBatch (for example a MERGE into a table) or a debugging sink.

Complete: the whole result, every trigger

outputMode("complete") writes the entire result table on every trigger, changed or not, and the sink replaces what it had. It works for aggregation queries only, since keeping every unaggregated row would be infeasible. It is also the only mode in which a stream can be sorted, after an aggregation.

The cost: all aggregate state is kept forever. A watermark cannot drop old groups, because the full result has to be reproduced each time. Fine for a count per country, expensive for millions of growing keys.

Which query allows which mode

Queryappendupdatecomplete
No aggregationYesYes (same as append)No
Aggregation, no watermarkNoYesYes
Event-time window with watermarkYes, after the window closesYes, old state droppedYes, state kept
Stream-stream joinYesNoNo

Sourced from the output modes and supported-operations sections of the Apache Spark Structured Streaming Programming Guide.

Sink support: why files mean append

def upsert_totals(batch_df, batch_id):
    batch_df.createOrReplaceTempView("updates")
    batch_df.sparkSession.sql("""
        MERGE INTO totals t USING updates u ON t.customer = u.customer
        WHEN MATCHED THEN UPDATE SET * WHEN NOT MATCHED THEN INSERT *""")

(per_customer.writeStream
    .foreachBatch(upsert_totals)
    .outputMode("update")
    .option("checkpointLocation", "/chk/per_customer")
    .start())

Update mode suits this: each trigger merges only the customers that changed. foreachBatch is at-least-once by default; use the batch ID to make writes idempotent.

Decision signals for the exam

Structured Streaming is 10% of the Databricks Certified Associate Developer for Apache Spark exam in the official guide, and its writing objective names output modes and sinks directly. These signals settle most questions:

Exam tip: the usual trap is the right mode with the wrong setup: append on a windowed count with no watermark, or update into the file sink. Check the sink and the watermark first.

FAQ

What is the default output mode in Spark Structured Streaming?

Append is the default output mode. It writes only the rows added to the result table since the last trigger, each exactly once, and it is the only mode the file sink accepts.

What is the difference between update and complete output mode in Spark?

Update writes only the result rows that changed in the current trigger, while complete rewrites the entire result table every trigger. Complete also keeps all aggregate state forever, so it suits small results, while update suits large keyed results going into a sink that can upsert.

Can I use append mode with an aggregation in Structured Streaming?

Yes, but only on an event-time window with a watermark set by withWatermark. Spark then emits each window once, after the watermark passes the window's end; without a watermark the query is rejected when it starts.

Which output modes does the file sink support in Spark?

The file sink supports append mode only, with exactly-once guarantees. To write a running aggregate in update mode, use Kafka or foreachBatch, for example to MERGE each micro-batch into a table.

Does a watermark reduce state in complete output mode?

No. Complete mode reproduces the whole result table on every trigger, so Spark keeps all aggregate state regardless of the watermark. A watermark only drops old state in append and update modes.

Practice this hands-on

Output modes and sinks are a trade-off chapter in the Spark Developer Associate course, with the support matrix stepped through card by card and two quiz cards that test opposite directions.

Open the chapter →