Apache Polaris 1.7.0
Released on August 2nd, 2026.
Downloads🔗
| Artifact | PGP Sig | SHA-512 |
|---|---|---|
| source tar.gz | .asc | .sha512 |
| binary tgz | .asc | .sha512 |
| binary zip | .asc | .sha512 |
| Spark 3.5 with Scala 2.12 Client Jar | .asc | .sha512 |
| Spark 3.5 with Scala 2.13 Client Jar | .asc | .sha512 |
Release Notes🔗
Upgrade notes🔗
- Relational JDBC: a new schema version 5 makes the
events.catalog_idcolumn nullable. Events that are not scoped to a catalog (principal, policy, rate-limiting, etc.) now persistNULLinstead of the placeholder string__realm__(which only ever existed in 1.6.0 release candidates). Fresh bootstraps use schema v5 automatically. Existing deployments on schema v3/v4 keep working — the server detects the older schema version and continues writing the legacy placeholder — but operators are encouraged to upgrade manually, since Polaris has no automated schema migrations:See the Relational JDBC metastore documentation for details.ALTER TABLE polaris_schema.events ALTER COLUMN catalog_id DROP NOT NULL; UPDATE polaris_schema.events SET catalog_id = NULL WHERE catalog_id = '__realm__'; UPDATE polaris_schema.version SET version_value = 5 WHERE version_key = 'version';
Breaking changes🔗
Removed the
--schema-version(-v) option from the admin tool’sbootstrapcommand. New realms are now always bootstrapped with the latest available schema version.The
MaintenanceService.performMaintenance()signature now requires an explicitOptionalLong overrideRunIdargument to supersede the latest unfinished maintenance run.Admin grant APIs now reject table-like privilege targets with an empty namespace. A table-like target without a namespace is considered invalid input.
PolarisPrincipalnow carries a genericMap<String, Object> attributesbag instead of the previousMap<String, String> propertiesandOptional<String> tokenfields. Three well-known attribute keys are defined as constants on the interface:PolarisPrincipal.PRINCIPAL_ENTITY_ATTRIBUTE_KEY: holds thePrincipalEntity;PolarisPrincipal.PRINCIPAL_ROLE_ALL_ATTRIBUTE_KEY: holds a boolean indicating if thePRINCIPAL_ROLE:ALLpseudo-role is present;PolarisPrincipal.JWT_ATTRIBUTE_KEY: holds the raw JWT string.
Use
PolarisPrincipal.getAttribute(key, type)for type-safe access. TheAuthenticatorinterface now accepts a QuarkusSecurityIdentityinstead of aPolarisCredential, and throwsAuthenticationFailedException(mapped to HTTP 401) instead of Iceberg’sNotAuthorizedException.Polaris does not include the
expiration-timeproperty anymore when vending credentials. This property is not consumed by any known client and duplicates the properties specific to each storage provider, such ass3.session-token-expires-at-msfor S3.
New Features🔗
- Added Kafka PolarisEventListener for publishing events to Kafka.
- Added GCS principal attribution for vended credentials (the GCP counterpart of AWS STS session tags). Set
GCS_PRINCIPAL_ATTRIBUTION_ENABLED=trueto activate; the feature flagsGCS_PRINCIPAL_ATTRIBUTION_WIF_AUDIENCE,GCS_PRINCIPAL_ATTRIBUTION_TOKEN_ISSUER, andGCS_PRINCIPAL_ATTRIBUTION_SIGNING_KEY_FILEare then required (a missing value is a fatal configuration error). Also requires agcpServiceAccounton the catalog StorageConfiguration. When enabled, credential vending chains a catalog-signed JWT through a Workload Identity Federation token exchange and service-account impersonation, so the Polaris principal appears in GCS Data Access audit logs (serviceAccountDelegationInfo.principalSubject) for any client.GCS_PRINCIPAL_ATTRIBUTION_SIGNING_KEY_IDsets the JWTkidfor JWKS key rotation. Attribution is keyed per-principal in the credential cache; when disabled (default), GCP vending behaviour is unchanged. - Added the
DEFAULT_UNIQUE_TABLE_LOCATION_ENABLEDfeature flag (off by default). When enabled, a managed location generated for a table or view created without an explicit location is given a unique, unpredictable suffix, so that no two tables share a path prefix. - Added the
ALLOW_CLIENT_SPECIFIED_TABLE_LOCATIONfeature flag (on by default). When set to false, a caller-specified location (thelocationfield, aSetLocationupdate, or thewrite.data.path/write.metadata.pathproperties) on a create-table (including a staged create-table request), create-view, update-table, replace-view, or commit-transaction request is rejected, forcing Polaris to manage all locations. Federated catalogs, committing an already staged create, andregister table/register vieware unaffected. - Added
maintenancesupport in Helm chart. - Python CLI: added
--catalog-urlto specify a custom base URL for the Iceberg REST Catalog (IRC) API. Allows use with deployments that map a path (e.g./server1) directly to the catalog root instead of the standard/api/catalog. See #4927. - Added support for publishing histogram buckets for HTTP server request duration as configured SLO boundaries.
- Added an OpenTelemetry event listener for emitting Polaris audit events as OpenTelemetry log records.
- Added optional
sessionPolicyfield toSigV4AuthenticationParametersfor catalog federation. When set, the IAM session policy JSON is attached to the STS AssumeRole request, allowing administrators to restrict vended credentials to only the required AWS services and actions (Principle of Least Privilege). - Python CLI: added
--schemeto specify URL scheme - Added opt-in idempotency for
createTableandupdateTablein the Iceberg REST catalog. When enabled viapolaris.idempotency.enabled=true(defaultfalse), a client-suppliedIdempotency-Keyheader is embedded into the table entity and committed in the same transaction as the operation; a retry carrying the same key within the TTL window (polaris.idempotency.ttl, defaultPT5M) replays the original success instead of failing — withAlreadyExistsforcreateTable, or withCommitFailedExceptionforupdateTablewhen the request’s requirements no longer match the already-advanced table. When idempotency is enabled, the reuse window is advertised to clients through theidempotency-key-lifetimefield of theGET /v1/configresponse.
Changes🔗
- The admin tool’s
bootstrapcommand is now idempotent: bootstrapping a realm that is already bootstrapped is reported as “Realm ‘’ is already bootstrapped; skipping.” and no longer fails the command, making automated bootstrap jobs safe to re-run. Correspondingly, PolarisMetaStoreManager.bootstrapPolarisServicenow returns a result withReturnStatus.ENTITY_ALREADY_EXISTSinstead of throwingIllegalArgumentExceptionwhen the root principal already exists. Existing credentials are never returned or altered. - Authorization failure messages (HTTP 403 /
ForbiddenExceptionfromPolarisAuthorizerImpl) now log the specific missing privilege(s) and the entity each was checked against server-side (atINFOlevel), e.g.missing TABLE_CREATE on NAMESPACE 'ns1'. The client-facing 403 response remains a generic message to avoid leaking authorization metadata to untrusted clients. Operators can correlate client errors to server logs using the existingX-Request-IDheader (present in default log MDC asrequestId). - The field
clientSecretof the Polaris management API typeResetPrincipalRequestis now usingformat: password. This does not change the wire format, but code generated from the OpenAPI may require downstream changes.
Deprecations🔗
- Deprecated
ALLOW_EXTERNAL_TABLE_LOCATION. UseALLOW_EXTERNAL_METADATA_FILE_LOCATIONfor external metadata file locations, including catalog configpolaris.config.allow.external.metadata.file.location.
Fixes🔗
- The NoSQL persistence commit log (
Commits.commitLog) no longer stops early when a commit’s recent-ancestor tail is shorter than the internal fetch page size. With apolaris.persistence.reference-previous-head-countsmaller than the page size, the natural-order commit log previously truncated at the first short tail because trailing null entries in the fetch page were treated as end-of-history, which could also drop still-referenced objects during maintenance. - Python CLI REPL now shows a clear “Syntax error” message for malformed input instead of a generic “unexpected error” message.
- Python CLI
setup applynow exits with an error after any setup operation fails, while still attempting the remaining operations. Previously, individual failures were logged but the command reported success and exited with status 0. - Python CLI
tables list,tables get, andtables deletecommands now exit with status 1 when catalog API requests fail. Previously, these commands printed an error but exited successfully. - Default table storage locations with object-storage prefixing enabled are now percent-encoded with UTF-8 instead of the JVM default charset. Previously the persisted location of a table under a non-ASCII namespace or table name depended on the platform default charset, so the same table could resolve to a different (or lossy) location on a non-UTF-8 JVM.
- Async task execution (table cleanup, manifest and batch file cleanup) now retries when a handler returns false on transient errors (e.g. IO or delete failures). Previously
falsewas swallowed with only a warning log and the task was never retried via the existing retry mechanism. TableCleanupTaskHandlernow clampsTABLE_METADATA_CLEANUP_BATCH_SIZEto at least 1. Previously a non-positive realm override caused an infinite loop (0) orIllegalArgumentException(<0) when splitting metadata files for cleanup.ManifestFileCleanupTaskHandlernow handles Iceberg v2 delete manifests in addition to data manifests. Previously,DROP TABLE PURGEon a v2 table that had been updated via merge-on-read DML left position-delete files and their manifests as orphans in object storage; the cleanup task failed silently becauseManifestFiles.read()rejects delete manifests.- OPA authorizer now includes the realm identifier in the authorization context sent to OPA (
input.context.realm). This ensures OPA policies can enforce tenant isolation across realms, preventing potential collisions if identical principal or resource names exist in different realms. - Management API delete operations for principals, principal roles, catalog roles, and catalogs now return error messages that match the actual failure reason (for example, concurrent modification no longer reports a misleading protected-entity message).
- Python CLI
setup applynow defaults to anINTERNALcatalog type when thetypefield is left blank or null in the setup config, instead of crashing withAttributeError