Job Board
Consulting

Spark Scala Collect List and Set

collect_list and collect_set are aggregate functions that gather the values in each group into an array. collect_list keeps every value, including duplicates; collect_set keeps only the distinct ones. array_agg is a SQL alias for collect_list. They're the go-to tools when you want to roll many rows up into a single row that holds all their values.

Gathering values with collect_list

collect_list takes a column — either by name or as a Column — and returns an array containing every value it sees in the group, duplicates and all:

def collect_list(columnName: String): Column

def collect_list(e: Column): Column

A typical use is rolling each customer's individual order rows up into one array of products:

val df = Seq(
  ("Alice", "Keyboard"),
  ("Alice", "Mouse"),
  ("Alice", "Keyboard"),
  ("Bob", "Monitor"),
  ("Bob", "Cable"),
  ("Carol", "Laptop"),
).toDF("customer", "product")

val df2 = df
  .groupBy("customer")
  .agg(collect_list("product").as("products"))
  .orderBy("customer")

df2.show(false)
// +--------+---------------------------+
// |customer|products                   |
// +--------+---------------------------+
// |Alice   |[Keyboard, Mouse, Keyboard]|
// |Bob     |[Monitor, Cable]           |
// |Carol   |[Laptop]                   |
// +--------+---------------------------+

Notice that Alice's array contains Keyboard twice — collect_list preserves every occurrence. The order of elements within each array is not guaranteed; it reflects the order Spark happened to process the rows. If you need a specific order, apply array_sort or sort_array to the result, or use collect_list as a window function over an ordered window.

Keeping only distinct values with collect_set

collect_set works exactly like collect_list but removes duplicates, returning the set of distinct values in each group:

def collect_set(columnName: String): Column

def collect_set(e: Column): Column
val df = Seq(
  ("Alice", "Keyboard"),
  ("Alice", "Mouse"),
  ("Alice", "Keyboard"),
  ("Bob", "Monitor"),
  ("Bob", "Cable"),
  ("Carol", "Laptop"),
).toDF("customer", "product")

val df2 = df
  .groupBy("customer")
  .agg(collect_set("product").as("products"))
  .orderBy("customer")

df2.show(false)
// +--------+-----------------+
// |customer|products         |
// +--------+-----------------+
// |Alice   |[Mouse, Keyboard]|
// |Bob     |[Cable, Monitor] |
// |Carol   |[Laptop]         |
// +--------+-----------------+

Alice's duplicate Keyboard is collapsed to a single entry. As with collect_list, the ordering of the result is not guaranteed — a set has no inherent order, so don't rely on the positions you see here.

array_agg via expr()

array_agg is a SQL-standard alias for collect_list — it collects values into an array, keeping duplicates. It isn't exposed in org.apache.spark.sql.functions, so you call it through expr():

The array_agg function first appeared in version 3.3.0.

array_agg(expr) — via expr()

Reach for it when you're porting SQL that already uses array_agg, or when the name reads more naturally in your pipeline. The result is identical to collect_list:

val df = Seq(
  ("Alice", "Keyboard"),
  ("Alice", "Mouse"),
  ("Alice", "Keyboard"),
  ("Bob", "Monitor"),
  ("Bob", "Cable"),
  ("Carol", "Laptop"),
).toDF("customer", "product")

val df2 = df
  .groupBy("customer")
  .agg(expr("array_agg(product)").as("products"))
  .orderBy("customer")

df2.show(false)
// +--------+---------------------------+
// |customer|products                   |
// +--------+---------------------------+
// |Alice   |[Keyboard, Mouse, Keyboard]|
// |Bob     |[Monitor, Cable]           |
// |Carol   |[Laptop]                   |
// +--------+---------------------------+

How nulls are handled

Both functions skip null values entirely — nulls never appear in the resulting array. If every value in a group is null, you get an empty array rather than an array of nulls:

val df = Seq(
  ("Alice", Some("Keyboard")),
  ("Alice", None),
  ("Alice", Some("Mouse")),
  ("Bob", None),
  ("Bob", None),
  ("Carol", Some("Laptop")),
).toDF("customer", "product")

val df2 = df
  .groupBy("customer")
  .agg(
    collect_list("product").as("list_products"),
    collect_set("product").as("set_products"),
  )
  .orderBy("customer")

df2.show(false)
// +--------+-----------------+-----------------+
// |customer|list_products    |set_products     |
// +--------+-----------------+-----------------+
// |Alice   |[Keyboard, Mouse]|[Mouse, Keyboard]|
// |Bob     |[]               |[]               |
// |Carol   |[Laptop]         |[Laptop]         |
// +--------+-----------------+-----------------+

Alice's None is dropped from both arrays, and Bob — whose only values were null — ends up with empty arrays. This is worth remembering when the array length matters: size on the collected column counts non-null values, not the total number of rows in the group.

To count rows or distinct values in a group instead of collecting them, see count and countDistinct. Once you have an array, array_distinct removes duplicates from an existing collect_list result, and array_sort puts the elements in a predictable order. To pick a single representative value from each group rather than all of them, see first and last.

Example Details

Created: 2026-07-18 10:33:55 PM

Last Updated: 2026-07-18 10:33:55 PM