This article provides a step by step comparison of seven Apache Iceberg REST catalog implementations, measuring what each one actually serves against the published specification.
A Python probe harness is built to issue one identical request suite to every catalog and store the raw response as evidence. https://github.com/xbill9/lakehouse-iceberg-2026 Every vendor with a lakehouse now ships an Iceberg REST catalog, and every one of them says it implements the same specification.
That claim is checkable.
The specification also asks a server to publish a machine-readable list of the endpoints it supports, which makes a second claim checkable: does a catalog agree with itself?
All results below were measured on 2026-09-03.
Nothing here is about the REST catalog being broken.
It works — every catalog served the core read path.
What Do You Need to Reproduce This?
Seven catalogs means seven accounts, and three of mine were trials that will have expired by the time most people read this.
Read this section before investing an afternoon.
Catalog What you need What I used Apache Polaris Docker local container, free Google BigLake GCP project, Lakehouse API enabled owned project AWS Glue AWS account owned account AWS S3 Tables AWS account owned account Databricks Unity Premium workspace 14-day trial Snowflake Horizon any Snowflake account 30-day Enterprise trial Microsoft OneLake Fabric licence on a work account 60-day Fabric trial capacity Three of those need saying plainly.
Databricks Free Edition will not work.
Enabling external data access is an account-level action, and Free Edition documents no access to the account console.
A personal Microsoft account cannot hold a Fabric licence.
The Fabric API returns and no configuration changes it.
A work or school account in an Entra tenant can, and the free 60-day trial capacity is enough — the paid F-SKU the Azure portal steers you toward is not needed.
Snowflake Open Catalog is closed to new signups.
Snowflake's documentation directs new customers to Horizon, which is what this measures.
Being on a trial tier is also a genuine confound in the results, not only an inconvenience.
The limitations section returns to it.
What Is Apache Iceberg?
Iceberg is a table format.
A table is a directory of Parquet files plus a chain of JSON metadata files recording which of those files belong to the table right now, what the schema is, how it is partitioned, and what it looked like at every previous commit.
That history is why engines can time-travel and why two writers can commit without corrupting each other.
Something has to hold the pointer to the current metadata file.
That something is the catalog.
It answers one question — for table , where is the metadata right now — and it makes commits atomic by swapping that pointer.
What Is the REST Catalog?
For years each engine brought its own catalog: Hive Metastore, a Glue client, a JDBC catalog, a filesystem convention.
Every engine needed a driver for every catalog.
The Iceberg REST catalog replaces that with one HTTP API.
A client speaks HTTP to a URL and the vendor implements the endpoints behind it.
The specification lives in the Iceberg repository as .
Thirty-five operations: listing namespaces, loading a table, committing an update, creating a view, planning a scan.
What Does a Catalog Say About Itself?
The endpoint every client calls first is .
It returns the routing prefix for every later request, and it may return one more thing: endpoints: A list of endpoints that the server supports.
The format of each endpoint must be . — , That field is the reason this article exists.
A catalog publishes a machine-readable list of what it supports, and nothing stops a client from trusting it.
Throughout, declared means named in that array and served means returned a 2xx to the probe.
Where a catalog declares an endpoint and does not serve it, I call that an overclaim, on the strength of the spec's own word supports.
What This Suite Does Not Do Apache ships a REST Compatibility Kit, , which tests a server's behaviour against the Java reference implementation.
It is the right tool for "is this catalog correct".
It does not compare a server's declaration against what that server serves, which is the axis here.
The two are complementary.
This suite also never checks whether a returned value is right.
It checks whether a field is present.
The Harness Three tiers of evidence, from one identical request suite: Endpoint tier — does the operation exist, and what status comes back Field tier — 30 specification field paths checked against each Declaration tier — the array cross-checked against behaviour Twenty-five of the specification's 35 operations, or 71%.
Two design rules matter for reading the results.
Raw request and response are stored for every probe, so a verdict is re-derivable without re-running against a vendor.
And a probe whose prerequisite failed is marked not-tested rather than failed — if a catalog refuses to create a namespace, the probes that needed one prove nothing about the endpoints they target.
At This Point You Should Have Docker, for the control catalog Python 3.13 with , and An account on whichever vendors you intend to probe The maximum privilege available on each — every result below was gathered with , AWS account root, , Databricks account admin, Fabric workspace Admin, and the Polaris root principal Bringing Up Apache Polaris, the Control Start with the control, not the clouds.
A red cell in a permissively configured reference implementation is almost always your bug, not a specification gap.
Four things there are not obvious, and each one cost a failed attempt.
FILE storage is refused by default, and enabling it needs both and .
Enabling it then escalates the production-readiness check from warning to fatal: So is required as well.
Third, the container writes table metadata while the client writes data files, so both need the same warehouse path — hence the bind mount at an identical absolute path and so files are owned by you.
Fourth, and the one that wastes an hour: with , Hadoop's cannot resolve the uid, and the failure surfaces as a 503 that reads like a storage error.
Mounting read-only fixes it. is on deliberately: with it off, Polaris refuses both a purge drop and , which would put artificial red cells in the control.
Then the catalog and its grants: Bringing Up Google BigLake The endpoint is .
Note , not — both resolve and return identical config, but is what the documentation gives.
Two things are required beyond the URL.
The API is enabled under the name , but the error calls it the Lakehouse API: And every request needs an header.
With both in place: The warehouse is for a single-bucket catalog or for a multi-bucket one — not , which addresses the separate BigQuery federation endpoint.
Seeding writes data files to GCS with application-default credentials, which are separate from the user token.
A error means .
Bringing Up AWS Glue The endpoint is , SigV4-signed with signing name , and the warehouse is the account id.
Glue rejects without an explicit table location, which every other catalog here infers: Bringing Up AWS S3 Tables The endpoint is , SigV4 with signing name , and the warehouse is the table bucket ARN.
Two constraints do not appear until you hit them.
Namespace names reject uppercase, so a scratch namespace stamped fails on the : And requires in the body.
S3 Tables' managed bucket also rejected 's default PyArrow writer: Switching to worked.
Bringing Up Databricks Unity The endpoint is .
Three prerequisites, in order.
External data access is off by default, per metastore: Second, the privilege, which must be granted on the catalog: Third, and the one worth knowing: the Iceberg endpoint requires a token with the scope.
A PAT scoped to plus — sufficient for Unity's own REST API and for the SQL statement API — is rejected: Probing the open-standard surface needs a broader credential than the vendor's own APIs do.
Unity also rejects two