Spark Schema Definition

A schema describes the shape of a DataFrame. It lists every column name, its data type, and whether it accepts null values. A clear schema protects your pipeline from wrong types, speeds up reading, and documents the data for the next person who opens your code.

The Layman View

A schema works like the header of a form. The form says the first box holds a name, the second holds an age, and the third holds a date. Anyone who reads the form knows what to expect. Spark reads data the same way once you hand it the form.

Inspecting a Schema

df = spark.read.csv("people.csv", header=True, inferSchema=True)
df.printSchema()

root
 |-- name: string (nullable = true)
 |-- age: integer (nullable = true)
 |-- joined: date (nullable = true)

Common Data Types

TypeHoldsExample
StringTypeText"Asha"
IntegerTypeWhole numbers42
LongTypeLarge whole numbers9000000000
DoubleTypeDecimal numbers3.14
DecimalTypeExact money values199.99
BooleanTypeTrue or falseTrue
DateTypeCalendar date2026-01-15
TimestampTypeDate and time2026-01-15 10:30:00
ArrayType, MapType, StructTypeNested values["a", "b"]

Why Inference Has a Cost

The inferSchema option makes Spark read the file once to guess the types and a second time to load the data. A large file pays for two passes. Guessing also goes wrong. A zip code such as 00501 becomes the number 501 and loses its leading zeros.

With inferSchema:    read file --> guess types --> read file again --> DataFrame
With explicit schema: read file with known types --> DataFrame

Defining a Schema with StructType

from pyspark.sql.types import (StructType, StructField, StringType,
                               IntegerType, DateType, DecimalType)

schema = StructType([
    StructField("name",   StringType(),      nullable=False),
    StructField("age",    IntegerType(),     nullable=True),
    StructField("joined", DateType(),        nullable=True),
    StructField("salary", DecimalType(10,2), nullable=True),
])

df = spark.read.csv("people.csv", header=True, schema=schema)

Defining a Schema with a DDL String

A DDL string gives a shorter way to write the same schema.

ddl = "name STRING NOT NULL, age INT, joined DATE, salary DECIMAL(10,2)"
df = spark.read.csv("people.csv", header=True, schema=ddl)

Handling Bad Records

Real files contain broken rows. The mode option tells Spark how to react when a row does not match the schema.

Mode           Behaviour
-------------  ----------------------------------------------
PERMISSIVE     Keep the row and set bad fields to null (default)
DROPMALFORMED  Drop the broken row
FAILFAST       Stop the job at the first broken row
df = (spark.read
        .schema(schema)
        .option("mode", "PERMISSIVE")
        .option("columnNameOfCorruptRecord", "_bad_row")
        .csv("people.csv", header=True))

Add a string column named _bad_row to the schema when you use that option. Spark stores the raw text of each broken line there for later review.

Changing Types After Loading

from pyspark.sql.functions import col

df2 = df.withColumn("age", col("age").cast("long"))
df2 = df.withColumn("joined", col("joined").cast("string"))

Good Habits

  • Define schemas by hand for production pipelines.
  • Choose DecimalType for money. Floating point types round values.
  • Store the schema in one shared module so every job reads files the same way.
  • Parquet files carry their own schema, so reads need no inference.

Nested Schemas

A schema can hold structs and arrays. Declare the inner shape the same way you declare the outer shape.

from pyspark.sql.types import ArrayType

nested = StructType([
    StructField("id", IntegerType()),
    StructField("address", StructType([
        StructField("city", StringType()),
        StructField("zip",  StringType())
    ])),
    StructField("skills", ArrayType(StringType()))
])

Nested schemas keep related fields together and make JSON and Parquet files easy to read.

Comparing Two Schemas

Pipelines break when a source file adds or renames a column. Compare the expected schema with the incoming one before the job starts.

if df.schema != schema:
    raise ValueError("Incoming data does not match the expected schema")