Free Interactive Course · Spark + Scala

String Functions

Text is where most real data is messy. This module covers the Spark string toolkit end to end — joining, casing, trimming, padding, slicing, searching, splitting, and the two regex workhorses regexp_replace and regexp_extract — each with its signature and the ways you'll actually use it.

The dataset

This module uses the two DataFrames from Foundations:

peopleid, name, country, age, signup_date
ordersid, user_id, product, category, amount, qty, status, ordered_at
3.1

concat & concat_ws — joining strings

concat glues columns/values together end to end; concat_ws ("with separator") joins them with a delimiter and skips nulls. Both are imported from org.apache.spark.sql.functions.

Signature
def concat(exprs: Column*): Column
def concat_ws(sep: String, exprs: Column*): Column   // skips nulls

1. concat — end to end

Build a label like Wireless Mouse (Electronics) from two columns and literals:

Scala
import org.apache.spark.sql.functions.{concat, lit}

orders.select(
  concat($"product", lit(" ("), $"category", lit(")")).as("label")
).show(3, false)
Run the equivalent Spark SQL — edit it and press Run
SQL
⌘/Ctrl + Enter

2. concat_ws — with a separator

Far cleaner when you have a delimiter — no repeating the separator, and nulls are dropped automatically:

Scala
import org.apache.spark.sql.functions.concat_ws

orders.select(
  concat_ws(" - ", $"product", $"category", $"status").as("summary")
).show(3, false)
Run the equivalent Spark SQL — edit it and press Run
SQL
⌘/Ctrl + Enter
Null behaviour differs: in Spark, concat returns null if any argument is null, while concat_ws simply skips the null and keeps going. When joining columns that might be null, prefer concat_ws — or wrap args in coalesce first.
3.2

upper, lower, initcap & length

Casing and length are the everyday cleanup functions. upper/lower change case, initcap title-cases each word, and length returns the character count.

Signature
def upper(e: Column): Column     def lower(e: Column): Column
def initcap(e: Column): Column   // Title Case Each Word
def length(e: Column): Column    // number of characters
Scala
orders.select(
  $"product",
  upper($"category").as("cat_upper"),
  length($"product").as("name_len")
).show(3, false)
Run the equivalent Spark SQL — edit it and press Run
SQL
⌘/Ctrl + Enter

initcap uppercases the first letter of every word and lowercases the rest — great for normalising names typed in inconsistent case:

Scala
// initcap normalises 'aiSHA kHAN' -> 'Aisha Khan'
people.select(initcap($"name").as("name")).show(3, false)
initcap($"name") — first letter of each word upper, rest lower
name
Aisha
Rohan
Mia
baked output — Spark-only type, not runnable in the browser engine
length counts characters; for the number of bytes (matters with multi-byte UTF-8) use octet_length. To measure after trimming whitespace, combine them: length(trim($"col")).
3.3

trim, ltrim, rtrim & lpad, rpad

trim removes leading and trailing characters (whitespace by default, or a set you choose); ltrim/rtrim do just one side. lpad/rpad pad a string to a fixed width — the go-to for zero-padded IDs and fixed-width exports.

Signature
def trim(e: Column): Column           def trim(e: Column, trimStr: String): Column
def ltrim(e: Column): Column          def rtrim(e: Column): Column
def lpad(e: Column, len: Int, pad: String): Column
def rpad(e: Column, len: Int, pad: String): Column

trim with an explicit character set strips those characters from both ends — here removing stray # markers:

Scala
// trim whitespace, or a chosen character set
Seq("  hello  ", "##SKU##").toDF("raw").select(
  trim($"raw").as("trimmed"),
  trim($"raw", "#").as("no_hash")
).show(false)
Run the equivalent Spark SQL — edit it and press Run
SQL
⌘/Ctrl + Enter

lpad left-pads to a width — turn an id into a fixed 5-digit code:

Scala
orders.select(lpad($"id".cast("string"), 5, "0").as("order_code")).show(3, false)
lpad(id, 5, "0") — zero-padded to width 5
order_code
00001
00002
00003
baked output — Spark-only type, not runnable in the browser engine
If the string is already longer than the target width, lpad/rpad truncate it to that width rather than error — so pick a width that fits your longest value.
3.5

split — string → array

split breaks a string into an array<string> on a regex delimiter. It's the front door to array-land: pair it with explode (Arrays module) to turn a delimited field into rows, or index into it with getItem/element_at.

Signature
def split(str: Column, pattern: String): Column
def split(str: Column, pattern: String, limit: Int): Column   // cap the pieces

1. Split on a delimiter

The pattern is a regex, so escape regex metacharacters — a literal dot is \\.:

Scala
Seq("a,b,c").toDF("csv").select(
  split($"csv", ",").as("parts")
).show(false)
split("a,b,c", ",") → array<string> of 3 elements
parts
[a, b, c]
baked output — Spark-only type, not runnable in the browser engine

2. Grab one piece with getItem

Arrays are 0-indexed with getItem — pull the domain out of an email:

Scala
Seq("mia@shop.com").toDF("email").select(
  split($"email", "@").getItem(1).as("domain")
).show(false)
split(email, "@").getItem(1) → the part after @
domain
shop.com
baked output — Spark-only type, not runnable in the browser engine

3. Limit the pieces

A limit caps how many splits happen — the last element keeps the remainder. Useful to split only on the first delimiter:

Scala
Seq("2025-06-20").toDF("d").select(
  split($"d", "-", 2).as("parts")   // ["2025", "06-20"]
).show(false)
split(..., "-", 2) — at most 2 pieces; the rest stays intact
parts
[2025, 06-20]
baked output — Spark-only type, not runnable in the browser engine
The delimiter is a regular expression, which trips people up: split($"ip", ".") splits on every character because . means "any char". Use split($"ip", "\\.") for a literal dot.
3.6

regexp_replace & regexp_extract

The two regex workhorses. regexp_replace substitutes every match of a pattern; regexp_extract pulls out a single capture group. Both take Java regex syntax. These are baked below (the in-browser engine has no regex), but the Scala is exactly what you'd run on Spark.

Signature
def regexp_replace(str: Column, pattern: String, replacement: String): Column
def regexp_extract(str: Column, pattern: String, groupIdx: Int): Column

1. regexp_replace — scrub

Strip everything that isn't a digit to normalise a phone number:

Scala
Seq("(555) 010-1234").toDF("phone").select(
  regexp_replace($"phone", "[^0-9]", "").as("digits")
).show(false)
regexp_replace(phone, "[^0-9]", "") — keep only digits
digits
5550101234
baked output — Spark-only type, not runnable in the browser engine

2. regexp_extract — capture a group

Group 0 is the whole match; group 1 is the first parenthesised group. Extract the numeric part of a SKU:

Scala
Seq("SKU-00423-X").toDF("sku").select(
  regexp_extract($"sku", "SKU-([0-9]+)", 1).as("num")
).show(false)
regexp_extract(sku, "SKU-([0-9]+)", 1) — capture group 1
num
00423
baked output — Spark-only type, not runnable in the browser engine
regexp_extract returns an empty string "" (not null) when there's no match — guard with a when or nullif if you need null. And remember backslashes are doubled in Scala string literals: a digit class is "\\d".
3.7

replace, reverse, translate, format_string & repeat

A grab-bag of the remaining everyday helpers. replace swaps a literal substring (no regex); reverse flips a string; translate maps characters one-to-one; format_string is printf for columns; repeat repeats a string N times.

Signature
def regexp_replace / replace(str, search, replace): Column   // replace = literal
def reverse(e: Column): Column        def repeat(e: Column, n: Int): Column
def translate(src: Column, from: String, to: String): Column
def format_string(format: String, args: Column*): Column      // printf-style
Scala
orders.select(
  $"status",
  replace($"status", "cancelled", "void").as("status2"),
  reverse($"status").as("reversed")
).show(3, false)
Run the equivalent Spark SQL — edit it and press Run
SQL
⌘/Ctrl + Enter

format_string builds a formatted string from a printf template — pad an id and stitch in the product:

Scala
orders.select(
  format_string("#%03d: %s", $"id", $"product").as("line")
).show(3, false)
Run the equivalent Spark SQL — edit it and press Run
SQL
⌘/Ctrl + Enter
translate($"x", "abc", "xyz") replaces a→x, b→y, c→z character-by-character (great for simple cipher-style swaps), whereas replace swaps a whole literal substring. The encode/hash family — ascii, base64/unbase64, encode/decode, soundex, levenshtein — are specialised; reach for them only when you specifically need byte/phonetic/edit-distance work.
P
PriyaFounder · Kettle & Co.now
Making a clean export for the catalog 🧾 — I want one column per order that reads like Wireless Mouse - Electronics: the product, a space-dash-space, then the category. Call the column label.
🎯 Your mission From orders, return a single label column that joins product and category with ' - ' between them (e.g. Wireless Mouse - Electronics).
SQL
⌘/Ctrl + Enter

Ready for the full data stack?

These Spark functions are one piece. When you want pipelines, Kafka, lakehouse table formats, and a full end-to-end project, the Data Engineering course is free too.

Explore Data Engineering →

Comments

0

Join the conversation. Sign in to leave a comment — questions and feedback welcome.