> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ditto.live/llms.txt
> Use this file to discover all available pages before exploring further.

# Java V4→V5 API Migration Guide

> Guide for migrating Java apps from Ditto v4 to v5.

## Overview

This guide covers the essential changes needed to migrate your Ditto Java app from v4 to v5. The main architectural shift is moving from identity-based initialization to a three-phase configuration model with CompletableFuture-based APIs.

**Also required:** v5 uses DQL (Ditto Query Language) for all data operations. See the [DQL Migration Guide](./java-dql) for query migration steps.

## AI Agent Prompt

Use this prompt when working with an AI coding assistant to migrate your Ditto Java app from v4 to v5.

<Accordion title="Copy AI Migration Prompt (Click to Expand)">
  ````text theme={null}
  I need help migrating a Ditto Java application from v4 to v5. Focus on these critical changes:

  INITIALIZATION:
  Replace new Ditto() with DittoFactory.create(config) using Builder pattern
  - Create DittoConfig with databaseId and connect mode
  - OnlinePlayground → new DittoConfig.Connect.Server(...)
  - OfflinePlayground → new DittoConfig.Connect.SmallPeersOnly(null)
  - Remove updateTransportConfig, disableSyncWithV3 calls
  - IMPORTANT: If maintaining v4 behavior, set DQL_STRICT_MODE=true BEFORE starting sync

  AUTHENTICATION:
  Set setExpirationHandler callback after initialization
  - Handler called when timeUntilExpiration == 0 (initial) or > 0 (refresh)
  - Use DittoAuthenticationProvider.development() for playground tokens
  - login() returns CompletableFuture with .thenAccept() and .exceptionally()

  BEFORE (V4):
  ```java
  Ditto ditto = new Ditto(
      new DittoIdentity.OnlinePlayground(
          context,
          "your-app-id",
          "your-token",
          false,
          "https://your-server.ditto.live"
      )
  );
  ditto.updateTransportConfig(config -> {
      config.connect.webSocketURLs.add("wss://...");
  });
  ditto.disableSyncWithV3();
  ditto.startSync();
  ```

  AFTER (V5):
  ```java
  DittoConfig config = new DittoConfig.Builder("your-database-id")
      .connect(new DittoConfig.Connect.Server("https://your-server.ditto.live"))
      .build();

  Ditto ditto = DittoFactory.create(config);

  ditto.getAuth().setExpirationHandler((expiringDitto, timeUntilExpiration) -> {
      expiringDitto.getAuth().login("your-token", DittoAuthenticationProvider.development())
          .thenAccept(clientInfo -> {
              System.out.println("Authentication successful");
          })
          .exceptionally(error -> {
              System.out.println("Authentication failed: " + error);
              return null;
          });
  });

  // If maintaining v4 behavior, set strict mode BEFORE starting sync
  // ditto.getStore().execute("ALTER SYSTEM SET DQL_STRICT_MODE = true", null);

  ditto.getSync().start();
  ```

  CHECKLIST:
  - Replace new Ditto() with DittoFactory.create(config)
  - Create DittoConfig with databaseId and connect
  - Replace ditto.startSync() with ditto.getSync().start()
  - Set DQL_STRICT_MODE=true BEFORE getSync().start() if maintaining v4 behavior
  - Set setExpirationHandler for authentication
  - Remove updateTransportConfig, disableSyncWithV3 calls
  - Replace getPeerKeyString() with getPeerKey()

  Work through the codebase systematically. Show me each file's changes.
  ````
</Accordion>

***

## Ditto Instance Initialization

v5 separates initialization into three distinct phases for better clarity and control:

<CodeGroup>
  ```java JAVA (v5) theme={null}
  // 1. Configure
  DittoConfig config = new DittoConfig.Builder("your-database-id")
      .connect(new DittoConfig.Connect.Server("https://your-server.ditto.live"))
      .build();

  // 2. Initialize
  Ditto ditto = DittoFactory.create(config);

  // 3. Authenticate
  ditto.getAuth().setExpirationHandler((expiringDitto, timeUntilExpiration) -> {
      expiringDitto.getAuth().login("your-token", DittoAuthenticationProvider.development())
          .thenAccept(clientInfo -> {
              System.out.println("Authentication successful");
          })
          .exceptionally(error -> {
              System.out.println("Authentication failed: " + error);
              return null;
          });
  });

  // 4. Start sync
  ditto.getSync().start();
  ```

  ```java JAVA (v4) theme={null}
  // Everything mixed together
  Ditto ditto = new Ditto(
      new DittoIdentity.OnlinePlayground(
          context,
          "your-app-id",
          "your-token",
          false,
          "https://your-server.ditto.live"
      )
  );

  // Workarounds
  ditto.updateTransportConfig(config -> {
      config.connect.webSocketURLs.add("wss://...");
  });
  ditto.getStore().execute("ALTER SYSTEM SET DQL_STRICT_MODE = false", null);
  ditto.disableSyncWithV3();

  ditto.startSync();
  ```
</CodeGroup>

**These changes provide:**

* Clear separation of connection, initialization, and authentication concerns
* CompletableFuture-based async authentication
* No workarounds needed (transport config, DQL strict mode, v3 sync disable)
* Type-safe configuration with compile-time validation

**Initialization Migration Steps**

<Steps>
  <Step title="Replace Initialization">
    Replace `new Ditto()` constructor with `DittoFactory.create(config)`.

    <CodeGroup>
      ```java JAVA (v5) theme={null}
      DittoConfig config = new DittoConfig.Builder("your-database-id")
          .connect(new DittoConfig.Connect.Server("https://your-server.ditto.live"))
          .build();

      Ditto ditto = DittoFactory.create(config);
      // Set up auth handler before starting sync (see Step 2)
      ditto.getSync().start();
      ```

      ```java JAVA (v4) theme={null}
      Ditto ditto = new Ditto(
          new DittoIdentity.OnlinePlayground(
              context,
              "your-app-id",
              "your-token",
              false,
              "https://your-server.ditto.live"
          )
      );
      ditto.startSync();
      ```
    </CodeGroup>

    **Key changes:**

    * Use `DittoConfig.Builder` pattern instead of `DittoIdentity`
    * `appId` → `databaseId`
    * `.OnlinePlayground` → `new DittoConfig.Connect.Server(...)`
    * `.OfflinePlayground` → `new DittoConfig.Connect.SmallPeersOnly(null)`
    * Remove `updateTransportConfig`, `disableSyncWithV3()`, and DQL strict mode queries
  </Step>

  <Step title="Update Authentication">
    Set authentication handler separately after initialization.

    <CodeGroup>
      ```java JAVA (v5) theme={null}
      // Set handler after DittoFactory.create()
      ditto.getAuth().setExpirationHandler((expiringDitto, timeUntilExpiration) -> {
          if (timeUntilExpiration == 0) {
              // Initial auth
              expiringDitto.getAuth().login(
                  "your-token",
                  DittoAuthenticationProvider.development()
              ).thenAccept(clientInfo -> {
                  System.out.println("Authentication successful");
              }).exceptionally(error -> {
                  System.out.println("Authentication failed: " + error);
                  return null;
              });
          } else {
              // Token refresh
              refreshToken(expiringDitto);
          }
      });
      ```

      ```java JAVA (v4) theme={null}
      // Auth mixed with identity
      Ditto ditto = new Ditto(
          new DittoIdentity.OnlinePlayground(
              context,
              "your-app-id",
              "your-token",  // Token in identity
              false,
              "https://your-server.ditto.live"
          )
      );
      ```
    </CodeGroup>

    **Key changes:**

    * Authentication now uses `setExpirationHandler` callback
    * Handler called when auth required (`timeUntilExpiration == 0`) or token expiring
    * Use `DittoAuthenticationProvider.development()` for playground tokens
    * Custom auth: `new DittoAuthenticationProvider("your-provider")`
  </Step>
</Steps>

## Additional Changes

### DQL Strict Mode Behavior Change

<Warning>
  **Breaking Change**: v5 defaults to `DQL_STRICT_MODE=false`, which fundamentally changes how DQL queries behave.

  * **v4 default**: Objects treated as **REGISTER** (whole-object replacement)
  * **v5 default**: Objects treated as **MAP** (field-level merging)

  This affects the behavior of all DQL `SELECT`, `INSERT`, and `UPDATE` operations.
</Warning>

Choose the appropriate migration path based on your current v4 configuration:

<AccordionGroup>
  <Accordion title="Currently Using DQL with DQL_STRICT_MODE=true (v4 default)">
    **If you're currently using the v4 default (`DQL_STRICT_MODE=true`)**, you **must** explicitly set strict mode to `true` in v5 before starting sync or executing any queries.

    <Warning>
      Failing to set strict mode will cause objects to merge at the field level instead of replacing entirely, which can result in unexpected data behavior and perceived data loss.
    </Warning>

    <Note>
      To migrate to `DQL_STRICT_MODE=false` (the new v5 default), contact Ditto Customer Support for guidance.
    </Note>

    <CodeGroup>
      ```java Java (v5 - Maintain v4 Behavior) highlight={6,7} theme={null}
      DittoConfig config = new DittoConfig.Builder("your-database-id")
          .connect(new DittoConfig.Connect.Server("https://your-server.ditto.live"))
          .build();
      Ditto ditto = DittoFactory.create(config);

      // IMPORTANT: Set strict mode BEFORE starting sync or running queries
      ditto.getStore().execute("ALTER SYSTEM SET DQL_STRICT_MODE = true", null);

      // Now safe to start sync
      ditto.getSync().start();
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="Currently Using DQL with DQL_STRICT_MODE=false">
    **If you explicitly set `DQL_STRICT_MODE=false` in v4**, no changes are required.

    v5 uses `DQL_STRICT_MODE=false` as the default, so your existing DQL queries will behave identically. You can upgrade freely.

    **Optional**: You can remove the explicit `ALTER SYSTEM SET DQL_STRICT_MODE = false` statement in v5 since this is now the default behavior.

    <CodeGroup>
      ```java Java (v5) theme={null}
      DittoConfig config = new DittoConfig.Builder("your-database-id")
          .connect(new DittoConfig.Connect.Server("https://your-server.ditto.live"))
          .build();
      Ditto ditto = DittoFactory.create(config);

      // No need to set DQL_STRICT_MODE - false is now the default
      ditto.getSync().start();
      ```

      ```java Java (v4 - Your Current Setup) theme={null}
      Ditto ditto = new Ditto(
          new DittoIdentity.OnlinePlayground(
              context,
              "your-app-id",
              "your-token",
              false,
              "https://your-server.ditto.live"
          )
      );

      // You explicitly set this in v4
      ditto.getStore().execute("ALTER SYSTEM SET DQL_STRICT_MODE = false", null);

      ditto.startSync();
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="Currently Using Legacy Query Builder">
    The Legacy Query Builder has been removed in v5. All queries must be converted to DQL before upgrading.

    **Good news**: Legacy Query Builder functionality has 1:1 support with `DQL_STRICT_MODE=false` (the v5 default), making migrations straightforward.

    **Migration Steps**:

    1. **In v4: Set `DQL_STRICT_MODE=false`** after initialization:
           <CodeGroup>
             ```java Java (v4 - Enable Non-Strict Mode) theme={null}
             Ditto ditto = new Ditto(
                 new DittoIdentity.OnlinePlayground(
                     context,
                     "your-app-id",
                     "your-token",
                     false,
                     "https://your-server.ditto.live"
                 )
             );

             // Set DQL_STRICT_MODE to false for Query Builder compatibility
             ditto.getStore().execute("ALTER SYSTEM SET DQL_STRICT_MODE = false", null);

             ditto.startSync();
             ```
           </CodeGroup>

    2. **Convert all queries from Legacy Query Builder to DQL**
       See the [Java Legacy→DQL Migration Guide](./java-dql) for detailed conversion examples.

    3. **Upgrade to v5**
       No DQL configuration changes required—v5 defaults to `DQL_STRICT_MODE=false`.
  </Accordion>
</AccordionGroup>

### Default Persistence Directory

v5 includes the database ID in the default directory name: `ditto-{databaseId}` instead of `ditto`

**To maintain v4 compatibility:**

```java theme={null}
DittoConfig config = new DittoConfig.Builder("your-database-id")
    .connect(new DittoConfig.Connect.Server("https://your-server.ditto.live"))
    .persistenceDirectory("/app/data/ditto")  // Old v4 path
    .build();
```

### API Changes

* `ditto.startSync()` → `ditto.getSync().start()`
* `ditto.stopSync()` → `ditto.getSync().stop()`
* `ditto.isSyncActive()` → `ditto.getSync().isActive()`
* `getPeerKeyString()` → `getPeerKey()`
* `isConnectedToDittoCloud()` → `isConnectedToDittoServer()`
* `new Ditto()` → `DittoFactory.create(config)`

***

## Migration Checklist

### Initialization

* [ ] Replace `new Ditto()` with `DittoFactory.create(config)`
* [ ] Create `DittoConfig` with `databaseId` and `connect` mode
* [ ] Update `.OnlinePlayground` → `new DittoConfig.Connect.Server(...)`
* [ ] Update `.OfflinePlayground` → `new DittoConfig.Connect.SmallPeersOnly(null)`
* [ ] Remove `updateTransportConfig` calls
* [ ] Remove `disableSyncWithV3()` calls
* [ ] **Set `DQL_STRICT_MODE=true` BEFORE starting sync if maintaining v4 behavior**

### Authentication

* [ ] Set `setExpirationHandler` callback after initialization
* [ ] Use `DittoAuthenticationProvider.development()` for playground tokens
* [ ] Remove authentication from identity configuration

### Sync API

* [ ] Update `ditto.startSync()` → `ditto.getSync().start()`
* [ ] Update `ditto.stopSync()` → `ditto.getSync().stop()`
* [ ] Remove `disableSyncWithV3()` calls

### Breaking Changes

* [ ] Set `persistenceDirectory` if maintaining v4 directory structure
* [ ] Update `getPeerKeyString()` → `getPeerKey()`
* [ ] Update `isConnectedToDittoCloud()` → `isConnectedToDittoServer()`

### Verification

* [ ] Build compiles with zero errors
* [ ] No deprecated API warnings
* [ ] Observers update data immediately
* [ ] Authentication works before sync starts
* [ ] No memory leaks
