Which Iceberg Catalog Should You Use: Polaris, Unity or Glue?
Quick answer: Which Iceberg catalog you should use comes down to platform gravity, not marketing. Use Unity Catalog if Databricks is your primary compute, AWS Glue if you are deep in Lake Formation and EMR, and Apache Polaris or Snowflake Horizon Catalog if you genuinely run multiple engines against the same tables. The file format is portable; the catalog is where the lock-in lives, because it owns commit coordination, credential vending and policy enforcement.
Last updated: August 2026
Every open-format pitch follows the same script: put your data in Apache Iceberg, keep it in your own object storage, and you are free. Parquet on S3, an open spec, no vendor holding your data hostage. All true, and all beside the point.
An Iceberg table is not just files. Something has to hold the pointer to the current metadata file and arbitrate concurrent commits so two writers do not clobber each other. That something is the catalog, and whoever runs it decides which engines can write, who gets storage credentials, and whether your masking policies survive the trip. Your Parquet is portable. Your catalog is a migration project.
So the question is not whether to go Iceberg - that argument is over. It is which Iceberg catalog you should use, and the answer turns on four things that rarely reach a feature matrix: where your compute already lives, whether you need more than Iceberg, how deep your governance goes, and who is on the hook at 2am.
Why the catalog, not the file format, is the lock-in
An Iceberg table has three layers: data files, metadata files, and a catalog entry saying which metadata JSON is current. The first two are object storage and genuinely portable. The third is a service with an API and a permissions model. It performs the atomic swap on commit, so it is the concurrency control for every writer, and increasingly it performs credential vending: the client asks for a table and gets back short-lived, scoped storage credentials instead of a broad IAM role. Good security, and the moment your governance becomes catalog-specific.
The Iceberg REST Catalog spec (IRC) was supposed to make this swappable. In practice it defines a surface with a lot of optional behaviour, and every implementation covers a different subset. Two catalogs can both be IRC-compliant and still differ on namespace nesting, accepted table spec versions, delegation headers, and whether a policy-protected table is visible at all.
The seven criteria that actually decide the choice
- Format scope. Iceberg only, or Iceberg plus Delta and Hudi? If half your estate is Delta, an Iceberg-only catalog means an unbudgeted conversion project.
- REST conformance. Which endpoints are implemented, which table spec versions are accepted, whether nested namespaces work. This is what breaks Trino and Spark in week three.
- Credential vending. Does the catalog issue scoped, short-lived credentials, and does it refuse for tables under fine-grained policies? The refusal case matters more than the happy path.
- Engine neutrality. Whether writes from a non-native engine are first-class or a preview with a limitations page.
- Governance depth. RBAC only, or column masking, row filters and tag-based policy - and whether those survive a foreign engine reading the table.
- Operational burden. Self-hosted OSS, managed service, or already part of the platform you pay for.
- Cost model. Per-call, per-credit or bundled. The per-call ones surprise people, because nobody instruments catalog chatter until the bill lands.
Which Iceberg catalog should you use: side-by-side
The vendor-neutral read as of August 2026, taken from each vendor's own documentation.
| Criterion | Apache Polaris | Snowflake Horizon Catalog | Databricks Unity Catalog | AWS Glue Data Catalog |
|---|---|---|---|---|
| Governance | Apache top-level project since Feb 19, 2026 | Snowflake product feature | Databricks product feature | AWS service |
| Formats | Iceberg, plus non-Iceberg formats such as Delta via Generic Tables | Iceberg; Delta readable via catalog integration | Iceberg and Delta natively | Iceberg plus Hive-era types |
| IRC endpoint | Native - Polaris is an IRC implementation | /polaris/api/catalog | /api/2.1/unity-catalog/iceberg-rest | glue.<region>.amazonaws.com/iceberg |
| External writes | Engine-agnostic by design | GA May 26, 2026, Snowflake-managed tables | Managed Iceberg yes; foreign Iceberg read-only | Yes, per Lake Formation permissions |
| Credential vending | Yes for Iceberg tables; not for Generic Tables | Yes, X-Iceberg-Access-Delegation | Yes, but not for tables with row filters or column masks | Yes, Lake Formation scoped credentials |
| Policy over IRC | External authorizers: OPA and Ranger, both beta | Masking, tag-based masking and row access policies enforced on reads | Vending blocked on masked tables; cross-engine ABAC reads in beta | Lake Formation, IAM or hybrid |
| Ops burden | You run it, or take a managed distribution | None - part of the account | None - part of the workspace | None - AWS-managed |
| Cost model | Infrastructure you provision | 0.5 credit per million API calls, as Cloud Services | Bundled with the platform | Glue request pricing |
Format support: Iceberg only, or Iceberg plus Delta?
This is the question that quietly eliminates options. Apache Polaris took the broadest position: alongside the Iceberg REST APIs it exposes a Generic Table API, GA since Polaris 1.3.0 in January 2026, which registers non-Iceberg tables under a free-form format string - the docs use delta and csv as the examples. Read the limitation before you plan around it: Polaris does not vend credentials for Generic Tables. They are catalogued pointers, so the engine still needs its own storage access. Releases since have been steady rather than dramatic - Apache Ranger as an external authorizer (beta) in 1.5.0, BigQuery Metastore federation docs in 1.6.0, and 1.7.0 in August 2026.
Unity Catalog handles both formats natively, unsurprising given Databricks owns Delta. The nuance is directional: over the Iceberg REST endpoint, managed Iceberg tables support read and write, but Delta tables with Iceberg reads enabled are read-only, as are foreign Iceberg tables. Bi-format in the platform, read-mostly at the REST boundary.
Snowflake came at it differently. Consuming Delta Shares in Horizon Catalog reached GA on July 21, 2026: create a catalog integration with CATALOG_SOURCE = DELTA_SHARING, attach a catalog-linked database, query the shared tables. It is explicitly read-only - no insert, update or create from Snowflake. Good for consuming a partner's share without a copy pipeline; not a bi-format lakehouse. Glue is Iceberg-and-Hive-lineage: a hard stop if you have Delta to catalogue.
Credential vending is where governance quietly breaks
All four vend short-lived storage credentials. The differentiator is what happens when the table sits under a column mask or a row filter, and the answers diverge enough to change your design.
Databricks documents that Unity Catalog credential vending is not supported for tables with row filters or column masks. That is the right call - once an external engine holds raw storage credentials, a row filter is a suggestion. Views, materialized views and tables shared through Open Sharing are outside vending too. Databricks' answer is a separate path rather than an exception: cross-engine ABAC, in beta, lets an external engine read managed tables with row filters and column masks enforced server-side, with Databricks compute doing the filtering instead of handing over storage credentials. It is read-only - writing requires exempting the principal from the policy - and it wants recent clients, Iceberg-Spark 1.11 and Spark 4.0 or above. Snowflake took a different route to a similar place: tables protected by masking, tag-based masking and row access policies can be read over the Horizon REST API from Spark, with policy applied on the read path. The price lands on the write side, where writing to tables carrying fine-grained policies or tags is unsupported.
On AWS, vending is Lake Formation's job: the engine assumes an IAM role, Lake Formation issues scoped credentials for the table location, and permissions are Lake Formation grants, IAM, or hybrid mode. The design lesson: decide up front whether external engines are trusted compute. If not, Unity's model is the honest one, and serve those workloads a governed view rather than table credentials.
What changed in Snowflake in 2026
Three changes landed this year that moved the Snowflake side of this comparison, and most published comparisons predate all three.
- Snowflake storage for Iceberg tables went GA on June 1, 2026. Set
EXTERNAL_VOLUME = SNOWFLAKE_MANAGEDand Snowflake stores and manages the data and metadata files. No bucket, no external volume object, no IAM trust relationship, and Fail-safe on permanent tables. - External engines can now write, not just read. Read access hit GA on Feb 6, 2026; write support from any engine speaking the Iceberg REST protocol hit GA on May 26, 2026, covering Snowflake-managed v2 and v3 tables, and added Workload Identity Federation with OIDC.
- Iceberg v3 went GA on May 7, 2026 with deletion vectors, row lineage, default column values, and the geography, geometry, nanosecond-timestamp and variant types.
Removing the external volume setup is the underrated one. The first two days of any Snowflake Iceberg project used to go on bucket policies and role trust:
-- Set the default once, at the database level
ALTER DATABASE analytics SET EXTERNAL_VOLUME = SNOWFLAKE_MANAGED;
CREATE OR REPLACE ICEBERG TABLE analytics.public.orders_iceberg (
order_id NUMBER(38,0),
customer_id NUMBER(38,0),
order_ts TIMESTAMP_NTZ,
order_status VARCHAR(32),
total_amount NUMBER(18,2),
line_items VARIANT
)
CATALOG = SNOWFLAKE
EXTERNAL_VOLUME = SNOWFLAKE_MANAGED;
-- Grants an external Spark job needs to read AND write over the REST API
GRANT USAGE ON DATABASE analytics TO ROLE spark_writer;
GRANT USAGE ON SCHEMA analytics.public TO ROLE spark_writer;
GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE
ON TABLE analytics.public.orders_iceberg TO ROLE spark_writer;
One caveat that decides real designs: Snowflake documents this as available only for accounts hosted on AWS or Azure, and not in government regions or in the People's Republic of China. On a GCP-hosted account you are still on external volumes, and this section is theory.
Reading and writing Snowflake tables from Spark
The Horizon endpoint lives at https://<account_identifier>.snowflakecomputing.com/polaris/api/catalog - the Polaris path is not a coincidence. Auth is External OAuth, a key-pair JWT exchanged for an access token, a Programmatic Access Token, or Workload Identity Federation. The scope parameter carries the Snowflake role, so your existing RBAC follows the connection.
from pyspark.sql import SparkSession
ACCOUNT = "myorg-myaccount"
CATALOG = "analytics" # the Snowflake database name
ROLE = "session:role:spark_writer"
TOKEN = "<access_token>" # PAT, External OAuth or key-pair exchange
spark = (
SparkSession.builder.appName("horizon-iceberg")
.config("spark.sql.extensions",
"org.apache.iceberg.spark.extensions.IcebergSparkSessionExtensions")
.config(f"spark.sql.catalog.{CATALOG}",
"org.apache.iceberg.spark.SparkCatalog")
.config(f"spark.sql.catalog.{CATALOG}.type", "rest")
.config(f"spark.sql.catalog.{CATALOG}.uri",
f"https://{ACCOUNT}.snowflakecomputing.com/polaris/api/catalog")
.config(f"spark.sql.catalog.{CATALOG}.warehouse", CATALOG)
.config(f"spark.sql.catalog.{CATALOG}.token", TOKEN)
.config(f"spark.sql.catalog.{CATALOG}.scope", ROLE)
.config(f"spark.sql.catalog.{CATALOG}.header.X-Iceberg-Access-Delegation",
"vended-credentials")
.getOrCreate()
)
spark.sql(f"SELECT order_status, COUNT(*) "
f"FROM {CATALOG}.public.orders_iceberg GROUP BY 1").show()
Read the limitations page first. Only Snowflake-managed Iceberg tables are reachable, not native tables and not externally managed ones. CTAS from an external engine, equality deletes, and creating Iceberg tags or branches are unsupported, and cloned or converted tables do not work with vended credentials. User-level network policies are unsupported here too, which catches security teams who standardised on them.
The cost line nobody has modelled: Iceberg REST API calls
Here is the trap. Snowflake documents Horizon Iceberg REST Catalog API requests at 0.5 credit per million calls, charged as Cloud Services, with billing "scheduled to begin in second half of 2026, subject to change". It is the only per-call catalog charge among the four, and because it is a new line rather than an established one, almost nobody has a baseline for their own call volume. Confirm the current rate and start date with your account team before you build a number on it.
| Monthly IRC calls | Credits | What generates this |
|---|---|---|
| 1 million | 0.5 | A handful of Spark jobs, hourly |
| 50 million | 25 | A few hundred tables polled every few minutes |
| 500 million | 250 | Chatty clients doing per-partition metadata loads |
| 2 billion | 1,000 | An agent or BI tool refreshing metadata per query |
Those rows are arithmetic from the documented rate, not measured benchmarks, and the right-hand column is illustrative - your own call volume is the only number that matters. The rate is not scary; call amplification is. A client issuing loadTable on every task rather than caching, or an orchestrator health-checking every table every minute, puts two orders of magnitude between a well-behaved deployment and a badly configured one. Budget for cross-region egress too: Snowflake's docs say standard cross-region egress charges apply, so an engine in a different region from the account pays twice.
SELECT
DATE_TRUNC('day', start_time) AS usage_day,
service_type,
ROUND(SUM(credits_used_compute), 2) AS compute_credits,
ROUND(SUM(credits_used_cloud_services), 2) AS cloud_services_credits,
ROUND(
100 * SUM(credits_used_cloud_services)
/ NULLIF(SUM(credits_used_compute), 0), 1
) AS cloud_svc_pct_of_compute
FROM snowflake.account_usage.metering_history
WHERE start_time >= DATEADD('day', -60, CURRENT_TIMESTAMP())
GROUP BY 1, 2
ORDER BY usage_day DESC, cloud_services_credits DESC;
One thing that softens the blow, and one caveat on it. Snowflake charges for Cloud Services only when daily Cloud Services consumption exceeds 10% of that day's virtual warehouse usage, calculated daily in UTC, with serverless compute excluded from the calculation. So an account already running warehouses hard has headroom. An account whose Snowflake compute is small relative to its external-engine traffic does not, and that is exactly the shape of a lakehouse where Spark and Trino do the work. Whether this specific line item falls under that adjustment is a question for your account team, not a blog.
Pointing Snowflake at an external catalog instead
The mirror-image design: keep Glue, Unity or Polaris as the source of truth and let Snowflake be one of the engines. Write support for externally managed Iceberg tables and catalog-linked databases has been GA since October 2025, so Snowflake runs INSERT, UPDATE, DELETE and MERGE against tables it does not own.
CREATE OR REPLACE CATALOG INTEGRATION glue_rest_int
CATALOG_SOURCE = ICEBERG_REST
TABLE_FORMAT = ICEBERG
CATALOG_NAMESPACE = 'sales'
REST_CONFIG = (
CATALOG_URI = 'https://glue.us-east-1.amazonaws.com/iceberg'
CATALOG_API_TYPE = AWS_GLUE
CATALOG_NAME = '123456789012' -- AWS account ID
)
REST_AUTHENTICATION = (
TYPE = SIGV4
SIGV4_IAM_ROLE = 'arn:aws:iam::123456789012:role/snowflake-glue-role'
SIGV4_SIGNING_REGION = 'us-east-1'
)
ENABLED = TRUE;
-- Auto-discovers namespaces and tables, and stays in sync
CREATE DATABASE glue_lakehouse
LINKED_CATALOG = (
CATALOG = 'glue_rest_int',
ALLOWED_NAMESPACES = ('sales', 'marketing'),
ALLOWED_WRITE_OPERATIONS = ALL,
SYNC_INTERVAL_SECONDS = 60
);
SELECT COUNT(*) FROM glue_lakehouse.sales.orders;
Swap REST_AUTHENTICATION for TYPE = OAUTH with OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET and OAUTH_TOKEN_URI to point at Polaris or Snowflake Open Catalog, or TYPE = BEARER for a generic REST catalog. Set ALLOWED_WRITE_OPERATIONS = NONE for a read-only consumer - the documented values are only NONE and ALL, so there is no partial-write middle ground, and anything that would commit back to the remote catalog is off the table. Snowflake polls the linked catalog on SYNC_INTERVAL_SECONDS, defaulting to 30 seconds, so discovery lag is a tuning knob rather than a surprise. Open Catalog is Snowflake's managed Apache Polaris service, with the same principal-role and catalog-role RBAC. It can federate to catalogs managed elsewhere, but Snowflake's docs are explicit that tables synced in from an external catalog are read-only inside Open Catalog: federation is discovery, not a write path.
Gotchas that decide real designs
- Glue REST only speaks single-level namespaces. If your Polaris or Unity layout uses nested namespaces, the mapping is not one-to-one and you will be flattening.
- Glue's REST endpoint documents v1 and v2 table specs, defaulting to v2. The Glue ETL runtime ships its own bundled Iceberg library, versioned separately from the endpoint, so what a Glue job can write and what the REST endpoint accepts are two different questions. Verify both against your writers before assuming v3 flows end-to-end.
- An external engine cannot upgrade a Snowflake Iceberg table from v2 to v3 through Horizon Catalog. Snowflake documents that plainly in the external-engine limitations, so plan the version at create time rather than assuming you can move a table later.
- At Iceberg v3 GA on May 7, 2026, external-engine writes to Snowflake-managed v3 tables through Horizon were not supported. The write GA on May 26 covers v2 and v3, but any architecture doc written in early May is stale here.
- Unity Catalog needs External data access enabled on the metastore plus
EXTERNAL USE SCHEMAon the schema. Both are off by default, and teams routinely lose a day before finding the metastore toggle.
The verdict, per platform gravity
- Databricks-centred: Unity Catalog. Native for Delta and Iceberg, nothing extra to run. Accept that credential vending stops at masked and filtered tables, and that the read path for those tables, cross-engine ABAC, is still beta - design your external-engine story around governed serving rather than vended credentials.
- Deep AWS: Glue Data Catalog. If Lake Formation already expresses your permissions and EMR, Athena and Redshift are your engines, a second catalog buys complexity, not neutrality.
- Snowflake-centred, wanting open access out: Horizon Catalog. Managed storage removes the IAM setup, external engines read and write over the standard REST protocol, and masking and row-access policies still apply on reads. Model the per-call cost before it bills.
- Genuinely multi-engine: Apache Polaris. A top-level Apache project since February 2026, the broadest format scope through Generic Tables, external authorizer hooks for OPA and Ranger while they mature out of beta, and no vendor owning your roadmap. You are running a service - budget for it, or take a managed distribution.
The one answer that is always wrong to the question of which Iceberg catalog you should use is that you will decide later because the data is open anyway. The moment the first engine commits through a catalog, that catalog owns your table state, and swapping it means re-registering every table and re-testing every writer.
Related Articles
Frequently Asked Questions
Q: Do I still need an S3 bucket for Snowflake Iceberg tables?
Not since June 1, 2026. Setting EXTERNAL_VOLUME = SNOWFLAKE_MANAGED tells Snowflake to store and manage the Iceberg data and metadata files for you, with no external volume object, bucket policy or IAM trust relationship, and Fail-safe on permanent tables. The catch: Snowflake documents it as available only for accounts hosted on AWS or Azure, and not in government regions or in the People's Republic of China, so GCP accounts still use external volumes.
Q: Can Spark write to Snowflake Iceberg tables?
Yes. Write support from any external engine speaking the Iceberg REST protocol reached general availability on May 26, 2026, covering Snowflake-managed v2 and v3 tables through the Horizon Catalog endpoint. The engine's role needs SELECT, INSERT, UPDATE, DELETE and TRUNCATE on the table plus USAGE on the parent database and schema. CTAS from an external engine is not supported.
Q: Is Apache Polaris production ready?
It graduated from the Apache Incubator to a top-level Apache project on February 19, 2026, the ASF's signal that community and governance are mature, and releases have kept a steady cadence since - 1.5.0 in May 2026, 1.6.0 in July, 1.7.0 in August. Two things to know before you commit: the external authorizer integrations, Open Policy Agent and Apache Ranger, are both documented as beta, and Generic Tables, the non-Iceberg format path, do not support credential vending.
Q: What does the Snowflake Iceberg REST API cost?
Snowflake documents Horizon Iceberg REST Catalog API requests at 0.5 credit per million calls, charged as Cloud Services, with billing scheduled to begin in the second half of 2026 and explicitly subject to change. Cross-region data egress applies on top. Baseline your call volume now, because chatty clients amplify this by two orders of magnitude.
Q: Polaris vs Glue for Iceberg on AWS - which should I pick?
If your engines are EMR, Athena and Redshift and your permissions already live in Lake Formation, Glue is the lower-friction answer and adding Polaris buys complexity rather than neutrality. Choose Polaris when you need nested namespaces, non-Iceberg formats through Generic Tables, external authorizer hooks such as OPA or Ranger (both still beta), or a catalog not tied to one cloud.
Q: Can Unity Catalog serve Delta tables to external Iceberg engines?
Read-only. Over the Unity Catalog Iceberg REST endpoint, managed Iceberg tables support read and write, while Delta tables with Iceberg reads enabled and foreign Iceberg tables are read-only. Credential vending is separately unavailable for tables carrying row filters or column masks. Databricks' cross-engine ABAC, currently in beta, gives external engines a read path to those tables with the policies enforced server-side, but it is reads only and it needs recent client versions.
