Iceberg REST Catalog Specification and Apache Polaris: Multi-Engine Catalog Guide
Updated October 2026. Checked against the REST catalog OpenAPI spec at theApache Iceberg 1.12.0 tag(released September 29, 2026) and the1.11.0 and 1.12.0 release notes. See what changed in Iceberg 1.11 and 1.12.
The Short Answer
The Iceberg REST Catalog specification is an OpenAPI-defined HTTP protocol, maintained in the Apache Iceberg repository, for everything an engine asks a catalog: configuration, namespaces, tables, views, commits, credentials, and (since recent releases) server-side scan planning and SQL functions. Any engine that implements the REST client once can talk to any catalog server that implements the spec, which is why Apache Polaris, Project Nessie, AWS Glue, and other catalogs expose it. Every resource path sits under /v1/{prefix}/, where the optional prefix comes from the server'sGET /v1/config response.
Need a one-page cheat sheet of request and response shapes? See theIceberg REST Catalog API endpoint reference in the knowledge base.
Decoupling Compute, Storage, and the Metadata Catalog
The core value proposition of the modern data lakehouse is the complete separation of compute and storage. In traditional data warehousing systems, data storage and query execution are tightly coupled within a proprietary database engine. To access your data, you are forced to run queries through that database’s specific compute infrastructure, paying execution costs determined by the database vendor. The lakehouse architecture breaks this monopoly by storing data in open, vendor-neutral formats such as Apache Parquet, Apache ORC, or Apache Avro within public cloud object stores like Amazon S3, Google Cloud Storage, or Azure Data Lake Storage.
However, raw files sitting in an object store do not behave like a database table. A collection of Parquet files does not support ACID transactions, schema evolution, or historical time travel queries. In early lakehouse implementations built on Apache Hive, tables were defined as directories of files. When query engines wanted to read a table, they had to list all files in the directory. This directory listing approach suffered from major scalability bottlenecks because listing files in object storage is an expensive, slow operation that grows linearly with the number of files. Furthermore, object storage systems do not provide directory-level atomicity, meaning concurrent readers would often see partial writes, leading to incorrect query results.
To provide database-grade reliability at scale, table formats like Apache Iceberg introduce a rich metadata abstraction layer. Apache Iceberg tracks the exact state of a table using a tree structure of metadata files: the table metadata file points to manifest lists, which in turn point to manifest files, which finally reference the physical data and delete files. By using explicit file paths in metadata instead of directory listings, Iceberg eliminates the need for directory listing operations, reducing planning latency and avoiding cloud provider rate limits.
While this metadata tree resides in your object storage, compute engines still require a central database registry to answer a fundamental question: What is the current version of the table? When multiple compute engines write to the same table concurrently, they generate competing metadata versions. Without a single, authoritative coordinator to validate commits and update the table state, the table would enter a split-brain state where different engines see different snapshots, leading to silent data loss or corruption. The coordinator that tracks the current version of the table is known as the catalog.
Historically, catalogs were engine-specific and vendor-locked. The Apache Hive Metastore served as the default catalog for the Hadoop ecosystem, but its Thrift-based protocol proved slow, hard to scale, and difficult to integrate with non-JVM query engines. Later, cloud providers and data platform vendors built proprietary catalogs, forcing users to route all metadata traffic through their platforms. If your ingestion engine wrote to one catalog and your business intelligence tool queried another, the two tools would see different versions of the table. To maintain the open promise of the lakehouse, the catalog itself had to be decoupled from the compute engine. The industry needed an open, engine-neutral catalog protocol.
The Iceberg REST Catalog Specification
To solve the problem of catalog fragmentation, the Apache Iceberg community created the REST Catalog specification. Instead of defining a specific database schema or serialization protocol, the REST Catalog spec defines an open HTTP API standard. Any catalog server that implements this standard can serve table metadata to any query engine that speaks the REST protocol.
This API-first approach transforms the lakehouse topology. Previously, adding a new catalog implementation required writing custom catalog connector classes for every query engine (Spark, Flink, Trino, Dremio, Presto, and PyIceberg). Under the REST specification, engines implement the REST client interface once. As long as a new catalog server implements the REST HTTP endpoints, every query engine can immediately connect to it.
API Endpoint Architecture
The Iceberg REST Catalog specification organizes catalog operations into standard RESTful resources. The endpoints utilize JSON payloads and handle authentication, configuration, namespace organization, table management, and view routing.
1. Initialization and Configuration (GET /v1/config)
When a client engine initializes its catalog connection, it sends a request to the configuration endpoint. The catalog server responds with the default configurations, warehouse paths, and credential requirements needed by the client to proceed with subsequent API requests. This dynamic initialization is highly beneficial for multi-region cloud configurations. For instance, the catalog server can inspect the client's network location and return a regionalized storage endpoint override, directing the client to route its S3 traffic through local regional VPC endpoints to minimize cross-region data transfer fees.
2. Authentication (and the deprecated POST /v1/oauth/tokens)
Note: the spec marks the built-in/v1/oauth/tokens endpoint as deprecated for removal. It has been deprecated since Iceberg Java 1.6.0, will be removed from the spec in Iceberg Java 2.0, and new catalog servers are advised not to implement it. Clients should set oauth2-server-uri to point at a real OAuth2 identity provider instead. Some catalogs, including Polaris, still serve the endpoint for compatibility, so the flow below remains common in practice.
The client exchanges its credentials (such as client ID and client secret) for a short-lived OAuth2 bearer token. The catalog server validates these credentials and returns a token that the client must include in the Authorization header of all future HTTP calls. Relying on short-lived OAuth2 tokens instead of sending basic credentials with every API request significantly reduces the risk of credential exposure. It also allows administrators to enforce fine-grained access control policies by mapping specific catalog role permissions directly to individual OAuth2 sessions.
3. Namespace Operations (GET /v1/{prefix}/namespaces, POST /v1/{prefix}/namespaces)
Namespaces represent logical databases or schemas inside the catalog. The REST spec supports hierarchical namespaces (e.g., production.analytics.finance), allowing teams to organize tables across multiple levels of folder structures. These endpoints allow query engines to dynamically list schemas, create new databases, and set database-level properties. In Polaris, namespaces can be assigned specific catalog roles, permitting security administrators to grant access control policies that automatically inherit down to all tables created within that namespace.
4. Table Resolution and Loading (GET /v1/{prefix}/namespaces/{ns}/tables/{table})
This endpoint is called when a query engine needs to read or write to a table. The catalog server returns the complete table metadata state, including the exact location of the current metadata.json file in object storage, table schema, partition specs, snapshot history, and storage access tokens. By receiving the exact file location of the current table metadata directly from the catalog, the query engine can parse the JSON tree structure and plan its scan operations instantly. This eliminates the need to query the cloud storage directory listing API, resulting in highly efficient query planning times.
5. Atomic Table Commits (POST /v1/{prefix}/namespaces/{ns}/tables/{table})
To write data to a table, the client engine prepares a new metadata version file. It then sends a commit request to the catalog server. The payload contains the updates the engine wants to apply, alongside requirements that must be met (such as asserting that the table’s UUID or snapshot ID has not changed since the client read it). The catalog executes an atomic compare-and-swap operation. If another engine committed a write first, the requirements fail, and the catalog rejects the commit, forcing the client to retry. This strict assertion validation prevents concurrent engines from overwriting each other's changes, protecting table integrity.
What Changed in the REST Spec in Iceberg 1.11 and 1.12
The REST catalog has grown from a metadata lookup service into a catalog protocol that can plan scans, sign storage requests, and store functions. These are the changes worth knowing, taken from theofficial release notesand the OpenAPI file at the 1.12.0 tag.
Iceberg 1.11.0 (May 19, 2026)
- Remote scan planning enabled. The Java client can hand planning to the server through
POST /v1/{prefix}/namespaces/{ns}/tables/{table}/plan, then pollGET .../plan/{plan-id}and fetch tasks withPOST .../tasks. The server can return a completed plan immediately or a plan ID to poll. - SQL UDF specification. A new spec defines how user-defined functions are stored in Iceberg catalogs.
- Register view for the REST catalog (
POST .../register-view), and freshness-aware table loading. In the spec, a client can sendIf-None-Matchwith the ETag it already holds, and the server can answer 304 Not Modified instead of resending the metadata.
Iceberg 1.12.0 (September 29, 2026)
- Functions endpoints:
GET /v1/{prefix}/namespaces/{ns}/functionsandGET .../functions/{function}list and load functions, with all overloads of a function returned in one response. - Unregister table:
POST .../tables/{table}/unregisterremoves a table from the catalog while leaving data and metadata files in place, so it can be registered in another catalog. This is the clean counterpart toregisterfor catalog migrations. - Idempotent retries: the REST client now retries POST requests that carry an
Idempotency-Key(a UUIDv7) on retriable errors. A server that supports this advertisesidempotency-key-lifetimein its config response. - Remote signing formalized: a remote signing configuration is now part of the spec, alongside the
POST .../tables/{table}/signendpoint. - Variant type added to the REST spec, REST expressions aligned with the new expressions spec, RFC 3986 percent-encoding for path segments, and a
rest-catalog-purgeproperty that delegatesDROP TABLE ... PURGEto the catalog.
Capability Discovery
Not every server implements every endpoint. The config response can include an endpoints list in the form"GET /v1/{prefix}/namespaces"; clients use it to decide whether to try scan planning, views, or functions. If a server omits the list, clients assume only the core namespace, table, rename, metrics, and multi-table transaction endpoints. Check this list before assuming a catalog supports a feature described here.
Full Endpoint Map (Spec at 1.12.0)
- Config and auth:
GET /v1/config;POST /v1/oauth/tokens(deprecated) - Namespaces: list, create, load, exists (HEAD), drop, update properties
- Tables: list, create, load, commit, drop, exists (HEAD), register, unregister, rename, report metrics, load credentials, sign request
- Scan planning: plan, fetch planning result, cancel planning, fetch scan tasks
- Transactions:
POST /v1/{prefix}/transactions/commitfor atomic multi-table commits - Views: list, create, load, replace, drop, exists (HEAD), rename, register
- Functions: list, load
Under the Hood: JSON Payload Specifications
To understand the actual mechanics of the communication protocol between a REST catalog client engine and a catalog server, we can examine the structural design of the JSON payloads exchanged during different phases of the session. The payload designs are dictated by the OpenAPI schema defined in the Apache Iceberg repository. These payloads are designed to be compact yet highly descriptive, ensuring that query engines receive all necessary metadata needed for query planning with minimal serialization overhead.
1. Get Configuration Response Payload
Upon startup, the client queries the server configurations to establish default connection behaviors, namespaces, and storage properties.
{
"defaults": {
"clients.factory": "org.apache.iceberg.aws.s3.S3FileIO",
"warehouse": "s3://my-warehouse-bucket/sales"
},
"overrides": {
"s3.endpoint": "https://s3.amazonaws.com"
}
}2. OAuth2 Token Response Payload
Exchanging the client credentials yields a bearer token with strict expiry, ensuring access control is rotated regularly.
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.ey...",
"token_type": "bearer",
"expires_in": 3600
}3. Load Table Response Payload
When an engine calls GET /v1/namespaces/analytics/tables/orders, the server returns a detailed payload outlining the table’s current state. This payload instructs the engine on how to read the metadata files.
{
"metadata-location": "s3://my-warehouse/analytics/orders/metadata/v14.metadata.json",
"metadata": {
"format-version": 2,
"table-uuid": "9c12b7a4-d1f8-4e1a-8c53-bdb4bc2a6440",
"location": "s3://my-warehouse/analytics/orders",
"last-sequence-number": 42,
"last-updated-ms": 1716382800000,
"last-column-id": 4,
"current-schema-id": 0,
"schemas": [
{
"type": "struct",
"schema-id": 0,
"fields": [
{ "id": 1, "name": "order_id", "required": true, "type": "long" },
{ "id": 2, "name": "customer_id", "required": true, "type": "long" },
{ "id": 3, "name": "order_amount", "required": false, "type": "double" },
{ "id": 4, "name": "order_date", "required": true, "type": "date" }
]
}
],
"default-spec-id": 0,
"partition-specs": [
{
"spec-id": 0,
"fields": [
{ "name": "order_date_month", "transform": "month", "source-id": 4, "field-id": 1000 }
]
}
],
"last-partition-id": 1000,
"default-sort-order-id": 0,
"sort-orders": [
{
"order-id": 0,
"fields": []
}
],
"properties": {
"write.format.default": "parquet",
"write.metadata.compression-codec": "gzip"
},
"current-snapshot-id": 8923487239487239482,
"snapshots": [
{
"snapshot-id": 8923487239487239482,
"timestamp-ms": 1716382800000,
"summary": {
"operation": "append",
"added-data-files": "3",
"added-records": "15000"
},
"manifest-list": "s3://my-warehouse/analytics/orders/metadata/snap-8923487239487239482.avro"
}
]
},
"config": {
"client.factory": "org.apache.iceberg.aws.s3.S3FileIO"
}
}4. Commit Table Request Payload
When an engine completes a write operation, it requests a commit by sending a list of requirements and updates to the server. The server verifies the requirements before applying the updates.
{
"requirements": [
{
"type": "assert-table-uuid",
"uuid": "9c12b7a4-d1f8-4e1a-8c53-bdb4bc2a6440"
},
{
"type": "assert-last-assigned-snapshot-id",
"snapshot-id": 8923487239487239482
}
],
"updates": [
{
"action": "add-snapshot",
"snapshot": {
"snapshot-id": 3487239487239487234,
"timestamp-ms": 1716386400000,
"summary": {
"operation": "overwrite",
"added-data-files": "1",
"deleted-data-files": "1"
},
"manifest-list": "s3://my-warehouse/analytics/orders/metadata/snap-3487239487239487234.avro"
}
},
{
"action": "set-current-snapshot",
"snapshot-id": 3487239487239487234
}
]
}Apache Polaris Architecture and Internals
Apache Polaris is a fully featured, open-source implementation of the Iceberg REST Catalog specification. Created by Dremio and Snowflake and donated to the Apache Software Foundation, Polaris provides a vendor-neutral, enterprise-grade catalog management server.
The internal architecture of Polaris is designed for stateless scalability. The Polaris server handles API routing, authentication, role-based access control, and query translation. It persists its transactional metadata (such as principal roles, namespaces, and table names) to a backing relational database or transactional key-value store, while the physical Iceberg tables and files remain in your object storage.
By separating catalog operational metadata storage from file storage, Polaris can scale dynamically. You can deploy multiple stateless Polaris containers behind a load balancer, each handling incoming REST requests and querying a shared relational metadata repository (such as PostgreSQL, MySQL, or an embedded store). The transactional pointer changes are managed cleanly without locking the query engines.
Hierarchical Access Control
Polaris secures catalog resources using a granular, hierarchical Role-Based Access Control (RBAC) model. Unlike traditional object storage access controls, which are based on storage paths, Polaris defines access privileges at the logical metadata level.
- Principals: An identity that requests access. This can represent an application, a query engine connection, an ETL tool, or a user.
- Principal Roles: Logical groupings of principals. For example, a principal role named
etl_writercan group ingestion tools. - Catalog Roles: Scoped roles inside a specific catalog instance that define metadata actions. Examples include
finance_readerorsales_admin. - Privileges: Specific access rights assigned to catalog roles, such as
TABLE_READ,TABLE_WRITE,NAMESPACE_CREATE, orVIEW_DROP.
By mapping Principal Roles to Catalog Roles, administrators decouple engine identities from table access rights. You can grant access to a specific table for a role without managing API keys or IAM roles for each compute engine.
Polaris Credential Vending and Security Mathematics
One of the most important security capabilities of Apache Polaris is credential vending. In a traditional lakehouse deployment, every query engine requires direct, long-lived access keys to your cloud object storage. If a user runs a query in Spark, the Spark session must be configured with AWS access keys or an IAM instance profile that has write permissions to the entire storage bucket. This creates a severe security risk: a user with access to Spark could bypass the table layout entirely and delete raw files directly from the bucket.
Credential vending eliminates this vulnerability by routing storage access authorization through the catalog. The query engine does not hold long-lived storage credentials. Instead, when it resolves a table metadata path, Polaris returns a short-lived, highly scoped token generated specifically for that table.
When the query engine calls the table load endpoint, Polaris contacts the cloud provider's token service (such as AWS Security Token Service, Azure Active Directory, or Google Cloud IAM) using its own highly authorized identity. It requests a set of temporary security credentials, specifying a strict resource policy that restricts access to the exact storage path where the table's files reside. Polaris passes these short-lived credentials back to the engine client inside the JSON response. The client uses these credentials to access the files directly and discards them when the query session expires.
Mathematical Modeling of Token Lifetimes
Implementing polaris credential vending requires a careful optimization of the session token time-to-live parameter. The security window of exposure is directly proportional to the lifespan of the vended token, while the API request overhead on the catalog server is inversely proportional to that lifespan. We can model this trade-off mathematically.
Let T_TTL represent the time-to-live of a vended storage token in seconds. Let S represent the total duration of the analytics pipeline run. If a credential leak occurs, the expected duration of security exposure E[T_exposure] for the leaked storage credentials during a token lifecycle is bounded by the token's remaining lifespan. Assuming a uniform probability distribution of leaks over time, we define the expected exposure time as:
E[T_exposure] = T_TTL / 2This equation shows that decreasing the token time-to-live reduces the security risk linearly. However, the query engine must refresh these credentials before they expire. For a session of duration Squerying a set of K independent tables, the total number of catalog REST API requests N_requests required to maintain storage access is defined by:
N_requests(S) = K * ceil(S / T_TTL)As the time-to-live approaches zero, the request load on the catalog server increases toward infinity:
lim (T_TTL → 0) N_requests = ∞To find the optimal balance between security exposure and catalog server request load, we can construct a total cost function C(T_TTL):
C(T_TTL) = W_s * E[T_exposure] + W_o * ((K * S) / T_TTL)Here, W_s represents the weighted cost of security risk per second of exposure, and W_o represents the operational cost per catalog API request. To minimize the total cost, we take the derivative of the cost function with respect to the time-to-live and set it to zero:
dC / dT_TTL = W_s / 2 - (W_o * K * S) / (T_TTL^2) = 0Solving this equation for the optimal token time-to-live T_TTL_optimalyields the square root relation:
T_TTL_optimal = sqrt( (2 * W_o * K * S) / W_s )This optimal value demonstrates how platform engineers can tune token lifespans based on security sensitivity and catalog server capacity, finding the balance point where the risk of credential exposure is minimized without overloading the REST endpoints.
Multi-Engine Connection Configurations
To configure compute engines to connect to an Apache Polaris catalog, you must define the catalog type as REST, set the API URI, and supply OAuth2 client credentials.
1. Connecting Spark to Polaris (Configuration Guide)
To establish a reliable connection between Spark and Polaris, developers must configure the Iceberg SparkCatalog classes to communicate over the REST protocol. When connecting spark to polaris, the Spark Session must pass OAuth2 client credentials to authenticate queries.
The catalog connection can be established using standard Java configuration properties. Here is the primary configuration set required for Spark:
spark.sql.catalog.polaris = org.apache.iceberg.spark.SparkCatalog
spark.sql.catalog.polaris.type = rest
spark.sql.catalog.polaris.uri = https://polaris.example.com/api/catalog
spark.sql.catalog.polaris.credential = client_id_xyz:client_secret_abc
spark.sql.catalog.polaris.scope = PRINCIPAL_ROLE:data_analyst
spark.sql.catalog.polaris.warehouse = sales_warehouseUnderstanding the scope mapping and credential format is critical. The credential property accepts a colon-separated string combining the client ID and the client secret. The scope property specifies the principal role that the connection session should assume. If no scope is provided, Polaris assigns the principal's default catalog roles, which might limit the permissions of the compute engine.
Additionally, when credential vending is enabled in Polaris, the client engine must configure its FileIO implementation to accept temporary credentials. For S3 storage, this requires using org.apache.iceberg.aws.s3.S3FileIO rather than the legacy Hadoop file system wrapper. By using the native S3FileIO, Spark reads storage access configuration values returned dynamically by the REST server, ensuring that the temporary session tokens are applied directly to bucket read and write operations.
2. Trino Configuration
Create a properties file inside your Trino catalog directory. This instructs Trino to route queries through the REST API using OAuth2 authentication.
/* /etc/trino/catalog/polaris.properties */
connector.name=iceberg
iceberg.catalog.type=rest
iceberg.rest-catalog.uri=https://polaris.example.com/api/catalog
iceberg.rest-catalog.security=OAUTH2
iceberg.rest-catalog.oauth2.credential=client_id_xyz:client_secret_abc
iceberg.rest-catalog.warehouse=sales_warehouse
iceberg.unique-table-location=true3. Dremio Integration and Query Acceleration
Dremio acts as a high-performance distributed query engine that queries the lakehouse directly. To unlock maximum query speeds, Dremio integrates natively with Apache Polaris as an Iceberg REST Catalog source, combining Polaris neutral metadata governance with Dremio query acceleration technologies.
When Dremio queries an Iceberg table managed by Polaris, the query optimizer first contacts the Polaris REST endpoint to resolve the table schema, partition definition, and file layout. Once the layout is resolved, Dremio executes queries across its distributed executor nodes using two primary acceleration mechanisms: Columnar Cloud Cache (C3) and Data Reflections.
Columnar Cloud Cache (C3) and Polaris Storage Access
Querying data files directly from cloud storage buckets over HTTP can introduce significant network latency. Dremio resolves this bottleneck using Columnar Cloud Cache. C3 is an autonomous, NVMe-backed caching layer built directly into Dremio executor nodes.
When Dremio executor nodes read Parquet files, they divide the files into columnar data blocks. As these blocks are fetched from the cloud bucket (using the scoped storage credentials vended by the Polaris REST catalog), the executor nodes write copies of the blocks onto their local high-speed NVMe drives. Subsequent queries targeting the same table columns read directly from local NVMe cache, bypassing the cloud storage network API. This reduces block fetch latency from hundreds of milliseconds to microseconds.
Because Polaris regularly rotates storage credentials via credential vending, Dremio handles token refreshing automatically. Dremio's internal file system layer caches the REST vended token for its lifetime and requests a new token from Polaris when the existing one is near expiration, ensuring uninterrupted C3 operations.
Dremio Data Reflections on Iceberg
Data Reflections are pre-computed optimization structures that Dremio maintains to accelerate queries without requiring manual table modifications. Reflections function similarly to materialized views but are managed entirely by Dremio's cost-based query optimizer.
When an administrator configures a Raw Reflection (which pre-sorts, partitions, or filters a dataset) or an Aggregation Reflection (which pre-calculates roll-ups), Dremio writes the pre-computed results as an Apache Iceberg table back to your object storage. Dremio registers these reflection tables within the Polaris catalog.
When a user runs a query against the base table, the Dremio query compiler matches the logical tree of the user query with the available reflections. If a matching reflection is found, the optimizer automatically rewrites the query to read from the pre-computed reflection table registered in Polaris instead of scanning the raw base table. This optimization provides sub-second query execution times on datasets containing billions of records.
Dremio Connection Configuration Guide
Follow these steps to connect Dremio to your Polaris instance:
- Log in to your Dremio administration console and select the Add Source button in the bottom-left corner.
- From the list of available source connectors, select Iceberg REST Catalog.
- In the source configuration window, enter a descriptive source name (e.g.,
polaris_prod). - Set the Connection URI to point to your Polaris REST API service endpoint (e.g.,
https://polaris.example.com/api/catalog). - Under the Authentication section, select OAuth2 Client Credentials. Enter your Polaris Client ID and Client Secret in the designated fields.
- Set the Warehouse property to match the exact name of the catalog instance configured inside Polaris (e.g.,
sales_warehouse). - If you are using self-signed SSL certificates for your Polaris deployment, navigate to the Advanced Options tab and enable the trustStore configuration, supplying the path to your certificate authority bundle.
- Click Save. Dremio will initiate the REST handshake, validate the OAuth2 credentials, and discover the namespaces and tables registered under the Polaris catalog.
4. PyIceberg Configuration
PyIceberg is the official Python implementation for reading and writing Apache Iceberg tables. It is designed for lightweight data manipulation, allowing developers to query datasets without running a heavy JVM engine like Spark or Trino. PyIceberg connects natively to REST catalog services, making it a perfect partner for Apache Polaris.
You can configure the Python client by creating a configuration file or passing configurations directly to the catalog load function.
# ~/.pyiceberg.yaml
catalogs:
polaris:
type: rest
uri: https://polaris.example.com/api/catalog
credential: client_id_xyz:client_secret_abc
scope: PRINCIPAL_ROLE:data_analyst
warehouse: sales_warehouseLoad the catalog in your Python code:
from pyiceberg.catalog import load_catalog
catalog = load_catalog("polaris")
table = catalog.load_table("sales.invoices")
df = table.scan().to_arrow()When PyIceberg requests table details, Polaris authorizes the session and vends temporary credentials scoped to the table location. PyIceberg's internal PyArrow-based FileIO implementation parses these credentials and reads the Parquet columns directly, returning a high-performance Apache Arrow table.
5. DuckDB Configuration
DuckDB is an embedded analytical database that excels at executing fast SQL queries on local files and object storage. DuckDB can query Iceberg tables via REST catalogs using the Iceberg extension.
INSTALL iceberg;
LOAD iceberg;
CREATE SECRET polaris_secret (
TYPE ICEBERG,
PROVIDER REST,
URI 'https://polaris.example.com/api/catalog',
CLIENT_ID 'client_id_xyz',
CLIENT_SECRET 'client_secret_abc',
WAREHOUSE 'sales_warehouse'
);
SELECT * FROM iceberg_scan('sales.invoices');By combining PyIceberg and DuckDB, developers can build a robust local querying environment. PyIceberg handles table metadata resolution and access control handshakes with Polaris, while DuckDB executes complex SQL queries directly on the resulting Arrow tables. This architecture keeps local query execution times fast while maintaining complete compliance with enterprise security rules enforced by Polaris.
6. Apache Flink SQL Client Configuration
Apache Flink is a popular distributed processing engine for stateful computations over data streams. Flink integrates seamlessly with Apache Iceberg and REST catalogs like Polaris, permitting developers to run streaming ingestion pipelines that write directly to the lakehouse.
To query or write to Iceberg tables from the Flink SQL client, you must define the catalog properties. Below is the configuration syntax used to create a REST catalog in Flink:
CREATE CATALOG polaris_catalog WITH (
'type'='iceberg',
'catalog-impl'='org.apache.iceberg.rest.RESTCatalog',
'uri'='https://polaris.example.com/api/catalog',
'credential'='client_id_xyz:client_secret_abc',
'warehouse'='sales_warehouse',
'io-impl'='org.apache.iceberg.aws.s3.S3FileIO'
);When Flink writes streaming data to an Iceberg table, it relies on its checkpointing mechanism to perform commits. During a checkpoint, Flink executors flush their in-memory data records to physical Parquet files in object storage. Once all files are written, the Flink job coordinator sends a commit request to the Polaris REST catalog server, updating the table's current snapshot pointer. If a checkpoint fails, Flink rolls back the transaction, ensuring that uncommitted data files do not pollute the table state.
Polaris vs Project Nessie: Architectural and Interoperability Trade-offs
When designing a modern open lakehouse, choosing the right metadata catalog is a critical decision. In the Apache Iceberg ecosystem, two of the most popular open-source catalog options are Apache Polaris and Project Nessie. Understanding the architectural differences between polaris vs project nessie is vital for establishing a scalable, secure data platform.
Project Nessie functions as a Git-for-data catalog. It stores table state updates inside a transaction log, database commit graph, or backing key-value store. This model enables branching, merging, tagging, and multi-table transactions. In Nessie, data engineers can create a development branch, run pipelines to insert or modify records across dozens of tables, and merge the branch back to the main production branch atomically. This catalog-level versioning is powerful for multi-table transactions. However, Nessie is not a pure REST Catalog. It relies on a custom catalog protocol and client-side libraries.
In contrast, Apache Polaris is a stateless REST Catalog service built strictly on the Iceberg REST Catalog specification. It does not track a historical commit graph or manage data branches. Instead, Polaris serves as a direct, stateless router for table pointer updates, concentrating on secure access control and multi-engine interoperability. While Nessie focuses on database versioning control, Polaris prioritizes enterprise governance, role-based access control hierarchies, and native storage token management.
For organizations requiring Git-like branching workflows for data processing, Project Nessie is a strong candidate. For enterprise platforms requiring multi-cloud interoperability, centralized access governance, and strict credential containment, Apache Polaris provides a more robust foundation. By acting as a standard REST catalog, Polaris ensures that engines query tables consistently without requiring custom, engine-specific plugins or client-side storage keys.
Catalog Ecosystem Comparison
When selecting a catalog architecture for your lakehouse, it is helpful to compare the features of different open and managed alternatives.
| Feature | Apache Polaris | Project Nessie | AWS Glue Catalog | Hive Metastore (HMS) |
|---|---|---|---|---|
| Specification | REST Catalog API | REST / Custom Spec | AWS Proprietary / REST | Thrift Protocol |
| Credential Vending | Yes (Fully Native) | No (Client Handled) | Partial (Lake Formation) | No |
| Governance (RBAC) | Native Fine-Grained | External / Basic | AWS IAM Policies | External / Storage Only |
| Branching & Git-like features | No | Yes (Native) | No | No |
| Multi-Engine Support | Universal | Universal | AWS-biased | Difficult (JVM legacy) |
| Metadata Storage | Plug-in Database | Key-Value Store | AWS Cloud Database | Relational DBMS |
Local Developer Playground: Running Polaris with Docker
To test the REST catalog configuration locally, you can run a small sandbox with Docker Compose: one Apache Polaris server, one S3-compatible object store, and Spark as the query engine. The setup below follows thePolaris quickstartand was last run end to end on October 14, 2026 with Polaris 1.8.0, Spark 4.1.3, and Iceberg 1.12.0.
For object storage it uses RustFS, the same S3-compatible store the Polaris quickstart uses. The MinIO community edition is now distributed as source code only, so guides built on the minio/minioimage are pinned to releases that no longer get updates. Polaris runs with its default in-memory metastore, which means catalogs and tables disappear when the container restarts. That is fine for a sandbox and wrong for anything else.
1. Docker Compose Configuration
Create a file named docker-compose.yml in a clean directory.
services:
rustfs:
image: rustfs/rustfs:1.0.0-alpha.81
ports:
- "9000:9000"
- "9001:9001"
environment:
RUSTFS_ACCESS_KEY: polaris_root
RUSTFS_SECRET_KEY: polaris_pass
RUSTFS_VOLUMES: /data
RUSTFS_ADDRESS: ":9000"
RUSTFS_CONSOLE_ENABLE: "true"
RUSTFS_CONSOLE_ADDRESS: ":9001"
healthcheck:
test: ["CMD-SHELL", "curl --fail http://127.0.0.1:9000/health"]
interval: 5s
retries: 20
bucket-setup:
image: amazon/aws-cli:2.36.44
depends_on:
rustfs:
condition: service_healthy
environment:
AWS_ACCESS_KEY_ID: polaris_root
AWS_SECRET_ACCESS_KEY: polaris_pass
AWS_ENDPOINT_URL: http://rustfs:9000
entrypoint: ["aws", "s3", "mb", "s3://warehouse"]
polaris:
image: apache/polaris:1.8.0
ports:
- "8181:8181"
- "8182:8182"
depends_on:
bucket-setup:
condition: service_completed_successfully
environment:
AWS_REGION: us-west-2
AWS_ACCESS_KEY_ID: polaris_root
AWS_SECRET_ACCESS_KEY: polaris_pass
POLARIS_BOOTSTRAP_CREDENTIALS: POLARIS,root,s3cr3t
polaris.realm-context.realms: POLARIS
quarkus.otel.sdk.disabled: "true"
healthcheck:
test: ["CMD", "curl", "--fail", "http://localhost:8182/q/health"]
interval: 5s
retries: 20
spark:
image: apache/spark:4.1.3
depends_on:
polaris:
condition: service_healthy
environment:
AWS_REGION: us-west-2
command: ["sleep", "infinity"]POLARIS_BOOTSTRAP_CREDENTIALS creates a root principal with client ID root and secret s3cr3t in thePOLARIS realm. The bucket-setup container creates the warehouse bucket and exits. The Spark container just stays up so you can run spark-sql inside it.
2. Creating the Catalog and a Namespace
Start the environment (Docker Compose v2):
docker compose up -dPolaris splits its API in two. Catalogs, principals, and grants live under the Polaris management API at/api/management/v1. Namespaces and tables live under the Iceberg REST API at /api/catalog/v1. Both accept the same OAuth2 token. Get one for the root principal (this usesjq):
TOKEN=$(curl -s -X POST http://localhost:8181/api/catalog/v1/oauth/tokens \
-d grant_type=client_credentials \
-d client_id=root \
-d client_secret=s3cr3t \
-d scope=PRINCIPAL_ROLE:ALL | jq -r .access_token)Create a catalog named sales_warehouse backed by the bucket. The endpoint is the address Spark will use for S3, which inside the Compose network is http://rustfs:9000:
curl -X POST http://localhost:8181/api/management/v1/catalogs \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"catalog": {
"name": "sales_warehouse",
"type": "INTERNAL",
"properties": { "default-base-location": "s3://warehouse/sales" },
"storageConfigInfo": {
"storageType": "S3",
"allowedLocations": ["s3://warehouse/sales"],
"endpoint": "http://rustfs:9000",
"pathStyleAccess": true,
"region": "us-west-2"
}
}
}'Creating a catalog also creates a catalog_admin catalog role and grants it to the root principal's service_adminrole. That role can manage the catalog, but it cannot create tables with delegated storage credentials until it holdsCATALOG_MANAGE_CONTENT. Without this grant, Spark'sCREATE TABLE fails with a 403 that namesCREATE_TABLE_DIRECT_WITH_WRITE_DELEGATION:
curl -X PUT http://localhost:8181/api/management/v1/catalogs/sales_warehouse/catalog-roles/catalog_admin/grants \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"grant": {"type": "catalog", "privilege": "CATALOG_MANAGE_CONTENT"}}'Now create a namespace through the Iceberg REST API. The catalog name is the URL prefix:
curl -X POST http://localhost:8181/api/catalog/v1/sales_warehouse/namespaces \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"namespace": ["analytics"]}'In a real deployment you would create a dedicated principal with its own principal role and catalog role instead of using root. The Polaris quickstart Compose file shows that full sequence.
3. Writing and Querying via Spark SQL
Open a Spark SQL shell inside the Spark container. The--packages flag pulls the Iceberg Spark runtime built for Spark 4.1 and the Iceberg AWS bundle from Maven Central on first run:
docker compose exec spark /opt/spark/bin/spark-sql \
--packages org.apache.iceberg:iceberg-spark-runtime-4.1_2.13:1.12.0,org.apache.iceberg:iceberg-aws-bundle:1.12.0 \
--conf spark.jars.ivy=/tmp/.ivy2 \
--conf spark.sql.extensions=org.apache.iceberg.spark.extensions.IcebergSparkSessionExtensions \
--conf spark.sql.catalog.polaris=org.apache.iceberg.spark.SparkCatalog \
--conf spark.sql.catalog.polaris.type=rest \
--conf spark.sql.catalog.polaris.uri=http://polaris:8181/api/catalog \
--conf spark.sql.catalog.polaris.warehouse=sales_warehouse \
--conf spark.sql.catalog.polaris.rest.auth.type=oauth2 \
--conf spark.sql.catalog.polaris.oauth2-server-uri=http://polaris:8181/api/catalog/v1/oauth/tokens \
--conf spark.sql.catalog.polaris.credential=root:s3cr3t \
--conf spark.sql.catalog.polaris.scope=PRINCIPAL_ROLE:ALL \
--conf spark.sql.catalog.polaris.header.X-Iceberg-Access-Delegation=vended-credentialsSpark gets no S3 keys here. TheX-Iceberg-Access-Delegation: vended-credentials header asks Polaris to return storage credentials, the endpoint, and path-style settings with each table load, which is the credential vending flow described earlier on this page.
Inside the Spark SQL shell, create a table, insert test records, and read them back:
CREATE TABLE polaris.analytics.orders (
order_id BIGINT,
customer_id BIGINT,
order_amount DOUBLE,
order_date DATE
) USING iceberg;
INSERT INTO polaris.analytics.orders VALUES
(1, 101, 150.50, DATE '2026-05-22'),
(2, 102, 99.90, DATE '2026-05-22'),
(3, 103, 300.00, DATE '2026-05-22');
SELECT * FROM polaris.analytics.orders;The query returns the three rows. The Parquet data files and Iceberg metadata files land in the bucket unders3://warehouse/sales/analytics/orders/, and Polaris holds the pointer to the current metadata file. Any other REST catalog client, such as PyIceberg, can point at port 8181 with the same warehouse name and read the table. When you are done, rundocker compose down -v to remove the containers and the bucket data.
Troubleshooting and Resolving Common Errors
Deploying and connecting compute engines to an Iceberg REST Catalog can expose configurations to connection or authentication issues. Below are the most common operational errors and strategies to resolve them.
1. OAuth2 Authentication and Token Failures
If your client engine fails to initiate, displaying an authentication error during startup:
HTTP 401 Unauthorized: Credentials invalid or scope roles not matching.This occurs when the Client ID or Client Secret passed by the engine does not match the catalog registry, or the specified scope is not associated with the principal. Verify the security mappings inside Polaris. If you are using principal role scopes, ensure the string prefix matches the format expected by Polaris (such as PRINCIPAL_ROLE:role_name).
2. Forbidden Access and RBAC Violations
If a query compilation fails with a permission warning:
HTTP 403 Forbidden: Principal is not authorized to perform TABLE_READ on table sales.invoices.This is an RBAC enforcement error. It indicates the client authenticated successfully but has not been granted permissions on the namespace or table. To resolve this, check that the Catalog Role has been assigned the specific privileges (like TABLE_READ or TABLE_WRITE) and that the Principal Role of your client is correctly mapped to that Catalog Role inside Polaris.
3. Optimistic Concurrency Control Collisions
During concurrent write operations, a write engine may throw a conflict error:
org.apache.iceberg.exceptions.CommitFailedException: Requirement failed: branch main has changed.This is expected behavior and indicates the REST Catalog successfully prevented data corruption. When two writers attempt to modify a table simultaneously, the first to submit the commit request succeeds, swapping the metadata pointer. The second writer's update request contains outdated requirements (matching the old metadata version). Polaris detects this version drift, rejects the commit, and prompts the client engine to retry. The client engine must reload the table metadata, re-apply its changes, and submit the commit again.
4. Storage Path Access and Credential Vending Failures
If an engine connects to the REST catalog but fails to read the physical data:
Access Denied: S3 Access Blocked. Scoped storage credential expired or missing permissions.This occurs if the cloud IAM role held by the Polaris server itself does not have authorization to request scoped tokens or lacks read/write rights to the specified bucket warehouse prefix. Ensure the master IAM policy allows the Polaris container to call AWS STS AssumeRole. Also, make sure the warehouse path parameter inside Polaris (e.g., s3://warehouse/sales) is a valid URI that matches the storage prefix.
Enterprise Migration Paths: Migrating to Apache Polaris
Migrating an existing enterprise data platform from legacy metadata catalogs like the Apache Hive Metastore (HMS) or vendor-managed catalogs to Apache Polaris requires a structured transition plan. Because Polaris serves metadata via the standard REST Catalog specification, the migration process can be executed with zero downtime or disruption to active query workloads.
1. Metadata Migration Strategy
The first step in a migration is registering table definitions within Polaris without moving or rewriting the underlying Parquet files in object storage. For tables already in the Apache Iceberg format, you can register them directly in Polaris using the table registration API.
The registration payload tells Polaris the current location of the root table metadata file (the metadata.json file). Once registered, any REST-compliant query engine pointing to Polaris can immediately read and write to the table. For legacy Hive tables (which store data in flat directories rather than Iceberg structures), you must first perform an in-place upgrade to Iceberg using Spark SQL's CONVERT TO ICEBERG or MIGRATE command. This action generates the required metadata tree in your storage bucket while keeping the original data files intact.
2. Safe Transition Workflow
To ensure a risk-free transition, implement a staged cut-over process:
- Phase 1: Shadow Registration and Read Verification: Register existing tables in a temporary Polaris catalog namespace. Configure your query engines (such as Dremio or Trino) to connect to this new catalog in read-only mode. Run validation queries to verify that the query engines return identical results when querying through the legacy catalog and Polaris.
- Phase 2: Dual Writing (Optional): If your workflows require absolute safety, configure ingestion pipelines to write to both the old catalog and the new Polaris catalog. This is rarely necessary for Iceberg tables because the table files are updated atomically, but it can be useful for validating concurrent commit behaviors.
- Phase 3: Production Routing Cut-over: Update the production configurations of your write engines (Spark, Flink) to point exclusively to Polaris. Once the ingestion pipelines are redirected, update the configurations of your query and business intelligence engines to route all read queries through Polaris.
- Phase 4: Legacy Catalog Decommissioning: Once you verify that all active workloads are reading and writing through Polaris, you can safely decommission the old Hive Metastore or vendor-managed catalog instances, reducing maintenance costs and eliminating catalog sync issues.
Advanced Production and Scaling Guidelines
Running a REST catalog in production requires careful planning for scalability, high availability, and security. Because the REST server is stateless, you can cluster it across multiple instances to manage large metadata traffic volumes.
1. High Availability and Persistence Adapters
In a local sandbox, Polaris runs with an in-memory metadata store. In a production cluster, you must configure a persistent database back-end. Polaris uses EclipseLink as its Object-Relational Mapping (ORM) layer, allowing it to connect to any transactional JDBC database.
For enterprise deployments, run Polaris containers on Kubernetes (such as Amazon EKS) backed by a managed relational database (such as Amazon Aurora PostgreSQL). This setup ensures the metadata catalog is transactional, highly available, and isolated from query execution failures.
2. Rate Limiting and Performance Optimization
Although query engines query cloud storage directly for data files, metadata calls are routed to the REST catalog server. A single query planning phase can trigger multiple catalog requests to load schemas, snapshots, and credential objects.
To prevent the REST catalog from becoming a bottleneck:
- Client-side Caching: Configure query engines to cache table metadata locally during planning phases to avoid excessive catalog lookups.
- Rate Limiting: Implement rate-limiting rules on your API gateway to prevent rogue client scripts from overwhelming the catalog server with loops of table resolution calls.
- Connection Pooling: Tune the JDBC connection pool settings on the Polaris server to ensure fast metadata query resolution to the backing database.
3. OAuth2 Token Lifetimes and Rotation Policies
Secure catalog access by applying strict expiration policies on OAuth2 bearer tokens. Set the token time-to-live (TTL) to one hour or less. Most modern Iceberg REST clients are designed to automatically refresh tokens before they expire. Keep access keys restricted and rotate client credentials regularly to maintain catalog security.