Apache Polaris is an open-source REST catalog for Apache Iceberg, originally built and open-sourced by Snowflake in 2024 and subsequently donated to the Apache Software Foundation. It implements the Iceberg REST Catalog specification and is rapidly becoming the standard answer to “what catalog do I run for Iceberg?” alongside AWS Glue and Nessie.
Polaris exposes the Iceberg REST API over HTTP, backed by a relational metastore (Postgres / managed Snowflake) for catalog state. Engines authenticate, list namespaces, and request table metadata pointers; Polaris then vends temporary credentials scoped to the requested table’s storage location. The actual data reads/writes happen directly between the engine and object storage.
An Iceberg table is a set of data files plus a tree of metadata files on object storage. What the files alone cannot provide is a single, atomically updated answer to "which metadata file is the current version of this table?" That is the catalog's job. Polaris implements the Iceberg REST Catalog specification, so any engine with a REST catalog client can list namespaces, load tables, and commit changes through it. Because every engine goes through the same catalog, a table written by Spark is immediately visible to Trino or Snowflake with consistent snapshots and a single place to enforce permissions.
When an engine loads a table, Polaris returns the location of the current metadata file and, if credential vending is configured, temporary storage credentials scoped to that table's location. The engine reads or writes data and manifest files directly on object storage; Polaris is not in the data path. To commit, the engine sends the new metadata along with requirements such as "the current snapshot is still X". Polaris applies the change only if the requirements hold, giving optimistic concurrency: one of two conflicting writers succeeds and the other retries against the new state.
Polaris separates principals, principal roles, and catalog roles. Privileges such as TABLE_READ_DATA or NAMESPACE_CREATE are granted to catalog roles on a catalog, namespace, or table; catalog roles are granted to principal roles; principal roles are assigned to principals, which are the service identities that engines authenticate as. This indirection lets one engine identity have different rights in different catalogs. Combined with credential vending, engines never need broad standing access to the storage bucket.
from pyspark.sql import SparkSession
spark = (SparkSession.builder
.config("spark.sql.extensions",
"org.apache.iceberg.spark.extensions.IcebergSparkSessionExtensions")
.config("spark.sql.catalog.polaris", "org.apache.iceberg.spark.SparkCatalog")
.config("spark.sql.catalog.polaris.type", "rest")
.config("spark.sql.catalog.polaris.uri", "https://polaris.example.com/api/catalog")
.config("spark.sql.catalog.polaris.warehouse", "analytics") # catalog name
.config("spark.sql.catalog.polaris.credential", "CLIENT_ID:CLIENT_SECRET")
.config("spark.sql.catalog.polaris.scope", "PRINCIPAL_ROLE:ALL")
.config("spark.sql.catalog.polaris.header.X-Iceberg-Access-Delegation",
"vended-credentials")
.getOrCreate())
spark.sql("CREATE NAMESPACE IF NOT EXISTS polaris.sales")
spark.sql("""CREATE TABLE IF NOT EXISTS polaris.sales.orders
(id BIGINT, amount DECIMAL(12,2), ts TIMESTAMP)
USING iceberg PARTITIONED BY (days(ts))""")
spark.sql("SELECT count(*) FROM polaris.sales.orders").show()
The Iceberg Spark runtime jar matching your Spark version must be on the classpath. Keep the client secret in a secret store rather than in code.
| Catalog | Interface | Notes |
|---|---|---|
| Apache Polaris | Iceberg REST | Vendor-neutral, RBAC, credential vending |
| Unity Catalog | Own API plus Iceberg REST | Delta-first, Databricks ecosystem |
| AWS Glue Data Catalog | Glue API, Iceberg REST endpoint | AWS-native, IAM permissions |
| Hive Metastore | Thrift | Legacy, no fine-grained Iceberg features |
| Nessie | Own API plus Iceberg REST | Git-like branches across tables |