Apache Polaris 1.8.0
Released on September 28th, 2026.
Downloads🔗
Release Notes🔗
Upgrade notes🔗
- Relational JDBC: schema version 6 corrects the
idx_locationsindex on Postgres and CockroachDB (see Fixes). Fresh bootstraps use schema v6 automatically and get the right index. Because Polaris has no automated schema migrations, existing Postgres/CockroachDB deployments keep the old, ineffective index until an operator recreates it manually:H2 is unaffected.DROP INDEX polaris_schema.idx_locations; CREATE INDEX idx_locations ON polaris_schema.entities USING btree (realm_id, catalog_id, location_without_scheme) WHERE location_without_scheme IS NOT NULL; - Relational JDBC: schema version 6 also declares
idx_grants_realm_grantee,idx_grants_realm_securableandidx_entities_catalog_id_idon CockroachDB (see Fixes), which the Postgres and H2 schemas have declared since schema v4. Fresh bootstraps get them automatically; existing CockroachDB deployments, including any already at schema version 6, need them created manually:Postgres and H2 are unaffected.CREATE INDEX IF NOT EXISTS idx_grants_realm_grantee ON polaris_schema.grant_records (realm_id, grantee_id); CREATE INDEX IF NOT EXISTS idx_grants_realm_securable ON polaris_schema.grant_records (realm_id, securable_id); CREATE INDEX IF NOT EXISTS idx_entities_catalog_id_id ON polaris_schema.entities (catalog_id, id);
Breaking changes🔗
Concurrent table commits that hit a stale sequence number now return a retryable
409instead of a fatal400, for both single-table commits andcommitTransaction.The Relational JDBC backend no longer creates its database schema during bootstrap: creating the schema is a privileged operation that belongs to a database administrator. Fresh installations must create the schema (by default
CREATE SCHEMA polaris_schema;on PostgreSQL) before running the admin tool’sbootstrapcommand. Existing deployments are unaffected — their schema already exists, and the shippedcurrentSchemadefault (POLARIS_SCHEMA) preserves the previous behavior on upgrade — with one exception:If your JDBC URL already sets
currentSchema, check it before upgrading. Polaris previously qualified every query withPOLARIS_SCHEMA, so the setting was ignored; it now selects the schema Polaris reads and writes, and the JDBC driver gives a value in the URL precedence over the shipped default. An upgrade would otherwise point Polaris away from its existing tables, and a subsequentbootstrapwould create a second, empty set of tables in the other schema. Either remove the setting from the URL, or point it at the schema that already holds your Polaris tables.PolarisAuthorizer inputs now reflect the intent-based authorization SPI more directly. Resource targets and parent paths no longer include Polaris’s internal synthetic
ROOTcontainer. For example, catalog targets now have empty parent paths, root-scoped operations such asLIST_CATALOGSno longer include a syntheticROOTtarget.PolarisAuthorizer requests with multiple intents are now evaluated one intent at a time. OPA deployments will observe this as separate OPA queries per intent, so existing Rego policies that depend on the previous combined-intent input shape may need to be updated. Same applies to other PolarisAuthorizer implementations.
Internal JWTs minted before credentials-generation binding (tokens without the
polaris-cvclaim) can no longer be used as subject tokens in token exchange; they remain valid as bearer tokens until expiry. During a rolling upgrade, an old node may still mint claim-less tokens: exchanging such a token on any already-upgraded node fails withinvalid_grant, so clients can see intermittent exchange failures until the last old node is gone; after that, rejection is consistent.
New Features🔗
- Semantic models now support dedicated privileges for listing, creating, reading, updating, and dropping. Privileges can be granted to catalog roles on individual models or at namespace or catalog scope, with separate controls for managing model grants.
- Python CLI:
catalogs updatenow supports--no-stsand--no-kmsto toggle STS/KMS availability on an existing S3 catalog. Previously these were only settable atcatalogs createtime. - Python CLI: added
gcpas an external catalog authentication type for Iceberg REST federation, enabling CLI creation of GCP-authenticated catalogs such as BigLake without passing Google credential secrets through command-line flags. - Python CLI: added a global
--page-sizeoption to paginate list calls internally on Iceberg endpoints. Requires the server-sideLIST_PAGINATION_ENABLEDfeature flag. - The database schema used by the Relational JDBC persistence backend is now configurable through standard datasource configuration: the JDBC driver’s
currentSchemaconnection property (defaulted toPOLARIS_SCHEMAviaquarkus.datasource.jdbc.additional-jdbc-properties.currentSchema) selects the schema, and the persistence layer is agnostic of the schema name. Also exposed aspersistence.relationalJdbc.additionalProperties.currentSchemain the Helm chart. - Python CLI:
catalogs createandcatalogs updatenow support--storage-nameto set an optional name referencing a server-side storage configuration.
Changes🔗
- A metastore failure during authentication now returns a fixed
Service unavailablemessage instead of naming the lookup that failed; the principal lookup previously returnedUnable to fetch principal entity. The failing lookup is still named in the server log atERROR, which operators can match to the client error through the request id, returned by default asX-Request-IDand printed by the default log format asrequestId. DROP_WITH_PURGE_ENABLEDnow applies to Iceberg tables only. It previously also gated the metadata purge that a view drop performs internally, so with its default offalseandPURGE_VIEW_METADATA_ON_DROPdefaulting totrue, dropping any view failed with HTTP 403 under the default configuration. A view drop is now governed byPURGE_VIEW_METADATA_ON_DROPalone, while the guard continues to protect a client-requested Iceberg table purge.TokenBroker.verifynow returnsnullfor tokens not recognized by the internal broker (instead of failing auth), so MIXED mode can delegate to other mechanisms. Exceptions fromverifyare forwarded as-is rather than mapped to auth failure or MIXED fallback.- Client-requested list page sizes can now be bounded by a server-side maximum, configured with
LIST_PAGINATION_MAX_PAGE_SIZE(overridable per catalog viapolaris.config.list-pagination-max-page-size). It defaults to-1, meaning unlimited, so the maximum is opt-in. Once set, a request for a larger page is reduced to the maximum rather than rejected, since the Iceberg REST specification treats the requested page size as an upper bound. For local catalogs the maximum takes effect only whenLIST_PAGINATION_ENABLEDis true, since with pagination disabled the requested page size is ignored and the full result set is returned; for federated catalogs it always applies, because Polaris paginates those listings itself. Setting a maximum deviates from the Iceberg REST specification, which requires a request that does not supply apageTokento receive the complete result with a nullnext-page-token: such a request is then truncated to the maximum and answered with a continuation token, so a client that does not follow continuations sees only the first page. - Table commits whose base metadata is already stale now fail before the new metadata file is
written, saving an object-storage write and delete per conflict and returning the
409to the client sooner.
Deprecations🔗
- Deprecated the
ADD_TRAILING_SLASH_TO_LOCATIONfeature flag; Polaris now always appends a trailing slash to table and namespace base locations, so the key is accepted-but-ignored (a startup warning is emitted only when it isfalseinpolaris.featuresdefaults or realm overrides) and will be removed in a future release.
Fixes🔗
- GCS credential vending no longer fails with HTTP 500 when a table’s location or
write.data.path/write.metadata.pathpoints at a bucket root without a trailing slash (e.g.gs://bucket). Such a location parses to an empty path and previously triggered aStringIndexOutOfBoundsExceptionwhile building the access-boundary rules; GCS now handles it like the AWS integration. - Return HTTP 404 instead of 204 when a generic table or its catalog path disappears after resolution and before deletion.
- Deleting a semantic model now returns HTTP 404 instead of HTTP 500 when the model or its catalog path disappears after resolution and before the deletion is persisted.
- Return HTTP 404 instead of 500 when a policy or its catalog path disappears after resolution and before deletion.
- Iceberg REST: a malformed
pageTokenon the namespace, table and view list endpoints now returns400 Bad Request(Invalid page token) instead of500 Internal Server Error. Tokens that are valid Base64 but not a serialized page token (garbage, truncated, or produced by an incompatible Polaris version) previously escaped as Jackson decoding exceptions. - Iceberg REST: when
X-Iceberg-Access-Delegationresolves to remote signing (not implemented), either becauseremote-signingwas requested alone or becausevended-credentials,remote-signingwas requested against a catalog that cannot vend credentials, the400response now explains the situation and what to do (This catalog cannot vend credentials or sign requests; request without X-Iceberg-Access-Delegation and configure storage credentials on the client) instead of the opaqueUnsupported access delegation mode: REMOTE_SIGNING. - Iceberg REST: renaming a table or view with a missing
sourceordestinationnow returns400 Bad Requestinstead of500 Internal Server Error. - Async file-cleanup tasks now bound how long they wait for object-store deletions via the new
polaris.tasks.file-deletion-timeout(default 1h), so a stalled storage endpoint can no longer pin a task-executor thread indefinitely; a timeout is terminal for the current run rather than immediately retried, so it does not stack more deletions onto the stalled endpoint. - Python CLI
catalogs create --type externalnow validates--storage-typeand--default-base-locationup front, matching the behavior for internal catalogs and the flags’ documented “(Required)” status. Previously, omitting either produced an opaque pydanticValidationErrorat request-build time. - Iceberg REST: server-side JSON processing failures (HTTP 500) now return the standard Iceberg
error envelope (
{"error": {...}}) instead of a flat{"code", "message"}body, so Iceberg clients can parse the response rather than failing on an off-schema shape. - Python CLI
setup exportnow represents namespace paths as lists of levels in namespace, policy, and namespace-privilege entries. This preserves namespace levels that contain dots duringsetup apply; apply remains compatible with existing dot-delimited configurations. Older CLI versions cannot apply the new export format. - Python CLI
setup exportnow writes each catalog’spoliciesas a list of{name, namespace, ...}entries instead of the previous name-keyed mapping, preserving policies with the same name in different namespaces. The new export format cannot be applied by older CLI versions; use the exporting CLI version or newer forsetup apply. - Python CLI
setup exportnow preserves user-defined properties on principal roles, so exported configurations restore that metadata duringsetup apply. - Python CLI
setup exportnow preserves user-defined properties on principals and catalog roles, so exported configurations restore that metadata duringsetup apply. - Python CLI
setup applyno longer double-encodes policy content emitted bysetup export, so exported configurations containing policies can be restored. - Python CLI
setup exportnow includes nested namespaces and policy definitions from those namespaces. Previously, only top-level namespaces and their policies were exported. - Python CLI
setup exportnow exits with an error without emitting partial YAML if any required API read fails. Previously, individual failures were logged but the command printed incomplete configuration and exited with status 0. - The Iceberg REST catalog now returns HTTP 500 (instead of 400) when a commit’s outcome is unknown (
CommitStateUnknownException), as required by the Iceberg REST spec. Previously, clients receivedBadRequestExceptionfor a commit that may have been applied, which could lead to unsafe retries. - Fixed JDBC persistence under
SERIALIZABLEisolation (e.g. CockroachDB default) so that a concurrent entity create that loses a unique-name race no longer returns the phantom new entity as a successful create. The conflicting row is now reported asENTITY_ALREADY_EXISTSinstead of fabricating the entity that was not persisted. - Python CLI
setupnow preservesendpoint_internalandsts_endpointduring apply and export for S3 configuration - Fixed a false-negative in the JDBC optimized location-overlap check (
OPTIMIZED_SIBLING_CHECK). Ancestor locations stored inlocation_without_schemewithout a trailing slash were not matched by the generated ancestor equality terms, allowing nested table/namespace locations to be created under existing prefixes. The query now emits both slash-terminated and non-slash-terminated prefix terms and uses a slash-terminatedLIKEpattern for descendant matching. - Python CLI
setupnow preserves the Azurehierarchicalstorage flag during apply and export - Python CLI
setup apply --dry-runnow reports already-existing namespaces as skipped instead of proposed creations. - Fixed policy detach on the relational JDBC backend silently doing nothing when the mapping’s
parameterschanged in between. The delete’sWHEREclause included the non-keyparameterscolumn, so a re-attach landing between the detach’s lookup and its delete made the delete match zero rows while detach still reported success, leaving the policy attached. The delete is now keyed on the mapping’s identity columns, matching the table’s primary key and the transactional backend’s behavior. - Relational JDBC: the
idx_locationsindex used by the optimized sibling check now matches the query that reads it. On Postgres and CockroachDB the index led withparent_id, while the overlap query filterscatalog_id, so withOPTIMIZED_SIBLING_CHECKenabled everyCREATE TABLE/CREATE NAMESPACEfell back to a realm-wide scan instead of the intended indexed lookup. A new schema version 6 creates the index on(realm_id, catalog_id, location_without_scheme); H2 was already correct. Existing deployments need a manual index recreation — see Upgrade notes. - A metastore failure while resolving a principal’s roles during authentication now returns HTTP 503, as it already did when looking up the principal entity; previously it propagated unwrapped and was reported as HTTP 500. All three lookups now report
PolarisServiceUnavailableExceptionas the errortype, where the principal entity lookup previously reportedServiceUnavailableExceptionwith the same status. All three also sendRetry-After: 0; the Iceberg REST spec has a client retry a non-idempotent request only when that header is present. - Relational JDBC: the CockroachDB schema now declares
idx_grants_realm_grantee,idx_grants_realm_securableandidx_entities_catalog_id_idas of schema version 6; the Postgres and H2 schemas have carried them since schema v4. Withoutidx_grants_realm_grantee, loading the grants held by a principal, principal role or catalog role scans every grant record in the realm, because thegrant_recordsprimary key continues with the securable columns afterrealm_id. Existing CockroachDB deployments need a manual index creation — see Upgrade notes. - Creating a namespace without an explicit location no longer fails with HTTP 400 when the
catalog’s
default-base-locationsits inside an allowed location instead of being one of them. For example, with allowed locations3://b1anddefault-base-locations3://b1/d1,CREATE NAMESPACE nsplaces the namespace ats3://b1/d1/ns, but the check expected it directly under an allowed location, ats3://b1/ns, and rejected it as a custom location even though the request asked for none. The namespace location is now compared against the catalog’sdefault-base-location, which is what it is derived from. - Setting a namespace’s
locationproperty no longer fails with HTTP 400. With custom namespace locations disabled, which is the default, everyupdateNamespacePropertiesrequest carrying alocationwas rejected, including one that simply repeated the location the namespace already had. The error also named an expected location one level too deep: a namespace ats3://b1/nswas told it should be ats3://b1/ns/ns. - Internal JWTs are bound to principal secret generation via
polaris-cv(no secret material in the token). Credential-generation is enforced on token exchange; bearer verify is signature and claims only. Secrets-load failures during exchange return service unavailable. - The Policy API now rejects an unknown
policyTypequery parameter onlistPoliciesandgetApplicablePolicieswith HTTP 400. Previously an unrecognized value (for examplesystem.data_compaction, misspellingsystem.data-compactionwith an underscore) was silently treated as “no filter”, so the request returned policies of every type with HTTP 200, and clients could not tell a filtered result from an unfiltered one. An absent or emptypolicyTypestill means “no filter”, as the API specification allows. - File cleanup tasks now issue batched object-storage deletes again.
CatalogUtil.deleteFilesbatches only when theFileIOis aninstanceof SupportsBulkOperations, but theFileIOreaching the cleanup tasks is wrapped byExceptionMappingFileIOand, on Azure, byWasbTranslatingFileIO. Neither wrapper declared the capability held by the wrappedFileIO, so the check always failed and every file was deleted individually. Both wrappers now propagateSupportsBulkOperationswhen the wrappedFileIOsupports it, which affects every storage backend, sinceS3FileIO,GCSFileIO,ADLSFileIOandHadoopFileIOall implementDelegateFileIO. - Async task retries no longer fail with a
NullPointerExceptionwhen the task entity has already been dropped by a previous attempt. Such a retry is now recognized as an already-completed task and exits cleanly, instead of exhausting all retry attempts and logging aNullPointerExceptionon each one.