include::{mod-loc}shared/all-attributes.adoc[]

:_mod-docs-content-type: ASSEMBLY
[id="registry-migration_{context}"]
= Migrating from {registry} {registry-v2} to {registry} 3.x

[role="_abstract"]
You can migrate from {registry} {registry-v2} to {registry} 3.x by deploying a new 3.x instance, exporting and importing your persisted data, and updating your client applications.

{registry} 3.x includes breaking changes that require a new deployment, data migration, and client application updates. There is no in-place upgrade path from v2 to v3.

NOTE: For general versioning policy, upgrade guidance within the 3.x series, and rollback procedures, see xref:versioning-support-policy_{context}[].

* xref:registry-migration-overview_{context}[]
* xref:migration-planning_{context}[]
* xref:migrating-minimal-downtime_{context}[]
* xref:migrating-maintenance-window_{context}[]
* xref:migrating-registry-data_{context}[]
* xref:migrating-registry-applications_{context}[]
* xref:migrating-kafka-serdes_{context}[]
* xref:migrating-rest-api-clients_{context}[]
* xref:client-migration-reference_{context}[]
* xref:migrating-registry-configuration_{context}[]
* xref:kafkasql-architecture_{context}[]
* xref:configuring-kafkasql-topic_{context}[]
* xref:verifying-migration-configuration_{context}[]
* xref:resolving-configuration-issues_{context}[]
* xref:user-interface_{context}[]
* xref:deploying-registry-ui_{context}[]
* xref:ui-configuration-properties_{context}[]
* xref:migration-scripts_{context}[]
* xref:troubleshooting-migration_{context}[]


// con-registry-migration-overview
:_mod-docs-content-type: CONCEPT
[id="registry-migration-overview_{context}"]
== Changes in {registry} 3.x

[role="_abstract"]
{registry} 3.x introduces a redesigned data model, a reworked REST API, and a refreshed SDK ecosystem. These changes require you to deploy a new 3.x instance, migrate your persisted content, and update your client applications and automation.

When you migrate to version 3.x, consider the following changes:

Updated data model and governance::
+
--
{registry} 3.x separates artifact metadata from version metadata and introduces first-class support for empty artifacts (artifacts without versions). You can prepare governance metadata, rules, and branch structures before you add the first version. Additional highlights include:

* You manage artifact metadata independently from version metadata.
* Groups and artifacts support custom labels for improved categorization.
* You can create and manage custom branches. The system manages the `latest` branch automatically.
--

Core v3 REST API::
+
--
The v3 REST API focuses on consistency and discoverability:

* Group management covers descriptions, owners, labels, and hierarchical rules.
* New search endpoints at `/search/groups` and `/search/versions` expand discovery.
* Version-focused endpoints streamline content management and require explicit `Content-Type` headers.
* A `dryRun` query parameter on artifact and version creation endpoints replaces the former `/test` endpoints. When `dryRun=true`, the API validates the request and returns a response without committing changes.
* A dedicated Branch API enables multitrack artifact development.
--


Re-engineered Kafka storage::
The KafkaSQL storage implementation optimizes stability and maintainability. The redesigned architecture reduces startup times for large installations.

Architectural changes in v3::
KafkaSQL storage uses a true event journaling pattern that requires a different Kafka topic than v2, with different configuration settings. The v3 journal format is incompatible with v2.

Separate UI container::
You deploy the user interface as a separate container, which provides more deployment flexibility and enables independent scaling of UI and API components.

Generated SDKs powered by Kiota::
{registry} 3.x ships Kiota-generated SDKs that mirror the REST API structure across languages. Updated libraries for Java, TypeScript, Python, and Go provide lightweight, type-safe clients with minimal dependencies and consistent naming.


// con-migration-planning
:_mod-docs-content-type: CONCEPT
[id="migration-planning_{context}"]
== Migration planning

[role="_abstract"]
You can choose from two migration approaches depending on your architecture and availability requirements. Review both approaches and select the one that best fits your environment.

[cols="1,2,2", options="header"]
|===
| Feature | Minimal downtime migration | Maintenance window migration

| *Primary method*
| Parallel environments with reverse proxy or load balancer routing
| Sequential process

| *Complexity*
| *High*: Requires configuration for dual-routing and traffic management.
| *Low*: Straightforward, linear execution steps.

| *Read downtime*
| 10-30 seconds (switchover latency)
| 1-2 hours (full duration of maintenance)

| *Write downtime*
| 30-45 minutes (final sync and import phase)
| 1-2 hours (full duration of maintenance)

| *Infrastructure*
| Requires Nginx or HAProxy and dual running instances.
| Single target environment.

| *Best for*
| High-availability production environments.
| Development, staging, or systems with flexible SLAs.
|===

Regardless of the migration path you choose, you must:

* Test the data migration in a staging environment before you migrate production. A dry run helps you estimate timing for export and import phases. See xref:migrating-registry-data_{context}[Migrating {registry} data].
* Verify that client applications connect to {registry} 3.x after the data migration completes. For minimal downtime migrations, verify that the load balancer routes to 3.x and update any clients that bypass the load balancer. For maintenance window migrations, update client configurations directly. See xref:migrating-registry-applications_{context}[Migrating {registry} client applications].
* Update configuration properties for the v3 deployment. Combine this step with client updates where possible. See xref:migrating-registry-configuration_{context}[Updating {registry} configuration].

Minimal downtime migration::
The objective is to keep the service reachable. While data moves between versions, a reverse proxy (such as nginx) manages the cutover. See xref:migrating-minimal-downtime_{context}[Migrating with minimal downtime].

Maintenance window migration::
This approach provides the cleanest data integrity. By stopping all client applications first, you ensure that no data is in flight during the move. See xref:migrating-maintenance-window_{context}[Migrating in a maintenance window].


// proc-migrating-minimal-downtime
:_mod-docs-content-type: PROCEDURE
[id="migrating-minimal-downtime_{context}"]
== Migrating with minimal downtime

[role="_abstract"]
You can migrate from {registry} {registry-v2} to 3.x with minimal downtime by using a load balancer to manage traffic routing. This phase-based workflow minimizes read-operation downtime and controls write operations during data export.

[literal]
....
┌────────────────────────────────────────────────────────────────┐
│                     Client Applications                        │
└───────────────────────────┬────────────────────────────────────┘
                            │
                            ↓
                    ┌───────────────┐
                    │ Load Balancer │  (such as nginx or HAProxy)
                    └───────┬───────┘
                            │
        ┌───────────────────┼───────────────────┐
        │                   │                   │
        ↓                   ↓                   ↓
┌───────────────┐   ┌───────────────┐   ┌───────────────┐
│  Registry 2.x │   │  Registry 2.x │   │  Registry 3.x │
│   + Storage   │   │   + Storage   │   │   + Storage   │
└───────────────┘   └───────────────┘   └───────────────┘
    (read/write)        (read-only)         (read/write)
....

During a minimal downtime migration, you temporarily run both {registry} {registry-v2} and {registry} 3.x simultaneously. Plan for the following resources:

Infrastructure:

* Separate database instances for v2 and v3 (or different schemas in the same database)
* For KafkaSQL deployments: the same Kafka cluster with different topic names
* Load balancer capacity for routing traffic
* Network connectivity between all components

Storage requirements:

* v2 database or storage: existing size
* v3 database or storage: approximately the same size as v2 (plan for 110% of the v2 size)
* Export file storage: approximately 10-20% of the database size (compressed)

Timeline:

* Keep v2 running for 1-7 days after migration for rollback capability
* Total parallel operation period: 1-7 days
* Budget for staging environment testing before production migration

.Prerequisites

[cols="1,3",options="header"]
|===
| Requirement | Description

| Staging environment
| A staging environment to test the complete migration workflow before production.

| Load balancer
| nginx, HAProxy, or a similar tool for traffic management.

| Administrative access
| Admin role credentials for export and import API endpoints and for testing admin functionality.

| Database or storage
| A separate database instance or schema (or a separate Kafka topic) for {registry} 3.x.

| Backup
| A current backup of {registry-v2} data.

| Rollback plan
| A documented procedure to revert to v2 if needed.
|===




NOTE: Test as much of the migration as possible in a staging environment before you migrate in production.

.Procedure

*Phase 1: Preparation*

.  Deploy the load balancer.
+
--
If you do not already use a load balancer, deploy nginx or HAProxy in front of your existing {registry-v2} deployment:

.. Deploy the load balancer.
.. Configure routing to {registry-v2}.
.. Update client applications to connect through the load balancer.
.. Validate that all traffic flows correctly.

See xref:migrating-registry-configuration_{context}[Updating registry configuration] for load balancer configuration examples.
--

.  Prepare the {registry} 3.x infrastructure.
+
--
Deploy the infrastructure for {registry} 3.x without starting the registry application:

* *For SQL storage:* Deploy a separate database instance or create a separate schema.
* *For KafkaSQL storage:* Use the same Kafka cluster but configure a different topic name.
* Prepare TLS certificates. You can reuse {registry-v2} certificates.
* Configure the OAuth2/OIDC authentication provider.

See xref:migrating-registry-configuration_{context}[Updating registry configuration] for detailed configuration changes.

*Checkpoint:* v2 traffic flows through the load balancer and v3 infrastructure is ready.
--


*Phase 2: Pre-migration validation*

[start=3]
.  Enable read-only mode.
+
--
Configure the load balancer to block write operations while it allows read operations. This prevents data changes during export.

Update your load balancer configuration to block POST, PUT, PATCH, and DELETE methods:

[source,nginx]
----
# nginx read-only configuration
server {
    location / {
        if ($request_method !~ ^(GET|HEAD|OPTIONS)$) {
            return 405 "Registry temporarily in read-only mode for migration";
        }
        proxy_pass http://registry-v2:8080;
    }
}
----

Reload the load balancer configuration and verify that read-only mode works correctly.

*Impact:* Read operations (schema lookups) continue normally. Write operations (new schemas, updates) return HTTP 405. Kafka producers with auto-registration enabled cannot register new schemas.

--

.  Capture baseline metrics.
+
--
Record the current state of your {registry-v2} deployment for comparison after migration.

For the metrics capture procedure, see xref:migration-scripts_{context}[Migration scripts reference].

*Checkpoint:* Read-only mode is active and write operations are blocked. Baseline is documented and ready for data migration.
--

*Phase 3: Data migration*

[start=5]
.  Export data from {registry-v2}.
+
--
Export all artifacts, versions, metadata, and rules from {registry-v2} while the registry is in read-only mode.

For the export procedure, see xref:migrating-registry-data_{context}[Migrating {registry} data].

IMPORTANT: Access the registry directly (not through the load balancer) to bypass read-only restrictions on admin endpoints if needed.

*Checkpoint:* Export is complete and the data snapshot is captured.
--
.  Deploy {registry} 3.x.
+
--
Deploy {registry} 3.x by using the infrastructure that you prepared in Step 2. Configure the v3 deployment similarly to the previous v2 deployment. Note that various configuration parameters are renamed or changed.

See xref:migrating-registry-configuration_{context}[Updating registry configuration] for v3 configuration information and examples.

*Checkpoint:* {registry} 3.x is deployed and healthy (empty, awaiting import).
--
.  Import data into {registry} 3.x.
+
--
Import the exported ZIP archive into the 3.x deployment.

For the import procedure, see xref:migrating-registry-data_{context}[Migrating {registry} data].

NOTE: The import adds to existing data and does not replace it. For best results, import into an empty v3 registry.

*Checkpoint:* Import is complete and ready for validation.
--

.  Validate the import.
+
--
Verify the {registry} 3.x deployment and confirm that the data migration succeeded.

For the validation procedure, see xref:migrating-registry-data_{context}[Migrating {registry} data].

If validation fails, investigate import logs and retry if necessary. Do not proceed to the traffic switch until all checks pass.

*Checkpoint:* v3 has all data, data metrics match v2, and the deployment is ready for traffic switch.
--

*Phase 4: Traffic switch*

[start=9]
.  Switch the load balancer to {registry} 3.x.
+
--
Update the load balancer configuration to route to {registry} 3.x and enable read and write operations. The following example shows an nginx configuration:

[source,nginx]
----
# nginx v3 configuration (read/write enabled)
upstream registry {
    server registry-v3:8080;
}

server {
    location / {
        # All methods allowed
        proxy_pass http://registry;
        proxy_set_header Host $host;
    }
}
----

Reload the load balancer.

*Service interruption:* This operation can cause 10-30 seconds of brief service interruption during the reload. If your load balancer supports graceful reloads (for example, `nginx -s reload`), the interruption is minimal.

*Checkpoint:* All traffic routes to v3 and write operations are resumed.

--

.  Verify traffic to the v3 deployment.
+
--
Confirm that traffic flows to {registry} 3.x through the load balancer. Verify that the registry is accessible, the version is 3.x, health checks pass, and write operations are enabled.

For the verification procedure, see xref:migration-scripts_{context}[Migration scripts reference].

*Checkpoint:* v3 is accessible, write operations work, and no errors appear in the logs.
--

*Phase 5: Post-migration validation*

[start=11]
.  Test v2 API backward compatibility.
+
--
Verify that existing v2 clients can access the 3.x registry through the v2 API compatibility layer.

For backward compatibility details and client migration guidance, see xref:migrating-registry-applications_{context}[Migrating {registry} client applications].

*Checkpoint:* v2 clients work correctly against the v3 registry.
--

.  Test v3 Core API functionality.
+
--
Verify that v3-specific API features work correctly, including system endpoints, search endpoints, admin endpoints, and the Branch API.

For the v3 API verification procedure, see xref:migration-scripts_{context}[Migration scripts reference].

*Checkpoint:* v3 Core API features work correctly.
--

.  Run smoke tests.
+
--
Execute production smoke tests to validate critical functionality:

.. Test critical client applications.
.. Verify that Kafka SerDes applications work (if applicable).
.. Check authentication and authorization.
.. Monitor error rates and performance metrics.
.. Validate that no increase in 4xx/5xx errors occurs.

*Checkpoint:* All smoke tests passed and production traffic is stable.
--

*Phase 6: Stabilization*

[start=14]
.  Monitor and observe.
+
--
Keep {registry-v2} running but not serving traffic for rollback capability:

* Monitor {registry} 3.x for 24-48 hours minimum.
* Watch for error rates, performance issues, or unexpected behavior.
* Prepare for immediate rollback if critical issues arise by switching the load balancer back to v2.
* Begin planning gradual client application migration to v3 SDKs.

See xref:migrating-registry-applications_{context}[Migrating registry client applications] for guidance on updating client applications.

*Checkpoint:* v3 is stable for 1-7 days and no rollback is needed.
--

*Phase 7: Cleanup*

[start=15]
.  Decommission {registry-v2}.
+
--
After 1-7 days of successful operation on {registry} 3.x, decommission v2 infrastructure.

Update documentation to reflect v3 as the production registry.

*Checkpoint:* Migration is complete and v2 is decommissioned.
--

.Verification

After you complete all phases, verify the following:

* All client applications connect to {registry} 3.x successfully.
* No increase in error rates compared to the v2 baseline.
* Artifact counts and global rules match the pre-migration baseline.

A key advantage of the parallel deployment approach is the ability to roll back quickly if issues arise.

.Rolling back to {registry-v2}
[cols="1,2,2", options="header"]
|===
| Scenario
| Description
| Considerations

| Immediate rollback (within hours of migration)
a|
* Switch load balancer traffic back to {registry-v2}.
* Investigate issues with the 3.x deployment.
a|
* Recovery time: 30-60 seconds.
* Minimal operational impact.

| Delayed rollback (days after migration)
a|
* Assess data that was created in 3.x since migration.
* Decide on a rollback approach.
a|
* Options include:
** Accepting data loss and rolling back to v2.
** Fixing issues and remaining on v3 (preferred).

| Preventing rollback scenarios
a|
* Test the migration thoroughly in a staging environment.
* Validate data integrity before you switch production traffic.
* Run comprehensive smoke tests after migration.
| Reduces the likelihood that a rollback is required.
|===


// proc-migrating-maintenance-window
:_mod-docs-content-type: PROCEDURE
[id="migrating-maintenance-window_{context}"]
== Migrating within a maintenance window

[role="_abstract"]
For environments where downtime is acceptable, you can follow this simpler sequential process to migrate from {registry} {registry-v2} to 3.x.

.Prerequisites

* You performed a dry run migration in a staging environment to estimate the timing of the data export and import phases accurately.

.Procedure

. Stop all client applications that connect to {registry} {registry-v2}.
. Export data from {registry} {registry-v2} by using the export script. See xref:migration-scripts_{context}[Migration scripts reference].
. Shut down {registry} {registry-v2}.
. Deploy {registry} 3.x with updated configuration. See xref:migrating-registry-configuration_{context}[Updating registry configuration].
. Import data into {registry} 3.x by using the import script. See xref:migration-scripts_{context}[Migration scripts reference].
. Validate the import by using the verification scripts. See xref:migration-scripts_{context}[Migration scripts reference].
. Update client application configurations to point to {registry} 3.x. See xref:migrating-registry-applications_{context}[Migrating {registry} client applications].
. Start client applications.

.Verification

* Verify that all client applications connect to {registry} 3.x successfully.
* Verify that artifact counts match the pre-migration baseline.
* Verify that no increase in error rates occurs.


// proc-migrating-registry-data
:_mod-docs-content-type: PROCEDURE
[id="migrating-registry-data_{context}"]
== Migrating {registry} data

[role="_abstract"]
You can use the export and import APIs to migrate persisted content from {registry} {registry-v2} to a new {registry} 3.x deployment. The APIs preserve artifact identifiers, metadata, and references, which ensures that downstream Kafka payloads remain compatible.

IMPORTANT: You must use the export and import procedure for migration. {registry} 3.x cannot directly read {registry-v2} storage because the underlying data structures changed significantly:

* *SQL storage:* The v3 database schema is completely redesigned and incompatible with v2. The data model changes (separate artifact and version metadata, new branch support) require a different table structure.
* *KafkaSQL storage:* The v3 Kafka journal topic uses a new event journaling format that is incompatible with v2. You cannot reuse the v2 topic.

Export data from v2 and import it into v3 by using the export and import APIs. The import process automatically transforms the data to the v3 format.

{registry} 3.x accepts the export archive that is generated from a {registry-v2} instance. Capture the archive from your existing deployment, provision the 3.x environment, and import the archive into the new cluster.

.Prerequisites

* The source {registry} {registry-v2} server and the destination 3.x server are both running.
* You have administrative credentials that allow access to the `/apis/registry/v2/admin/export` and `/apis/registry/v3/admin/import` endpoints.
* If you use OAuth2 authentication, you have valid client credentials with the admin role (for example, `sr-admin`).

.Procedure

The migration scripts automate the export, import, and validation process. They support OAuth2 authentication and provide detailed error handling.

. Export all data from {registry} {registry-v2} by using the export script:
+
[source,bash]
----
./apicurio-export-registry-data.sh
----
+
The script performs the following actions:
+
* Connects to your {registry-v2} deployment.
* Exports all artifacts, versions, metadata, and rules.
* Saves the data to a ZIP file (default: `registry-export.zip`).
* Validates the export file format and size.
* Reports export duration and file details.
+
For detailed configuration options and OAuth2 authentication setup, see xref:migration-scripts_{context}[Migration scripts reference].

. Import the export archive into {registry} 3.x by using the import script:
+
[source,bash]
----
./apicurio-import-registry-data.sh
----
+
The script performs the following actions:
+
* Validates that the import ZIP file exists and is valid.
* Checks {registry} 3.x accessibility and version.
* Imports all data from the export file.
* Verifies artifact counts before and after import.
* Reports global rules configuration.
+
For detailed configuration options, see xref:migration-scripts_{context}[Migration scripts reference].

. Validate the imported data by using the verification script:
+
[source,bash]
----
./apicurio-verify-registry-version.sh
----
+
The script performs the following validation:
+
* Health endpoint verification.
* System information and version check.
* Artifact count comparison.
* Global rules validation.
* v2 API backward compatibility check (optional).
+
For detailed configuration options, see xref:migration-scripts_{context}[Migration scripts reference].

.Verification

* Verify that artifact counts in v3 match the v2 baseline.
* Verify that global rules are configured correctly in v3.
* Verify that sample artifacts contain the expected content and metadata.

[role="_additional-resources"]
.Additional resources
* {LinkServiceRegistryUser}[{NameServiceRegistryUser}]


// con-client-backward-compatibility
:_mod-docs-content-type: CONCEPT
[id="migrating-registry-applications_{context}"]
== Client application backward compatibility

[role="_abstract"]
{registry} 3.x maintains backward compatibility with v2 client libraries and SerDes. You can continue to use v2 clients with a v3 registry during your migration period, which allows you to update clients gradually.

{registry} 3.x provides the following backward compatibility for existing v2 clients:

* *v2 REST API:* remains available at `/apis/registry/v2`.
* *v2 SerDes libraries:* continue to work with the v3 registry.
* *v3 SerDes:* can consume v2 client Kafka messages with proper configuration.
* *No immediate client updates required:* you can update clients gradually during the migration period.

This backward compatibility enables you to:

. Migrate the registry to v3 first.
. Test with existing v2 clients.
. Gradually update clients to v3 libraries over time.

[id="choosing-migration-strategy_{context}"]

[role="_abstract"]
You can choose from two client migration strategies.

Gradual migration::
Migrate the registry first, then update clients incrementally. Deploy {registry} 3.x, verify that v2 clients continue to work, update high-priority clients to v3 SerDes or SDKs, gradually migrate remaining clients, and deprecate v2 API access after you update all clients. This approach provides lower risk because clients continue to work during migration, a flexible timeline for client updates, and easier rollback if issues occur.

Big-bang migration::
Update all clients and the registry simultaneously. Prepare all client updates in advance, deploy {registry} 3.x, deploy updated client applications, and verify that all integrations work. This approach provides a clean cutover to v3 with no mixed v2/v3 client environment, but requires higher complexity and coordination and is more difficult to troubleshoot if issues arise.

[role="_additional-resources"]
.Additional resources
* xref:migrating-kafka-serdes_{context}[]
* xref:migrating-rest-api-clients_{context}[]
* xref:client-migration-reference_{context}[]


// proc-migrating-kafka-serdes
:_mod-docs-content-type: PROCEDURE
[id="migrating-kafka-serdes_{context}"]
== Migrating Kafka SerDes applications

[role="_abstract"]
You can update your Kafka SerDes applications from {registry} {registry-v2} to 3.x by updating Maven dependencies, reconfiguring SerDes properties, and updating TLS settings.

.Prerequisites

* You have existing Kafka SerDes applications that use {registry} {registry-v2} libraries.
* You have access to the source code and build configuration for each application.
* You installed the required dependencies for running migration scripts. See xref:installing-script-dependencies_{context}[Installing dependencies].

.Procedure

. Update your Maven dependency coordinates from v2 to v3.
+
Replace the v2 SerDes dependency:
+
[source,xml]
----
<dependency>
    <groupId>io.apicurio</groupId>
    <artifactId>apicurio-registry-serdes-avro-serde</artifactId>
    <version>2.6.13.Final</version>
</dependency>
----
+
With the v3 SerDes dependency:
+
[source,xml,subs="+attributes"]
----
<dependency>
    <groupId>io.apicurio</groupId>
    <artifactId>apicurio-registry-avro-serde-kafka</artifactId>
    <version>{registry-release}</version>
</dependency>
----
+
NOTE: Artifact IDs changed in v3. The v2 artifact ID is `apicurio-registry-serdes-avro-serde`, while v3 uses `apicurio-registry-avro-serde-kafka`.

. Update your SerDes configuration properties for v3.
+
Replace the v2 configuration:
+
[source,java]
----
Properties props = new Properties();

// v2 SerDes configuration
props.put(SerdeConfig.REGISTRY_URL, "https://registry-v2.example.com/apis/registry/v2");
props.put(SerdeConfig.AUTO_REGISTER_ARTIFACT, Boolean.TRUE);
props.put(SerdeConfig.ENABLE_HEADERS, false);
props.put(SerdeConfig.ENABLE_CONFLUENT_ID_HANDLER, true);
props.put(SerdeConfig.USE_ID, IdOption.contentId.name());

// v2 OAuth2 configuration
props.put(SerdeConfig.AUTH_SERVICE_URL, "https://keycloak.example.com");
props.put(SerdeConfig.AUTH_REALM, "registry");
props.put(SerdeConfig.AUTH_CLIENT_ID, "kafka-client");
props.put(SerdeConfig.AUTH_CLIENT_SECRET, "secret");
----
+
With the v3 configuration:
+
[source,java]
----
Properties props = new Properties();

// v3 SerDes configuration
props.put(SerdeConfig.REGISTRY_URL, "https://registry-v3.example.com/apis/registry/v3");
props.put(SerdeConfig.AUTO_REGISTER_ARTIFACT, Boolean.TRUE);

// v3 OAuth2 configuration (simplified)
props.put(SerdeConfig.AUTH_TOKEN_ENDPOINT, "https://keycloak.example.com/realms/registry/protocol/openid-connect/token");
props.put(SerdeConfig.AUTH_CLIENT_ID, "kafka-client");
props.put(SerdeConfig.AUTH_CLIENT_SECRET, "secret");
----
+
For a detailed comparison of all changed properties, see xref:client-migration-reference_{context}[Client migration configuration reference].

. Update your TLS configuration.
+
Replace the v2 system-property-based TLS configuration:
+
[source,java]
----
// v2 SerDes clients use system properties for TLS configuration
System.setProperty("javax.net.ssl.trustStore", "/path/to/truststore.jks");
System.setProperty("javax.net.ssl.trustStorePassword", "password");
----
+
With the v3 SerdeConfig-based configuration:
+
[source,java]
----
// v3 SerDes clients use SerdeConfig properties for TLS configuration
Properties props = new Properties();

// Configure trust store for Registry HTTPS connections
props.put(SerdeConfig.TLS_TRUSTSTORE_LOCATION, "/path/to/truststore.jks");
props.put(SerdeConfig.TLS_TRUSTSTORE_PASSWORD, "password");
props.put(SerdeConfig.TLS_TRUSTSTORE_TYPE, "JKS");

// System properties for OAuth2 Keycloak connections
System.setProperty("javax.net.ssl.trustStore", "/path/to/truststore.jks");
System.setProperty("javax.net.ssl.trustStorePassword", "password");
----
+
IMPORTANT: v3 SerDes requires both SerdeConfig TLS properties (for Registry SDK connections) and system properties (for OAuth2 Keycloak connections). This differs from v2, which uses only system properties.

.Verification

* Verify that v3 consumers read messages that v2 producers produced.
* Verify that v2 consumers read messages that v3 producers produced.
* Verify that mixed-environment deployments (v2 and v3 clients) operate without data loss.

[role="_additional-resources"]
.Additional resources
* xref:client-migration-reference_{context}[]
* {LinkServiceRegistryUser}[{NameServiceRegistryUser}]


// proc-migrating-rest-api-clients
:_mod-docs-content-type: PROCEDURE
[id="migrating-rest-api-clients_{context}"]
== Migrating REST API and SDK client applications

[role="_abstract"]
You can update applications that use the {registry} REST API directly by updating SDK dependencies, reconfiguring TLS settings, and updating OAuth2/OIDC authentication.

.Prerequisites

* You have existing applications that use the {registry} {registry-v2} REST API or Java SDK.
* You have access to the source code or deployment configuration for each application.

.Procedure

. Update your SDK Maven dependency coordinates from v2 to v3.
+
Replace the v2 SDK dependency:
+
[source,xml]
----
<dependency>
    <groupId>io.apicurio</groupId>
    <artifactId>apicurio-registry-client</artifactId>
    <version>2.6.13.Final</version>
</dependency>
----
+
With the v3 SDK dependency:
+
[source,xml,subs="+attributes"]
----
<dependency>
    <groupId>io.apicurio</groupId>
    <artifactId>apicurio-registry-java-sdk</artifactId>
    <version>{registry-release}</version>
</dependency>
----

. Update your TLS configuration.
+
Replace the v2 system-property-based TLS configuration:
+
[source,java]
----
// v2 clients use system properties
System.setProperty("javax.net.ssl.trustStore", "/path/to/truststore.jks");
System.setProperty("javax.net.ssl.trustStorePassword", "password");
----
+
With the v3 `RegistryClientOptions`-based configuration:
+
[source,java]
----
// v3 clients use RegistryClientOptions
RegistryClientOptions options = RegistryClientOptions.builder()
    .trustStore(new File("/path/to/truststore.jks"))
    .trustStorePassword("password")
    .build();

props.put(SerdeConfig.REGISTRY_CLIENT_OPTIONS, options);
----

. Update your OAuth2/OIDC authentication configuration.
+
Replace the v2 authentication setup:
+
[source,java]
----
import io.apicurio.rest.client.auth.OidcAuth;
import io.apicurio.rest.client.spi.ApicurioHttpClientFactory;

String authServerUrl = "https://keycloak.example.com/realms/registry";
ApicurioHttpClient httpClient = ApicurioHttpClientFactory.create(authServerUrl,
    new AuthErrorHandler());
Auth auth = new OidcAuth(httpClient, clientId, clientSecret, null, null);
RegistryClient client = RegistryClientFactory.create(registryUrl, Collections.emptyMap(), auth);
----
+
With the v3 authentication setup:
+
[source,java]
----
import io.apicurio.registry.client.RegistryClientFactory;
import io.apicurio.registry.client.RegistryClientOptions;

String tokenEndpoint = "https://keycloak.example.com/realms/registry/protocol/openid-connect/token";
RegistryClientOptions options = RegistryClientOptions.create(registryUrl)
    .oauth2(tokenEndpoint, clientId, clientSecret, null);
RegistryClient client = RegistryClientFactory.create(options);
----
+
For a detailed comparison of all authentication changes, see xref:client-migration-reference_{context}[Client migration configuration reference].

.Verification

* Verify that REST API calls to v3 endpoints return expected results.
* Verify that authenticated requests succeed with the new OAuth2 configuration.
* Verify that v2 API backward compatibility endpoints respond correctly.

[role="_additional-resources"]
.Additional resources
* xref:client-migration-reference_{context}[]
* {LinkServiceRegistryUser}[{NameServiceRegistryUser}]
* https://docs.redhat.com/en/documentation/red_hat_build_of_apicurio_registry/3.1/html/apicurio_registry_user_guide/configuring-kafka-client-serdes_registry[Kafka SerDes Configuration Guide]
* https://learn.microsoft.com/en-us/openapi/kiota/[Kiota SDK Documentation]


// ref-client-migration-reference
:_mod-docs-content-type: REFERENCE
[id="client-migration-reference_{context}"]
== Client migration configuration reference

[role="_abstract"]
The following tables summarize the configuration property changes between {registry} {registry-v2} and {registry} 3.x for SerDes applications, REST API clients, and authentication.

.SerDes configuration property changes

[cols="1,1,1",options="header"]
|===
| Property
| Registry 2.x
| Registry 3.x

| OAuth2 endpoint
| `AUTH_SERVICE_URL` + `AUTH_REALM`
| `AUTH_TOKEN_ENDPOINT` (full URL)

| ID handler
| `ID_HANDLER` (optional default "8 byte handler")
| `ID_HANDLER` (optional default "4 byte handler")

| Confluent ID handler
| `ENABLE_CONFLUENT_ID_HANDLER` (optional convenience property)
| Removed

| Content ID usage
| `USE_ID` with `IdOption` (default "globalId")
| `USE_ID` with `IdOption` (default "contentId")
|===

NOTE: The default ID option changed from `globalId` to `contentId` in v3. Both v2 and v3 SerDes can read messages that use either ID type, which ensures compatibility during migration. If your v2 applications explicitly set `USE_ID=contentId`, you can remove this configuration in v3 because it is now the default.

.REST API client authentication changes

[cols="1,1,1",options="header"]
|===
| Aspect
| Registry 2.x
| Registry 3.x

| Configuration approach
| Separate `Auth` object with `OidcAuth`
| Fluent builder with `RegistryClientOptions`

| OAuth2 endpoint
| Full auth server URL with realm path
| Full OIDC token endpoint URL

| Factory method
| `RegistryClientFactory.create(url, map, auth)`
| `RegistryClientFactory.create(options)`
|===

With proper configuration, {registry} 3.x SerDes reads messages that v2 SerDes serialized:

* *v3 consumers* read messages that v2 producers produce.
* *v2 consumers* read messages that v3 producers produce.
* *Mixed environment* (v2 and v3 clients) is fully supported.

This compatibility helps you avoid data loss during migration.

[role="_additional-resources"]
.Additional resources
* {LinkServiceRegistryUser}[{NameServiceRegistryUser}]
* https://docs.redhat.com/en/documentation/red_hat_build_of_apicurio_registry/3.1/html/apicurio_registry_user_guide/configuring-kafka-client-serdes_registry[Kafka SerDes Configuration Guide]


// ref-configuration-property-changes
:_mod-docs-content-type: REFERENCE
[id="migrating-registry-configuration_{context}"]
== Configuration property changes

[role="_abstract"]
Configuration keys in {registry} 3.x adopt a unified `apicurio.` namespace and reorganize authentication, eventing, UI, and storage settings. The following tables map v2 properties to their v3 equivalents.

IMPORTANT: Configuration property changes are extensive in 3.x. Review all sections carefully and test in a staging environment before you migrate production deployments.

[id="configuring-storage_{context}"]

[role="_abstract"]
You must use separate database instances or schemas for v2 and v3 because the database schemas are incompatible.

[cols="1,1",options="header"]
|===
| Registry 2.x property
| Registry 3.x property

| `REGISTRY_DATASOURCE_URL`
| `APICURIO_DATASOURCE_URL`

| `REGISTRY_DATASOURCE_USERNAME`
| `APICURIO_DATASOURCE_USERNAME`

| `REGISTRY_DATASOURCE_PASSWORD`
| `APICURIO_DATASOURCE_PASSWORD`

| `QUARKUS_DATASOURCE_DB_KIND`
| Not required (auto-detected from URL)

| `REGISTRY_SQL_INIT`
| `APICURIO_SQL_INIT`

| Not applicable
| `APICURIO_STORAGE_KIND=sql` (required)

| Not applicable
| `APICURIO_STORAGE_SQL_KIND` (PostgreSQL, MySQL, H2)
|===

*Example migration:*

[source,properties]
----
# Registry 2.x SQL configuration
REGISTRY_DATASOURCE_URL=jdbc:postgresql://postgres:5432/registry
REGISTRY_DATASOURCE_USERNAME=apicurio
REGISTRY_DATASOURCE_PASSWORD=password123
QUARKUS_DATASOURCE_DB_KIND=postgresql

# Registry 3.x SQL configuration
APICURIO_STORAGE_KIND=sql
APICURIO_STORAGE_SQL_KIND=postgresql
APICURIO_DATASOURCE_URL=jdbc:postgresql://postgres:5432/registry_v3
APICURIO_DATASOURCE_USERNAME=apicurio
APICURIO_DATASOURCE_PASSWORD=password123
----

IMPORTANT: Use separate database instances or different database names for v2 and v3. The v3 database schema is incompatible with v2.

You must use a different Kafka topic for v3 than v2 because the journal formats are incompatible.

.KafkaSQL storage properties
[cols="1,1",options="header"]
|===
| Registry 2.x property
| Registry 3.x property

| `KAFKA_BOOTSTRAP_SERVERS`
| `APICURIO_KAFKASQL_BOOTSTRAP_SERVERS`

| `REGISTRY_KAFKASQL_TOPIC`
| `APICURIO_KAFKASQL_TOPIC`

| `REGISTRY_KAFKASQL_CONSUMER_GROUP_ID`
| Not applicable. Remove this property. In v3, the consumer group ID is always auto-generated. Use
`APICURIO_KAFKASQL_CONSUMER_GROUP_PREFIX` to set a recognizable prefix if needed.

| Not applicable
| `APICURIO_STORAGE_KIND=kafkasql` (required)
|===

*Example migration:*

[source,properties]
----
# Registry 2.x KafkaSQL configuration
KAFKA_BOOTSTRAP_SERVERS=kafka:9092
REGISTRY_KAFKASQL_TOPIC=kafkasql-journal-v2
REGISTRY_KAFKASQL_CONSUMER_GROUP_ID=registry-v2-consumer

# Registry 3.x KafkaSQL configuration
APICURIO_STORAGE_KIND=kafkasql
APICURIO_KAFKASQL_BOOTSTRAP_SERVERS=kafka:9092
APICURIO_KAFKASQL_TOPIC=kafkasql-journal-v3
----

IMPORTANT: Use a different topic name for v3 (`kafkasql-journal-v3`) than v2 (`kafkasql-journal-v2`). The v3 journal format is incompatible with v2.

.Authentication and authorization properties

[cols="1,1",options="header"]
|===
| Registry 2.x property
| Registry 3.x property

| `AUTH_ENABLED`
| `QUARKUS_OIDC_TENANT_ENABLED`

| `KEYCLOAK_URL`
| Use `QUARKUS_OIDC_AUTH_SERVER_URL` with full realm path

| `KEYCLOAK_REALM`
| Included in `QUARKUS_OIDC_AUTH_SERVER_URL`

| `KEYCLOAK_API_CLIENT_ID`
| `QUARKUS_OIDC_CLIENT_ID`

| `ROLE_BASED_AUTHZ_ENABLED`
| `APICURIO_AUTH_ROLE-BASED-AUTHORIZATION`

| `REGISTRY_AUTH_ROLES_ADMIN`
| `APICURIO_AUTH_ROLES_ADMIN`

| `REGISTRY_AUTH_ROLES_DEVELOPER`
| `APICURIO_AUTH_ROLES_DEVELOPER`

| `REGISTRY_AUTH_ROLES_READONLY`
| `APICURIO_AUTH_ROLES_READONLY`

| `QUARKUS_OIDC_TLS_VERIFICATION`
| `QUARKUS_OIDC_TLS_VERIFICATION` (same)
|===

*Example migration with Keycloak:*

[source,properties]
----
# Registry 2.x authentication
AUTH_ENABLED=true
KEYCLOAK_URL=https://keycloak:8443
KEYCLOAK_REALM=registry
KEYCLOAK_API_CLIENT_ID=registry-api
ROLE_BASED_AUTHZ_ENABLED=true
REGISTRY_AUTH_ROLES_ADMIN=sr-admin
REGISTRY_AUTH_ROLES_DEVELOPER=sr-developer
REGISTRY_AUTH_ROLES_READONLY=sr-readonly

# Registry 3.x authentication
QUARKUS_OIDC_TENANT_ENABLED=true
QUARKUS_OIDC_AUTH_SERVER_URL=https://keycloak:8443/realms/registry
QUARKUS_OIDC_CLIENT_ID=registry-api
APICURIO_AUTH_ROLE-BASED-AUTHORIZATION=true
APICURIO_AUTH_ROLES_ADMIN=sr-admin
APICURIO_AUTH_ROLES_DEVELOPER=sr-developer
APICURIO_AUTH_ROLES_READONLY=sr-readonly
----

IMPORTANT: The `QUARKUS_OIDC_AUTH_SERVER_URL` property in v3 requires the full realm path (for example, `https://keycloak.example.com/realms/registry`). In v2, you specified the realm by using a separate property.

.API and deletion properties

[cols="1,1",options="header"]
|===
| Registry 2.x property
| Registry 3.x property

| `REGISTRY_REST_ARTIFACT_DELETION_ENABLED`
| `APICURIO_REST_DELETION_ARTIFACT_ENABLED`

| Not available
| `APICURIO_REST_DELETION_ARTIFACT-VERSION_ENABLED`

| Not available
| `APICURIO_REST_DELETION_GROUP_ENABLED`

| `REGISTRY_UI_FEATURES_READONLY`
| `APICURIO_UI_FEATURES_READ-ONLY_ENABLED`

| `CORS_ALLOWED_ORIGINS`
| `QUARKUS_HTTP_CORS_ORIGINS`
|===

*Example migration:*

[source,properties]
----
# Registry 2.x API configuration
REGISTRY_REST_ARTIFACT_DELETION_ENABLED=true
REGISTRY_UI_FEATURES_READONLY=false
CORS_ALLOWED_ORIGINS=*

# Registry 3.x API configuration
APICURIO_REST_DELETION_ARTIFACT_ENABLED=true
APICURIO_REST_DELETION_ARTIFACT-VERSION_ENABLED=true
APICURIO_REST_DELETION_GROUP_ENABLED=true
APICURIO_UI_FEATURES_READ-ONLY_ENABLED=false
QUARKUS_HTTP_CORS_ORIGINS=*
----

TLS configuration remains unchanged between v2 and v3 for server-side settings.

.TLS/HTTPS properties
[cols="1,1",options="header"]
|===
| Registry 2.x property
| Registry 3.x property

| `QUARKUS_HTTP_SSL_PORT`
| `QUARKUS_HTTP_SSL_PORT` (same)

| `QUARKUS_HTTP_SSL_CERTIFICATE_KEY_STORE_FILE`
| `QUARKUS_HTTP_SSL_CERTIFICATE_KEY_STORE_FILE` (same)

| `QUARKUS_HTTP_SSL_CERTIFICATE_KEY_STORE_PASSWORD`
| `QUARKUS_HTTP_SSL_CERTIFICATE_KEY_STORE_PASSWORD` (same)

| `QUARKUS_HTTP_INSECURE_REQUESTS`
| `QUARKUS_HTTP_INSECURE_REQUESTS` (same)
|===

*Example (same for v2 and v3):*

[source,properties]
----
# TLS configuration (unchanged)
QUARKUS_HTTP_SSL_PORT=8443
QUARKUS_HTTP_SSL_CERTIFICATE_KEY_STORE_FILE=/certs/registry-keystore.p12
QUARKUS_HTTP_SSL_CERTIFICATE_KEY_STORE_PASSWORD=password123
QUARKUS_HTTP_INSECURE_REQUESTS=disabled
----

NOTE: TLS configuration is identical for v2 and v3. If the hostname and routes remain the same, reuse the same certificates.

[role="_additional-resources"]
.Additional resources
* {LinkServiceRegistryUser}[{NameServiceRegistryUser}]


// con-kafkasql-architecture
:_mod-docs-content-type: CONCEPT
[id="kafkasql-architecture_{context}"]
== KafkaSQL storage changes in {registry} 3.x

[role="_abstract"]
{registry} 3.x completely re-engineers the KafkaSQL storage implementation with significant architectural improvements. If you use KafkaSQL storage, understand these changes before you configure the v3 journal topic.

{registry} 3.x introduces a fundamentally different KafkaSQL implementation:

* *True journaling approach:* The new implementation uses a true event journaling pattern.
* *No log compaction:* Unlike v2, the v3 implementation does not rely on Kafka log compaction.
* *Immutable event log:* All registry operations are stored as immutable events in the journal topic.
* *Improved consistency:* Better handling of concurrent operations and edge cases.

{registry} 3.x KafkaSQL includes an optional snapshotting feature for advanced use cases.

Snapshotting creates periodic database snapshots of the registry state, which provides the following benefits:

* *Faster startup times:* The registry loads from the snapshot instead of replaying the entire journal.
* *Backup capability:* Use snapshots for disaster recovery.
* *Optional journal cleanup:* When snapshotting is enabled, you can configure finite retention to remove older journal events.

*When to use snapshotting:*

* Large registries with many artifacts (thousands or more).
* Long-running deployments with growing journal topics.
* Situations that need faster registry startup and recovery times.

IMPORTANT: Without snapshotting enabled, you must preserve all journal events (infinite retention). If you enable snapshotting and want to use finite retention, set `apicurio.kafkasql.topic-configuration-verification-override-enabled=true` and ensure that snapshots are created more frequently than messages are deleted.

NOTE: Snapshotting is an advanced feature. Most deployments do not need snapshotting enabled. Consult the link:https://www.apicur.io/registry/docs/apicurio-registry/3.1.x/index.html[official {registry} documentation] for more information on how to configure and use the snapshotting feature.

[role="_additional-resources"]
.Additional resources
* xref:configuring-kafkasql-topic_{context}[]
* xref:migrating-registry-configuration_{context}[]


// proc-configuring-kafkasql-topic
:_mod-docs-content-type: PROCEDURE
[id="configuring-kafkasql-topic_{context}"]
== Configuring the KafkaSQL journal topic

[role="_abstract"]
You can create and configure the v3 KafkaSQL journal topic with the correct cleanup policy and retention settings.

.Prerequisites

* You have access to Kafka administrative tools (`kafka-topics.sh`).
* You understand the KafkaSQL architectural changes in v3. See xref:kafkasql-architecture_{context}[KafkaSQL storage changes].

.Procedure

. Create the v3 KafkaSQL journal topic with the correct settings.
+
[source,bash]
----
kafka-topics.sh --bootstrap-server kafka:9092 --create \
  --topic kafkasql-journal-v3 \
  --partitions 1 \
  --replication-factor 3 \
  --config cleanup.policy=delete \
  --config retention.ms=-1 \
  --config retention.bytes=-1
----
+
The partition count is configurable. All journal messages use a global partition key for ordering, so a single partition is sufficient. Adjust based on your Kafka cluster configuration.
+
IMPORTANT: Configure the journal topic with `cleanup.policy=delete` and infinite retention (`retention.ms=-1`). Do not use log compaction. You must preserve all journal events for proper registry operation.

. Verify the topic configuration.
+
[source,bash]
----
kafka-topics.sh --bootstrap-server kafka:9092 --describe \
  --topic kafkasql-journal-v3

# Expected output:
#   cleanup.policy=delete
#   retention.ms=-1
#   retention.bytes=-1
----

.Verification

* Verify that the topic uses `cleanup.policy=delete` (not `compact`).
* Verify that `retention.ms=-1` and `retention.bytes=-1` are set for infinite retention.
* Verify that the topic is accessible from the {registry} 3.x deployment.

[role="_additional-resources"]
.Additional resources
* xref:kafkasql-architecture_{context}[]


// proc-verifying-migration-configuration
:_mod-docs-content-type: PROCEDURE
[id="verifying-migration-configuration_{context}"]
== Verifying migration configuration

[role="_abstract"]
You can verify your v3 configuration by following this checklist to ensure that all properties are correctly updated before you switch production traffic.

.Procedure

. Inventory all configuration files, including deployment manifests, `application.properties`, and environment variables.
. Create a mapping spreadsheet that documents v2 properties and their v3 equivalents.
. Update configuration for v3 deployment:
  * Storage: Change the database name or topic name.
  * Authentication: Update to the full realm URL format.
  * Properties: Apply the `apicurio.*` namespace.
. Deploy v3 to a staging environment with updated configuration.
. Monitor startup logs for configuration errors or warnings.
. Test all functionality:
  * Storage connectivity.
  * Authentication and authorization.
  * API operations (create, read, update, delete).
  * UI access.
. Document any custom or undocumented properties for future reference.

.Verification

* Verify that {registry} 3.x starts without configuration errors.
* Verify that all API endpoints respond correctly.
* Verify that authentication and authorization work as expected.

[role="_additional-resources"]
.Additional resources
* xref:migrating-registry-configuration_{context}[]
* xref:resolving-configuration-issues_{context}[]


// ref-resolving-configuration-issues
:_mod-docs-content-type: REFERENCE
[id="resolving-configuration-issues_{context}"]
== Resolving configuration issues

[role="_abstract"]
If you encounter configuration issues during migration, use the following guidance to diagnose and resolve common problems.

v3 fails to start with "Storage not configured"::
+
--
*Symptoms:*

* Registry pod or container fails to start.
* Logs show: `Storage kind not configured`.

*Cause:* Missing `APICURIO_STORAGE_KIND` property.

*Solution:*

[source,properties]
----
# For SQL storage
APICURIO_STORAGE_KIND=sql
APICURIO_STORAGE_SQL_KIND=postgresql

# For KafkaSQL storage
APICURIO_STORAGE_KIND=kafkasql
----
--

v3 authentication fails with "User is not authenticated"::
+
--
*Symptoms:*

* All API requests return 401 Unauthorized.
* Valid access tokens are rejected.

*Cause:* Incorrect OAuth2/OIDC configuration, typically a missing realm path.

*Solution:*

Verify that `QUARKUS_OIDC_AUTH_SERVER_URL` includes the full realm path:

[source,properties]
----
# Wrong (v2 style)
QUARKUS_OIDC_AUTH_SERVER_URL=https://keycloak:8443

# Correct (v3 requires realm path)
QUARKUS_OIDC_AUTH_SERVER_URL=https://keycloak:8443/realms/registry
----

Verify that Keycloak is accessible from the registry container:

[source,bash]
----
curl -k https://keycloak:8443/realms/registry/.well-known/openid-configuration
----
--

Database connection fails::
+
--
*Symptoms:*

* Registry fails to start.
* Logs show JDBC connection errors.

*Cause:* Using old `REGISTRY_DATASOURCE_*` or `QUARKUS_DATASOURCE_*` properties.

*Solution:*

Update to v3 datasource properties:

[source,properties]
----
# Remove these v2 properties
# REGISTRY_DATASOURCE_URL=...
# REGISTRY_DATASOURCE_USERNAME=...

# Use v3 properties
APICURIO_DATASOURCE_URL=jdbc:postgresql://postgres:5432/registry_v3
APICURIO_DATASOURCE_USERNAME=apicurio
APICURIO_DATASOURCE_PASSWORD=password123
----
--

CORS errors in browser when accessing UI::
+
--
*Symptoms:*

* UI loads but API calls fail.
* Browser console shows CORS errors.

*Cause:* CORS property name changed in v3.

*Solution:*

[source,properties]
----
# Wrong (v2 property name)
CORS_ALLOWED_ORIGINS=*

# Correct (v3 property name)
QUARKUS_HTTP_CORS_ORIGINS=*
----
--

Role-based authorization does not work::
+
--
*Symptoms:*

* Users can access the registry but cannot perform admin operations.
* No 403 Forbidden errors, but operations fail silently.

*Cause:* Role configuration properties not updated to the v3 namespace.

*Solution:*

[source,properties]
----
# Update all role properties
APICURIO_AUTH_ROLE-BASED-AUTHORIZATION=true
APICURIO_AUTH_ROLES_ADMIN=sr-admin
APICURIO_AUTH_ROLES_DEVELOPER=sr-developer
APICURIO_AUTH_ROLES_READONLY=sr-readonly
----

Verify that the user has correct roles in Keycloak and the token includes expected roles:

[source,bash]
----
# Decode JWT token to check roles
echo $ACCESS_TOKEN | cut -d. -f2 | base64 -d | jq .realm_access.roles
----
--

[role="_additional-resources"]
.Additional resources
* xref:migrating-registry-configuration_{context}[]


// con-ui-architecture-changes
:_mod-docs-content-type: CONCEPT
[id="user-interface_{context}"]
== User interface architecture changes

[role="_abstract"]
{registry} 3.x introduces a significant architectural change to the user interface. You deploy the UI as a separate container, which provides more deployment flexibility but requires additional configuration considerations during migration.

In {registry} {registry-v2}, the UI is built into the main `apicurio-registry` container as a single monolithic application. {registry} 3.x separates the UI into its own container:

*Registry 2.x architecture:*

----
┌─────────────────────────────┐
│  apicurio-registry-sql:2.x  │
│  ┌───────────┬────────────┐ │
│  │    UI     │    API     │ │
│  └───────────┴────────────┘ │
└─────────────────────────────┘
----

*Registry 3.x architecture:*

----
┌────────────────────┐    ┌────────────────────┐
│ apicurio-registry  │    │ apicurio-registry  │
│       -ui:3.x      │    │       :3.x         │
│                    │    │                    │
│   UI (Frontend)    │───▶│   API (Backend)    │
└────────────────────┘    └────────────────────┘
----

*Implications for migration:*

* You deploy and manage two containers instead of one.
* Additional networking configuration is required.
* You have more flexible deployment options.
* You can scale UI and API components independently.

{registry} 3.1.0 introduces integrated Designer functionality, which replaces the standalone Apicurio Studio project.

Designer provides visual editing capabilities for OpenAPI and AsyncAPI specifications directly within the {registry} UI. This functionality previously existed as a separate project (Apicurio Studio) but is now fully integrated into {registry}.

*Key features:*

* Visual editor for OpenAPI 3.x specifications.
* Visual editor for AsyncAPI specifications.
* Draft artifact version state for work-in-progress designs.
* Direct integration with {registry} storage.
* No separate deployment required.

[role="_additional-resources"]
.Additional resources
* xref:deploying-registry-ui_{context}[]
* xref:ui-configuration-properties_{context}[]
* https://www.apicur.io/blog/2025/10/23/studio-fully-deprecated[Apicurio Studio Deprecation Announcement]


// proc-deploying-registry-ui
:_mod-docs-content-type: PROCEDURE
[id="deploying-registry-ui_{context}"]
== Deploying the {registry} user interface

[role="_abstract"]
You can deploy the {registry} 3.x user interface by choosing a deployment topology, deploying the UI container, and optionally enabling Designer functionality.

.Prerequisites

* You deployed {registry} 3.x (the backend API container).
* You have access to your container orchestration platform (Docker Compose, Kubernetes, or OpenShift).

.Procedure

. Choose a deployment topology.
+
--
*Option 1: Separate subdomains*

Deploy the UI and API to separate subdomains:

[source,yaml,subs="+attributes"]
----
# UI accessible at: https://registry-ui.example.com
apicurio-registry-ui:
  image: apicurio/apicurio-registry-ui:{registry-release}
  ports:
    - "443:8443"
  environment:
    - REGISTRY_API_URL=https://registry-api.example.com/apis/registry/v3

# API accessible at: https://registry-api.example.com
apicurio-registry:
  image: apicurio/apicurio-registry:{registry-release}
  ports:
    - "443:8443"
----

Benefits: clean separation of concerns, independent DNS, easier independent scaling.

*Option 2: Co-located with reverse proxy*

Use a reverse proxy (nginx, HAProxy) to serve both the UI and API from the same domain:

[source,yaml,subs="+attributes"]
----
services:
  apicurio-registry:
    image: apicurio/apicurio-registry:{registry-release}
    environment:
      - apicurio.app.context-path=/app/
      - QUARKUS_HTTP_CORS_ORIGINS=*

  apicurio-registry-ui:
    image: apicurio/apicurio-registry-ui:{registry-release}
    environment:
      - REGISTRY_CONTEXT_PATH=/ui/
      - REGISTRY_API_URL=/app/apis/registry/v3
      - REGISTRY_DOCS_URL=/ui/docs/
      - REGISTRY_EDITORS_URL=/ui/editors/

  nginx:
    image: nginx
    ports:
      - "443:8443"
    # nginx.conf routes:
    #   /ui/* → apicurio-registry-ui
    #   /app/* → apicurio-registry
----

Benefits: single domain for users, simpler certificate management, avoids CORS complexity.
--

. Deploy the UI container with the `REGISTRY_API_URL` environment variable pointing to your backend API.

. Configure UI settings on the application (API) container by using `apicurio.ui.*` properties.
+
For a complete list of available properties, see xref:ui-configuration-properties_{context}[User interface configuration properties].

. Optional: Enable Designer functionality.
+
Designer functionality requires you to enable content mutability for artifact versions:
+
[source,properties]
----
apicurio.rest.mutability.artifact-version-content.enabled=true
----
+
When enabled, users with the developer or admin role can create draft versions, edit draft content by using the visual editor, save changes iteratively, and finalize drafts to create immutable versions.

.Verification

* Verify that the UI loads and displays the registry dashboard.
* Verify that the UI connects to the backend API and displays artifacts.
* If Designer is enabled, verify that you can create and edit draft versions.

[role="_additional-resources"]
.Additional resources
* xref:ui-configuration-properties_{context}[]
* {LinkServiceRegistryUser}[{NameServiceRegistryUser}]


// ref-ui-configuration-properties
:_mod-docs-content-type: REFERENCE
[id="ui-configuration-properties_{context}"]
== User interface configuration properties

[role="_abstract"]
The following tables list the configuration properties for the {registry} 3.x user interface. Configure most settings on the application (API) container. The UI container environment variables override application settings when set.

Configure the following properties on the `apicurio-registry` (API) container:

.Application container properties
[cols="1,2,1",options="header"]
|===
| Property
| Description
| Default

| `apicurio.ui.contextPath`
| Context path of the UI
| `/`

| `apicurio.ui.navPrefixPath`
| Navigation prefix for all UI paths
| `/`

| `apicurio.ui.docsUrl`
| URL of the documentation component
| `/docs/`

| `apicurio.ui.editorsUrl`
| URL of the editors component (Designer)
| `/editors/`

| `apicurio.ui.auth.oidc.redirect-uri`
| OAuth2/OIDC redirect URI
| `/`

| `apicurio.ui.auth.oidc.client-id`
| OAuth2/OIDC client ID for UI
| `apicurio-registry-ui`

| `apicurio.ui.auth.oidc.logout-url`
| OAuth2/OIDC logout URL
| `f5`

| `apicurio.ui.auth.oidc.scope`
| OAuth2/OIDC scope
| `openid profile email`

| `apicurio.ui.features.read-only.enabled`
| Enable read-only mode for the UI
| `false`

| `apicurio.ui.features.breadcrumbs`
| Show breadcrumbs in the UI
| `true`

| `apicurio.ui.features.settings`
| Show the Settings tab in the UI
| `true`
|===

*Example application container configuration:*

[source,properties]
----
apicurio.ui.contextPath=/
apicurio.ui.editorsUrl=/editors/
apicurio.ui.auth.oidc.client-id=registry-ui-client
apicurio.ui.auth.oidc.scope=openid profile email
apicurio.ui.features.read-only.enabled=false
----

The UI container environment variables override application configuration. You typically do not need to set these variables.

.UI container environment variables
[cols="1,2",options="header"]
|===
| Environment variable
| Description

| `REGISTRY_API_URL`
| *Required:* URL to the backend API (for example, `http://registry-api:8080/apis/registry/v3`)

| `REGISTRY_CONTEXT_PATH`
| Override for the UI context path

| `REGISTRY_NAV_PREFIX_PATH`
| Override for the navigation prefix path

| `REGISTRY_DOCS_URL`
| Override for the documentation URL

| `REGISTRY_EDITORS_URL`
| Override for the editors (Designer) URL

| `REGISTRY_AUTH_URL`
| Override for the OAuth2/OIDC auth server URL

| `REGISTRY_AUTH_CLIENT_ID`
| Override for the OAuth2/OIDC client ID

| `REGISTRY_FEATURE_READ_ONLY`
| Override for read-only mode (`true` or `false`)
|===

NOTE: The only required UI container property is `REGISTRY_API_URL`. Configure all other UI settings on the application container by using `apicurio.ui.*` properties.

[role="_additional-resources"]
.Additional resources
* {LinkServiceRegistryUser}[{NameServiceRegistryUser}]


// ref-migration-scripts
:_mod-docs-content-type: REFERENCE
[id="migration-scripts_{context}"]
== Migration scripts reference

[role="_abstract"]
The migration scripts automate key steps in the workflow. All scripts support interactive and automated modes through environment variables, with OAuth2 authentication support.

The migration scripts are production-ready tools that handle the most critical and error-prone aspects of migration:

* *apicurio-capture-metrics.sh:* Captures metrics before and after migration for comparison.
* *apicurio-export-registry-data.sh:* Exports all data from {registry-v2}.
* *apicurio-import-registry-data.sh:* Imports data into {registry} 3.x.
* *apicurio-verify-registry-version.sh:* Verifies registry version and health.
* *apicurio-verify-v3-api-features.sh:* Tests v3-specific API features.

Find all scripts in the {registry} GitHub repository under link:https://github.com/Apicurio/apicurio-registry/tree/3.2.x/docs/scripts[docs/scripts/].

All migration scripts share the following common features:

*Interactive and automated modes:*

* Interactive mode: Scripts prompt for required inputs with sensible defaults.
* Automated mode: Set environment variables to skip prompts (useful for CI/CD).

*OAuth2 authentication support:*

* Client credentials grant type.
* Configurable token endpoint and client credentials.
* Optional: Run without authentication for testing environments.

*TLS/HTTPS support:*

* Works with trusted certificates by default.
* Option to skip TLS verification for development and testing (`SKIP_TLS_VERIFY=true`).

*Clear output and error handling:*

* Color-coded output (info, success, warning, error).
* Detailed progress indicators.
* Clear exit codes for automation (0 = success, 1 = failure).

Script: apicurio-capture-metrics.sh

*Purpose:* Captures metrics from the registry before and after migration.

*What it captures:*

* Total artifact count.
* Sample of up to 5 artifacts with full metadata.
* Global rule configurations.
* Output saved to a text file for post-migration comparison.

*Usage (interactive):*

[source,bash]
----
./apicurio-capture-metrics.sh
----

*Usage (automated):*

[source,bash]
----
export REGISTRY_URL="https://registry.example.com"
export REGISTRY_API_VERSION="v2"
export AUTH_ENABLED="true"
export AUTH_TOKEN_URL="https://keycloak.example.com/realms/registry/protocol/openid-connect/token"
export AUTH_CLIENT_ID="admin-client"
export AUTH_CLIENT_SECRET="your-secret"
export OUTPUT_FILE="registry-baseline.txt"

./apicurio-capture-metrics.sh
----

*Environment variables:*

[cols="1,2",options="header"]
|===
| Variable
| Description

| `REGISTRY_URL`
| Registry base URL (for example, `https://registry.example.com`)

| `REGISTRY_API_VERSION`
| API version to use (default: `v2`)

| `AUTH_ENABLED`
| Set to `true` to enable OAuth2 authentication

| `AUTH_TOKEN_URL`
| OAuth2 token endpoint URL

| `AUTH_CLIENT_ID`
| OAuth2 client ID

| `AUTH_CLIENT_SECRET`
| OAuth2 client secret

| `OUTPUT_FILE`
| Output file path (default: `registry-baseline.txt`)

| `SKIP_TLS_VERIFY`
| Set to `true` to skip TLS certificate verification
|===

*Output:*

Text file that contains artifact summary and global rules (default: `registry-baseline.txt`).

Script: apicurio-export-registry-data.sh

*Purpose:* Exports all data from a {registry-v2} deployment to a ZIP file.

*What it exports:*

* All artifacts and versions.
* Artifact metadata (labels, properties, descriptions).
* Global and artifact-specific rules.
* Artifact references.

*Usage (interactive):*

[source,bash]
----
./apicurio-export-registry-data.sh
----

*Usage (automated):*

[source,bash]
----
export REGISTRY_URL="https://registry-v2.example.com"
export REGISTRY_API_VERSION="v2"
export AUTH_ENABLED="true"
export AUTH_TOKEN_URL="https://keycloak.example.com/realms/registry/protocol/openid-connect/token"
export AUTH_CLIENT_ID="admin-client"
export AUTH_CLIENT_SECRET="your-secret"
export EXPORT_FILE="registry-export.zip"

./apicurio-export-registry-data.sh
----

*Environment variables:*

[cols="1,2",options="header"]
|===
| Variable
| Description

| `REGISTRY_URL`
| Registry base URL (for example, `https://registry-v2.example.com`)

| `REGISTRY_API_VERSION`
| API version to use (default: `v2`)

| `AUTH_ENABLED`
| Set to `true` to enable OAuth2 authentication

| `AUTH_TOKEN_URL`
| OAuth2 token endpoint URL

| `AUTH_CLIENT_ID`
| OAuth2 client ID

| `AUTH_CLIENT_SECRET`
| OAuth2 client secret

| `EXPORT_FILE`
| Export file path (default: `registry-export.zip`)

| `SKIP_TLS_VERIFY`
| Set to `true` to skip TLS certificate verification
|===

*Output:*

ZIP file that contains the complete registry export (default: `registry-export.zip`).

*Important notes:*

* Run this script while the registry is in read-only mode to maintain data consistency.
* The export file size varies depending on registry size.

Script: apicurio-import-registry-data.sh

*Purpose:* Imports data from a v2 export file into a {registry} 3.x deployment.

*What it does:*

* Validates the import ZIP file.
* Checks {registry} 3.x accessibility and version.
* Imports all data from the export file.
* Verifies the artifact count after import.
* Reports global rules configuration.

*Usage (interactive):*

[source,bash]
----
./apicurio-import-registry-data.sh
----

*Usage (automated):*

[source,bash]
----
export REGISTRY_URL="https://registry-v3.example.com"
export REGISTRY_API_VERSION="v3"
export AUTH_ENABLED="true"
export AUTH_TOKEN_URL="https://keycloak.example.com/realms/registry/protocol/openid-connect/token"
export AUTH_CLIENT_ID="admin-client"
export AUTH_CLIENT_SECRET="your-secret"
export IMPORT_FILE="registry-export.zip"

./apicurio-import-registry-data.sh
----

*Environment variables:*

[cols="1,2",options="header"]
|===
| Variable
| Description

| `REGISTRY_URL`
| Registry base URL (for example, `https://registry-v3.example.com`)

| `REGISTRY_API_VERSION`
| API version to use (default: `v3`)

| `AUTH_ENABLED`
| Set to `true` to enable OAuth2 authentication

| `AUTH_TOKEN_URL`
| OAuth2 token endpoint URL

| `AUTH_CLIENT_ID`
| OAuth2 client ID

| `AUTH_CLIENT_SECRET`
| OAuth2 client secret

| `IMPORT_FILE`
| Import file path (default: `registry-export.zip`)

| `SKIP_TLS_VERIFY`
| Set to `true` to skip TLS certificate verification
|===

*Important notes:*

* The import adds to existing data and does not replace it.
* The script warns if the registry already contains artifacts.
* Import into an empty v3 registry for best results.

Script: apicurio-verify-registry-version.sh

*Purpose:* Verifies the version and health of a {registry} deployment.

*What it checks:*

* Health endpoint (200 OK).
* System information retrieval.
* Version verification (2.x or 3.x).

*Usage (interactive):*

[source,bash]
----
./apicurio-verify-registry-version.sh
----

*Usage (automated):*

[source,bash]
----
export REGISTRY_URL="https://registry.example.com"
export REGISTRY_VERSION="v3"
export AUTH_ENABLED="true"
export AUTH_TOKEN_URL="https://keycloak.example.com/realms/registry/protocol/openid-connect/token"
export AUTH_CLIENT_ID="admin-client"
export AUTH_CLIENT_SECRET="your-secret"

./apicurio-verify-registry-version.sh
----

*Environment variables:*

[cols="1,2",options="header"]
|===
| Variable
| Description

| `REGISTRY_URL`
| Registry base URL (for example, `https://registry.example.com`)

| `REGISTRY_VERSION`
| Registry version to test (default: `v3`)

| `AUTH_ENABLED`
| Set to `true` to enable OAuth2 authentication

| `AUTH_TOKEN_URL`
| OAuth2 token endpoint URL

| `AUTH_CLIENT_ID`
| OAuth2 client ID

| `AUTH_CLIENT_SECRET`
| OAuth2 client secret

| `SKIP_TLS_VERIFY`
| Set to `true` to skip TLS certificate verification
|===

*Exit codes:*

* `0`: All checks passed.
* `1`: One or more checks failed.

Script: apicurio-verify-v3-api-features.sh

*Purpose:* Verifies that v3-specific API features are available and functioning correctly.

*What it tests:*

* v3 system endpoints (`/system/info`, `/system/limits`).
* v3 search endpoints (`/search/groups`, `/search/versions`).
* Admin endpoints.

*Usage (interactive):*

[source,bash]
----
./apicurio-verify-v3-api-features.sh
----

*Usage (automated):*

[source,bash]
----
export REGISTRY_URL="https://registry-v3.example.com"
export AUTH_ENABLED="true"
export AUTH_TOKEN_URL="https://keycloak.example.com/realms/registry/protocol/openid-connect/token"
export AUTH_CLIENT_ID="admin-client"
export AUTH_CLIENT_SECRET="your-secret"

./apicurio-verify-v3-api-features.sh
----

*Environment variables:*

[cols="1,2",options="header"]
|===
| Variable
| Description

| `REGISTRY_URL`
| Registry base URL (for example, `https://registry-v3.example.com`)

| `AUTH_ENABLED`
| Set to `true` to enable OAuth2 authentication

| `AUTH_TOKEN_URL`
| OAuth2 token endpoint URL

| `AUTH_CLIENT_ID`
| OAuth2 client ID

| `AUTH_CLIENT_SECRET`
| OAuth2 client secret

| `SKIP_TLS_VERIFY`
| Set to `true` to skip TLS certificate verification
|===

*Exit codes:*

* `0`: All feature tests passed.
* `1`: One or more feature tests failed.

All scripts support OAuth2 authentication by using the client credentials grant type.

*Interactive mode (prompts):*

If environment variables are not set, scripts prompt for the following information:

* Auth token endpoint URL.
* Client ID.
* Client secret (hidden input).

*Automated mode (environment variables):*

For automation, set the following environment variables:

[source,bash]
----
export AUTH_ENABLED="true"
export AUTH_TOKEN_URL="https://keycloak.example.com/realms/registry/protocol/openid-connect/token"
export AUTH_CLIENT_ID="admin-client"
export AUTH_CLIENT_SECRET="your-secret"
----

*Unauthenticated deployments:*

If your {registry} does not use authentication:

[source,bash]
----
export AUTH_ENABLED="false"
----

Alternatively, press Enter when prompted for authentication.

*Production (trusted certificates):*

The scripts use trusted certificates by default. To configure this explicitly:

[source,bash]
----
export SKIP_TLS_VERIFY="false"
----

*Development (self-signed certificates):*

For testing with self-signed certificates:

[source,bash]
----
export SKIP_TLS_VERIFY="true"
----

WARNING: Use `SKIP_TLS_VERIFY=true` only in non-production environments.

"Failed to obtain access token"::
Verify that `AUTH_TOKEN_URL` is correct. Check that client credentials are valid. Confirm that the client has appropriate roles (for example, `sr-admin`). Test manually with curl.

"Registry is not accessible"::
Verify that `REGISTRY_URL` is correct. Check network connectivity. Confirm that the registry is running and healthy. Verify firewall and security group rules.

"Export file is too small"::
Check that authentication is working. Verify admin permissions. Review registry logs for errors. Confirm that the export endpoint is accessible.

"Import failed"::
Verify that the export file is a valid ZIP: `unzip -t registry-export.zip`. Check that registry v3 has sufficient storage. Review registry v3 logs for errors. Confirm that registry v3 is healthy before import.

"Artifact count mismatch"::
Wait a few seconds after import (processing delay). Check import logs for errors. Verify export file integrity. Consider re-exporting and re-importing.

[id="installing-script-dependencies_{context}"]
=== Installing dependencies

[role="_abstract"]
All migration scripts require the following tools:

* `bash` (version 4.0 or later).
* `curl`.
* `jq` (for JSON parsing).
* `unzip` (optional, for ZIP validation).

[role="_additional-resources"]
.Additional resources
* {LinkServiceRegistryUser}[{NameServiceRegistryUser}]


// ref-troubleshooting-migration
:_mod-docs-content-type: REFERENCE
[id="troubleshooting-migration_{context}"]
== Troubleshooting migration issues

[role="_abstract"]
If you encounter issues during migration, use the following guidance to diagnose and resolve common problems.

Export fails::
+
--
* Verify that the authentication credentials are valid.
* Verify that {registry-v2} is accessible.
* Review registry logs for errors.
--

Import fails::
+
--
* Verify that the export file is a valid ZIP format.
* Verify that {registry} 3.x has sufficient storage.
* Review registry v3 logs for specific errors.
* If a partial import occurred, drop the v3 database (or Kafka topic) and retry.
--

Baseline metrics mismatch::
+
--
* Review import logs for skipped artifacts.
* Check for import errors or warnings.
* Verify export file integrity.
* Consider re-exporting and re-importing.
--

Rollback needed::
+
--
. Switch load balancer traffic back to {registry-v2}.
. Investigate v3 issues.
+
Recovery time is 30-60 seconds. Your v2 data remains unchanged because the export operation is read-only.
--

[role="_additional-resources"]
.Additional resources
* xref:migrating-minimal-downtime_{context}[]
* xref:resolving-configuration-issues_{context}[]
* xref:migration-scripts_{context}[]
