# Databricks Isv Java Sdk

> PWAF-compliant Databricks SDK for Java (databricks-sdk-java): PAT, OAuth M2M, U2M custom OAuth app, UserAgent.withProduct/withPartner. Use when building or testing Java SDK workspace API integrations.

- Skill: `databricks-solutions/databricks-isv-java-sdk` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add databricks-solutions/databricks-isv-java-sdk`
- Raw SKILL.md: https://api.skillmd.com/api/skills/databricks-solutions/databricks-isv-java-sdk/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: databricks-solutions (https://skillmd.com/u/databricks-solutions)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/databricks-solutions/databricks-isv-java-sdk

---


<!-- skill-version: 1.0.0 -->

# Databricks SDK for Java (ISV)

Use this skill when implementing or testing **Databricks SDK for Java** (`databricks-sdk-java`) integrations for PWAF-compliant workspace management, Unity Catalog, Jobs, and REST-style API access.

## PWAF Documentation Links

| Resource | URL |
|----------|-----|
| **PWAF Home** | https://databrickslabs.github.io/partner-architecture/ |
| **Authentication Best Practices** | https://databrickslabs.github.io/partner-architecture/isv-partners/lakehouse-patterns/access-auth/ |
| **OAuth M2M Guide** | https://databrickslabs.github.io/partner-architecture/isv-partners/lakehouse-patterns/access-auth/oauth-m2m |
| **OAuth U2M Guide** | https://databrickslabs.github.io/partner-architecture/isv-partners/lakehouse-patterns/access-auth/oauth-u2m |
| **User-Agent Telemetry** | https://databrickslabs.github.io/partner-architecture/isv-partners/telemetry-attribution/sdks |
| **SDK Docs** | https://docs.databricks.com/aws/en/dev-tools/sdk-java |
| **SDK GitHub** | https://github.com/databricks/databricks-sdk-java |

## Requirements

- **SDK:** `com.databricks:databricks-sdk-java:0.54.0` (or later) from Maven Central
- **Java:** 11+ required
- **Install:** Add to Maven `pom.xml` (see dependencies section below)

## Authentication Decision Guide

```
Which authentication method to use?

Production / automated workloads?
  → OAuth M2M (client credentials)  ✅ RECOMMENDED

User-interactive flows?
  → U2M External Browser (custom OAuth app)  ✅ SUPPORTED

Already have an OAuth access token?
  → U2M Token-Env  ✅ SUPPORTED

Local development/testing only?
  → PAT (Personal Access Token)  ⚠️ LIMITED
```

## Authentication Comparison

| Method | PWAF Status | Auto Token Refresh | Browser Required | Use Case |
|--------|-------------|-------------------|------------------|----------|
| PAT | ⚠️ Limited | No | No | Testing only |
| OAuth M2M | ✅ Recommended | Yes (SDK handles) | No | Production/automated |
| U2M Custom OAuth App | ✅ Supported | No | Yes | User-interactive (ISV) |
| U2M Token-Env | ✅ Supported | No | No | Headless/CI |

**Note:** These examples do NOT use the SDK's built-in `databricks-cli` OAuth app. ISV/partner applications should register custom OAuth apps in App connections for proper branding, audit trails, and scoped permissions.

## Auth Mapping (DatabricksConfig)

| Auth | DatabricksConfig Fields | Token Management |
|------|-------------------------|------------------|
| PAT | `.setHost()`, `.setToken()` | Manual (no refresh) |
| OAuth M2M | `.setHost()`, `.setClientId()`, `.setClientSecret()` | SDK handles token fetch + refresh |
| U2M Custom OAuth App | `.setHost()`, `.setAuthType("external-browser")`, `.setClientId()`, `.setClientSecret()`, `.setScopes()` | SDK opens browser with custom app |
| U2M Token-Env | `.setHost()`, `.setToken()` | Manual (pre-obtained token) |

## CLIENT_ID Distinction (CRITICAL)

| Variable | Purpose | Used By |
|----------|---------|---------|
| `DATABRICKS_CLIENT_ID` | M2M service principal UUID | OAuth M2M only |
| `DATABRICKS_U2M_CLIENT_ID` | Custom OAuth app client ID | U2M custom OAuth app |

**Using the wrong client_id causes:** `"OAuth application with client_id not available in Databricks account"`

## SDK vs REST APIs

- **No SQL warehouse needed:** The SDK uses REST APIs (e.g., UC Tables API at `/api/2.1/unity-catalog/tables/`). No `httpPath` or `DATABRICKS_HTTP_PATH` required.
- **Use cases:** Workspace management, Unity Catalog metadata, Jobs orchestration, Clusters, DBFS, etc.
- **For SQL queries:** Use the Databricks JDBC Driver (`databricks-jdbc`) with a SQL warehouse httpPath.

---

## User-Agent (Required per PWAF)

Call once before creating `WorkspaceClient`:

```java
import com.databricks.sdk.core.UserAgent;

UserAgent.withProduct("YourCompany_YourProduct", "1.0.0");
UserAgent.withPartner("YourCompany");
```

These are **static/global** registrations applied to all SDK HTTP requests in the JVM.

---

## Environment Variables Reference

| Variable | Required For | Description |
|----------|-------------|-------------|
| `DATABRICKS_HOST` | All | Workspace URL (e.g., `https://myworkspace.cloud.databricks.com`) |
| `DATABRICKS_TOKEN` | PAT | Personal access token |
| `DATABRICKS_CLIENT_ID` | OAuth M2M | Service principal UUID |
| `DATABRICKS_CLIENT_SECRET` | OAuth M2M | Service principal OAuth secret |
| `DATABRICKS_U2M_CLIENT_ID` | U2M Custom OAuth | Custom OAuth app client ID from App connections |
| `DATABRICKS_U2M_CLIENT_SECRET` | U2M Custom OAuth | Custom OAuth app client secret (Java SDK requires this) |
| `DATABRICKS_REDIRECT_URI` | U2M Custom OAuth (optional) | Custom redirect URI (default: `http://localhost:8080/callback`) |
| `DATABRICKS_ACCESS_TOKEN` | U2M Token-Env | Pre-obtained OAuth access token |
| `APP_AUTH_TYPE` | Multi-auth | App-level auth selector: `pat`, `oauth_m2m`, `u2m_custom_oauth_app`, `u2m_token_env` |

**Important:** 
- Use `APP_AUTH_TYPE` (not `DATABRICKS_AUTH_TYPE`) because the SDK reads `DATABRICKS_AUTH_TYPE` internally.
- Do not mix M2M and U2M environment variables. Use `env -i` for clean test environments.

---

## Complete Examples

### PAT Authentication (Testing Only)

```java
import com.databricks.sdk.WorkspaceClient;
import com.databricks.sdk.core.DatabricksConfig;
import com.databricks.sdk.core.UserAgent;
import com.databricks.sdk.service.catalog.TableInfo;

public class SdkPatExample {
    public static void main(String[] args) {
        UserAgent.withProduct("YourCompany_YourProduct", "1.0.0");
        UserAgent.withPartner("YourCompany");

        String host = System.getenv("DATABRICKS_HOST");
        String token = System.getenv("DATABRICKS_TOKEN");

        DatabricksConfig config = new DatabricksConfig()
                .setHost(ensureScheme(host))
                .setToken(token);

        WorkspaceClient client = new WorkspaceClient(config);

        TableInfo table = client.tables().get("samples.nyctaxi.trips");
        System.out.println("PAT OK: " + table.getName() 
            + " (" + table.getColumns().size() + " columns)");
    }

    private static String ensureScheme(String host) {
        if (!host.startsWith("https://") && !host.startsWith("http://")) {
            return "https://" + host;
        }
        return host;
    }
}
```

**Env vars:** `DATABRICKS_HOST`, `DATABRICKS_TOKEN`

---

### OAuth M2M Authentication (Production Recommended)

```java
import com.databricks.sdk.WorkspaceClient;
import com.databricks.sdk.core.DatabricksConfig;
import com.databricks.sdk.core.UserAgent;
import com.databricks.sdk.service.catalog.TableInfo;

public class SdkOAuthM2MExample {
    public static void main(String[] args) {
        UserAgent.withProduct("YourCompany_YourProduct", "1.0.0");
        UserAgent.withPartner("YourCompany");

        String host = System.getenv("DATABRICKS_HOST");
        String clientId = System.getenv("DATABRICKS_CLIENT_ID");
        String clientSecret = System.getenv("DATABRICKS_CLIENT_SECRET");

        DatabricksConfig config = new DatabricksConfig()
                .setHost(ensureScheme(host))
                .setClientId(clientId)
                .setClientSecret(clientSecret);

        WorkspaceClient client = new WorkspaceClient(config);

        TableInfo table = client.tables().get("samples.nyctaxi.trips");
        System.out.println("OAuth M2M OK: " + table.getName() 
            + " (" + table.getColumns().size() + " columns)");
    }

    private static String ensureScheme(String host) {
        if (!host.startsWith("https://") && !host.startsWith("http://")) {
            return "https://" + host;
        }
        return host;
    }
}
```

**Env vars:** `DATABRICKS_HOST`, `DATABRICKS_CLIENT_ID`, `DATABRICKS_CLIENT_SECRET`

**How it works:**
1. SDK POSTs to `https://<host>/oidc/v1/token` with `grant_type=client_credentials`
2. Receives `access_token` and expiry
3. Attaches token as `Authorization: Bearer <token>` on every REST call
4. Refreshes automatically before expiry

**Setup:** Create service principal in Account Console → Settings → Service principals. Generate OAuth secret.

---

### U2M Custom OAuth App

Uses a custom OAuth app registered in App connections:

```java
import com.databricks.sdk.WorkspaceClient;
import com.databricks.sdk.core.DatabricksConfig;
import com.databricks.sdk.core.UserAgent;
import com.databricks.sdk.service.catalog.TableInfo;
import java.util.Arrays;

public class SdkU2MCustomOAuthAppExample {
    public static void main(String[] args) {
        UserAgent.withProduct("YourCompany_YourProduct", "1.0.0");
        UserAgent.withPartner("YourCompany");

        String host = System.getenv("DATABRICKS_HOST");
        String clientId = System.getenv("DATABRICKS_U2M_CLIENT_ID");
        String clientSecret = System.getenv("DATABRICKS_U2M_CLIENT_SECRET"); // required by Java SDK
        String redirectUri = System.getenv("DATABRICKS_REDIRECT_URI");

        DatabricksConfig config = new DatabricksConfig()
                .setHost(ensureScheme(host))
                .setAuthType("external-browser")
                .setClientId(clientId)
                .setScopes(Arrays.asList("all-apis"));

        if (clientSecret != null && !clientSecret.isBlank()) {
            config.setClientSecret(clientSecret);
        }
        if (redirectUri != null && !redirectUri.isBlank()) {
            config.setOAuthRedirectUrl(redirectUri);
        }

        config.resolve();

        WorkspaceClient client = new WorkspaceClient(config);

        TableInfo table = client.tables().get("samples.nyctaxi.trips");
        System.out.println("U2M Custom OAuth App OK: " + table.getName());
    }

    private static String ensureScheme(String host) {
        if (!host.startsWith("https://") && !host.startsWith("http://")) {
            return "https://" + host;
        }
        return host;
    }
}
```

**Env vars:** `DATABRICKS_HOST`, `DATABRICKS_U2M_CLIENT_ID`

**Recommended:** `DATABRICKS_U2M_CLIENT_SECRET` (required by Java SDK for proper auth configuration)

**Optional:** `DATABRICKS_REDIRECT_URI` (must match App connections registration)

**CRITICAL:** Use `DATABRICKS_U2M_CLIENT_ID` for custom OAuth apps — NOT `DATABRICKS_CLIENT_ID` (that's for M2M).

**Note:** Testing showed the Java SDK requires `DATABRICKS_U2M_CLIENT_SECRET` for custom OAuth apps to properly configure external-browser auth. Without it, you may get `"cannot configure default credentials"` errors.

---

### U2M Token-Env (Pre-obtained Token)

```java
import com.databricks.sdk.WorkspaceClient;
import com.databricks.sdk.core.DatabricksConfig;
import com.databricks.sdk.core.UserAgent;
import com.databricks.sdk.service.catalog.TableInfo;

public class SdkU2MTokenEnvExample {
    public static void main(String[] args) {
        UserAgent.withProduct("YourCompany_YourProduct", "1.0.0");
        UserAgent.withPartner("YourCompany");

        String host = System.getenv("DATABRICKS_HOST");
        String accessToken = System.getenv("DATABRICKS_ACCESS_TOKEN");
        if (accessToken == null || accessToken.isBlank()) {
            accessToken = System.getenv("DATABRICKS_TOKEN");
        }

        DatabricksConfig config = new DatabricksConfig()
                .setHost(ensureScheme(host))
                .setToken(accessToken);

        WorkspaceClient client = new WorkspaceClient(config);

        TableInfo table = client.tables().get("samples.nyctaxi.trips");
        System.out.println("U2M Token-Env OK: " + table.getName());
    }

    private static String ensureScheme(String host) {
        if (!host.startsWith("https://") && !host.startsWith("http://")) {
            return "https://" + host;
        }
        return host;
    }
}
```

**Env vars:** `DATABRICKS_HOST`, `DATABRICKS_ACCESS_TOKEN` (or `DATABRICKS_TOKEN`)

---

## Token Refresh for U2M Flows

U2M access tokens expire after approximately 1 hour. For long-running applications, you need to handle token refresh.

### Token Lifetime Summary

| Auth Type | Token TTL | Refresh Strategy |
|-----------|-----------|------------------|
| PAT | User-configured (90 days default) | Generate new token manually |
| OAuth M2M | ~1 hour | SDK handles automatically |
| U2M Custom OAuth App | ~1 hour | Re-authenticate or use refresh_token |
| U2M Token-Env | ~1 hour | Application must handle |

### SDK Token Refresh Pattern (Recommended)

The SDK's `DatabricksConfig.authenticate()` method automatically handles token refresh when using persistent configuration. For single-shot scripts, re-run the browser flow when tokens expire:

```java
public class TokenRefreshExample {
    private DatabricksConfig config;
    private WorkspaceClient client;
    private long tokenExpiresAt;
    private static final long REFRESH_BUFFER_MS = 5 * 60 * 1000; // 5 minutes

    public TokenRefreshExample() {
        this.config = createConfig();
        this.client = new WorkspaceClient(config);
        this.tokenExpiresAt = System.currentTimeMillis() + (55 * 60 * 1000); // ~55 min
    }

    private DatabricksConfig createConfig() {
        String host = System.getenv("DATABRICKS_HOST");
        String clientId = System.getenv("DATABRICKS_U2M_CLIENT_ID");
        String clientSecret = System.getenv("DATABRICKS_U2M_CLIENT_SECRET");

        DatabricksConfig cfg = new DatabricksConfig()
                .setHost(host.startsWith("https://") ? host : "https://" + host)
                .setAuthType("external-browser")
                .setClientId(clientId)
                .setClientSecret(clientSecret)
                .setScopes(Arrays.asList("all-apis", "offline_access")); // offline_access for refresh_token

        cfg.resolve();
        return cfg;
    }

    public WorkspaceClient getClient() {
        if (System.currentTimeMillis() > (tokenExpiresAt - REFRESH_BUFFER_MS)) {
            // Token nearing expiration, re-authenticate
            this.config = createConfig();
            this.client = new WorkspaceClient(config);
            this.tokenExpiresAt = System.currentTimeMillis() + (55 * 60 * 1000);
        }
        return client;
    }
}
```

### Manual Refresh Token Flow (Advanced)

For applications that need to refresh without user interaction, request `offline_access` scope to receive a `refresh_token`:

```java
// When obtaining initial token, include offline_access scope
config.setScopes(Arrays.asList("all-apis", "offline_access"));

// The SDK caches the refresh_token in ~/.databricks/token-cache.json
// On subsequent runs, the SDK will automatically use the refresh_token
// if the access_token is expired but refresh_token is still valid
```

**Important:** The `offline_access` scope must be enabled for your OAuth app in App connections.

### Best Practices

1. **Request `offline_access`:** Include this scope for long-running applications
2. **Check expiration before operations:** Avoid mid-operation failures
3. **Handle refresh failures gracefully:** Fall back to re-authentication
4. **Don't cache tokens in code:** Use SDK token cache or secure storage

---

## Environment Variables Reference

| Variable | Used By | Description |
|----------|---------|-------------|
| `DATABRICKS_HOST` | All | Workspace URL |
| `DATABRICKS_TOKEN` | PAT, U2M token-env | Personal access token or pre-obtained OAuth token |
| `DATABRICKS_CLIENT_ID` | OAuth M2M | Service principal UUID |
| `DATABRICKS_CLIENT_SECRET` | OAuth M2M | Service principal OAuth secret |
| `DATABRICKS_U2M_CLIENT_ID` | U2M Custom OAuth | Custom OAuth app client ID |
| `DATABRICKS_U2M_CLIENT_SECRET` | U2M Custom OAuth | Custom OAuth app secret (if confidential) |
| `DATABRICKS_REDIRECT_URI` | U2M Custom OAuth | Redirect URI (default: `http://localhost:8080/callback`) |
| `APP_AUTH_TYPE` | Multi-auth binary | App-level auth type selector (NOT `DATABRICKS_AUTH_TYPE`) |

---

## Required Imports / Maven Dependencies

```xml
<!-- Databricks SDK for Java -->
<dependency>
    <groupId>com.databricks</groupId>
    <artifactId>databricks-sdk-java</artifactId>
    <version>0.54.0</version>
</dependency>

<!-- Suppress SDK logging (optional) -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-nop</artifactId>
    <version>2.0.16</version>
</dependency>
```

Pin to specific minor version (SDK is in Beta). Java 11+ required.

---

## Multi-Auth Pattern

Support multiple auth types in a single binary using a custom selector (e.g., `APP_AUTH_TYPE`):

```java
String authType = System.getenv("APP_AUTH_TYPE");
if (authType == null || authType.isBlank()) authType = "oauth_m2m";

DatabricksConfig config = new DatabricksConfig()
        .setHost(ensureScheme(host));

switch (authType.toLowerCase()) {
    case "pat":
        config.setToken(System.getenv("DATABRICKS_TOKEN"));
        break;
    case "oauth_m2m":
        config.setClientId(System.getenv("DATABRICKS_CLIENT_ID"));
        config.setClientSecret(System.getenv("DATABRICKS_CLIENT_SECRET"));
        break;
    case "u2m_custom_oauth_app":
        config.setAuthType("external-browser");
        config.setClientId(System.getenv("DATABRICKS_U2M_CLIENT_ID"));
        config.setClientSecret(System.getenv("DATABRICKS_U2M_CLIENT_SECRET"));
        config.setScopes(Arrays.asList("all-apis"));
        String redirect = System.getenv("DATABRICKS_REDIRECT_URI");
        if (redirect != null && !redirect.isBlank()) config.setOAuthRedirectUrl(redirect);
        config.resolve();
        break;
    case "u2m_token_env":
        String token = System.getenv("DATABRICKS_ACCESS_TOKEN");
        if (token == null || token.isBlank()) token = System.getenv("DATABRICKS_TOKEN");
        config.setToken(token);
        break;
}

WorkspaceClient client = new WorkspaceClient(config);
```

| `APP_AUTH_TYPE` | Auth Flow | Env Vars Required |
|-----------------|-----------|-------------------|
| `pat` | Personal Access Token | `DATABRICKS_TOKEN` |
| `oauth_m2m` (default) | OAuth M2M (client credentials) | `DATABRICKS_CLIENT_ID`, `DATABRICKS_CLIENT_SECRET` |
| `u2m_custom_oauth_app` | U2M external browser (custom app) | `DATABRICKS_U2M_CLIENT_ID`, `DATABRICKS_U2M_CLIENT_SECRET` |
| `u2m_token_env` | U2M pre-obtained token | `DATABRICKS_ACCESS_TOKEN` or `DATABRICKS_TOKEN` |

**CRITICAL:** Use `APP_AUTH_TYPE` (not `DATABRICKS_AUTH_TYPE`). The SDK reads `DATABRICKS_AUTH_TYPE` internally.

---

## Implementation Learnings (Java SDK v0.54.0)

Key findings from testing that differ from documentation or other SDKs:

### 1. U2M Custom OAuth App Requires Client Secret

Unlike some other SDKs, the Java SDK requires `DATABRICKS_U2M_CLIENT_SECRET` even for public OAuth apps:

```java
// REQUIRED: Both client ID and secret must be set
config.setClientId(System.getenv("DATABRICKS_U2M_CLIENT_ID"));
config.setClientSecret(System.getenv("DATABRICKS_U2M_CLIENT_SECRET")); // Required!
```

Without client secret, you get: `"cannot configure default credentials"`

### 2. SDK Reads `DATABRICKS_AUTH_TYPE` Internally

The SDK uses `DATABRICKS_AUTH_TYPE` internally. If you set this environment variable, it may conflict with programmatic config. Use a custom variable like `APP_AUTH_TYPE` for your own auth type selection.

### 3. `config.resolve()` Must Be Called

For external-browser auth, `config.resolve()` must be called after configuration but before creating `WorkspaceClient`:

```java
DatabricksConfig config = new DatabricksConfig()
        .setHost(host)
        .setAuthType("external-browser")
        .setClientId(clientId)
        .setClientSecret(clientSecret)
        .setScopes(Arrays.asList("all-apis"));

config.resolve();  // REQUIRED: initializes HTTP client

WorkspaceClient client = new WorkspaceClient(config);
```

---

## SDK Workarounds Summary

| Workaround | Code | Without It |
|-----------|------|------------|
| Set scopes | `.setScopes(Arrays.asList("all-apis"))` | `NullPointerException` at `TokenCacheUtils` |
| Call resolve | `config.resolve()` before `WorkspaceClient` | `NullPointerException` at `Consent.<init>` |
| Set client secret | `.setClientSecret(...)` | `"cannot configure default credentials"` |
| Set redirect URL (if custom) | `.setOAuthRedirectUrl(...)` | Must match App connections registration |

> ⚠️ **WorkspaceClient resource cleanup (SDK 0.54)**
> `WorkspaceClient` does **not** implement `Closeable` or `AutoCloseable` in SDK 0.54.
> Do **not** call `workspaceClient.close()` — it causes a compile error: `cannot find symbol: method close()`.
> Correct pattern for a wrapper's `close()` method:
> ```java
> @Override
> public void close() {
>     workspaceClient = null;  // GC handles cleanup; no explicit close needed
> }
> ```

---

## Redirect URI Reference

| Scenario | Redirect URI | Notes |
|----------|-------------|-------|
| Custom OAuth app (default) | `http://localhost:8080/callback` | Java SDK default |
| Custom OAuth app (configured) | Via `DATABRICKS_REDIRECT_URI` | Must match App connections registration |

---

## Validation Query

Verify User-Agent telemetry is being recorded correctly:

```sql
SELECT event_time, user_agent, action_name, request_params
FROM system.access.audit
WHERE event_time > current_timestamp() - INTERVAL 1 HOUR
  AND lower(user_agent) LIKE '%yourcompany%'
ORDER BY event_time DESC
LIMIT 10;
```

---

## U2M Browser Environment Requirements

When using `env -i` for U2M browser flows, include these variables for Desktop.browse() to work:

```bash
# Essential for browser launch
HOME="$HOME"       # Required: token cache location
PATH="$PATH"       # Required: find java, browser binaries
JAVA_HOME="$JAVA_HOME"  # Required: find java

# For macOS (Desktop.browse() typically works)
# For Linux, may also need:
DISPLAY="$DISPLAY"   # For X11-based browser launch
```

---

## Auth Isolation

Run tests with a **clean environment** using `env -i`:

```bash
# PAT test
env -i HOME="$HOME" PATH="$PATH" JAVA_HOME="$JAVA_HOME" \
  DATABRICKS_HOST="$DATABRICKS_HOST" \
  DATABRICKS_TOKEN="$DATABRICKS_TOKEN" \
  APP_AUTH_TYPE=pat \
  mvn exec:java -Dexec.mainClass="com.example.SdkPatExample"

# OAuth M2M test
env -i HOME="$HOME" PATH="$PATH" JAVA_HOME="$JAVA_HOME" \
  DATABRICKS_HOST="$DATABRICKS_HOST" \
  DATABRICKS_CLIENT_ID="$DATABRICKS_CLIENT_ID" \
  DATABRICKS_CLIENT_SECRET="$DATABRICKS_CLIENT_SECRET" \
  APP_AUTH_TYPE=oauth_m2m \
  mvn exec:java -Dexec.mainClass="com.example.SdkOAuthM2MExample"

# U2M Custom OAuth App test (include client secret per Java SDK requirements)
env -i HOME="$HOME" PATH="$PATH" JAVA_HOME="$JAVA_HOME" \
  DATABRICKS_HOST="$DATABRICKS_HOST" \
  DATABRICKS_U2M_CLIENT_ID="$DATABRICKS_U2M_CLIENT_ID" \
  DATABRICKS_U2M_CLIENT_SECRET="$DATABRICKS_U2M_CLIENT_SECRET" \
  DATABRICKS_REDIRECT_URI="$DATABRICKS_REDIRECT_URI" \
  APP_AUTH_TYPE=u2m_custom_oauth_app \
  mvn exec:java -Dexec.mainClass="com.example.SdkU2MCustomOAuthAppExample"

# U2M Token-Env test
env -i HOME="$HOME" PATH="$PATH" JAVA_HOME="$JAVA_HOME" \
  DATABRICKS_HOST="$DATABRICKS_HOST" \
  DATABRICKS_ACCESS_TOKEN="$DATABRICKS_ACCESS_TOKEN" \
  APP_AUTH_TYPE=u2m_token_env \
  mvn exec:java -Dexec.mainClass="com.example.SdkU2MTokenEnvExample"
```

---

## Error Handling Patterns

Production applications should handle authentication and API errors gracefully.

### Basic Error Handling

```java
import com.databricks.sdk.WorkspaceClient;
import com.databricks.sdk.core.DatabricksConfig;
import com.databricks.sdk.core.DatabricksException;
import com.databricks.sdk.service.catalog.TableInfo;

public class SdkErrorHandlingExample {
    public static void main(String[] args) {
        try {
            WorkspaceClient client = createClient();
            TableInfo table = client.tables().get("samples.nyctaxi.trips");
            System.out.println("Success: " + table.getName());
        } catch (DatabricksException e) {
            handleDatabricksError(e);
        } catch (Exception e) {
            handleGeneralError(e);
        }
    }

    private static WorkspaceClient createClient() {
        String host = System.getenv("DATABRICKS_HOST");
        String clientId = System.getenv("DATABRICKS_CLIENT_ID");
        String clientSecret = System.getenv("DATABRICKS_CLIENT_SECRET");

        if (host == null || host.isBlank()) {
            throw new IllegalStateException("DATABRICKS_HOST is required");
        }
        if (clientId == null || clientId.isBlank()) {
            throw new IllegalStateException("DATABRICKS_CLIENT_ID is required");
        }
        if (clientSecret == null || clientSecret.isBlank()) {
            throw new IllegalStateException("DATABRICKS_CLIENT_SECRET is required");
        }

        DatabricksConfig config = new DatabricksConfig()
                .setHost(host.startsWith("https://") ? host : "https://" + host)
                .setClientId(clientId)
                .setClientSecret(clientSecret);

        return new WorkspaceClient(config);
    }

    private static void handleDatabricksError(DatabricksException e) {
        String message = e.getMessage();

        if (message.contains("INVALID_PARAMETER_VALUE") || message.contains("invalid_client")) {
            System.err.println("Authentication failed: Invalid credentials");
            System.err.println("  - Verify DATABRICKS_CLIENT_ID is correct");
            System.err.println("  - Verify DATABRICKS_CLIENT_SECRET is correct");
        } else if (message.contains("PERMISSION_DENIED") || message.contains("403")) {
            System.err.println("Authorization failed: Insufficient permissions");
            System.err.println("  - Verify service principal has required permissions");
        } else if (message.contains("NOT_FOUND") || message.contains("404")) {
            System.err.println("Resource not found");
        } else if (message.contains("TEMPORARILY_UNAVAILABLE") || message.contains("503")) {
            System.err.println("Service temporarily unavailable - retry with backoff");
        } else {
            System.err.println("Databricks API error: " + message);
        }
    }

    private static void handleGeneralError(Exception e) {
        String message = e.getMessage();

        if (e instanceof java.net.UnknownHostException) {
            System.err.println("Network error: Cannot resolve host");
            System.err.println("  - Verify DATABRICKS_HOST is correct");
            System.err.println("  - Check network connectivity");
        } else if (e instanceof java.net.SocketTimeoutException) {
            System.err.println("Network timeout - retry with backoff");
        } else if (message != null && message.contains("cannot configure default credentials")) {
            System.err.println("Configuration error: Missing or invalid auth configuration");
            System.err.println("  - For M2M: Set DATABRICKS_CLIENT_ID and DATABRICKS_CLIENT_SECRET");
            System.err.println("  - For U2M: Set DATABRICKS_U2M_CLIENT_ID and DATABRICKS_U2M_CLIENT_SECRET");
        } else {
            System.err.println("Unexpected error: " + message);
            e.printStackTrace();
        }
    }
}
```

### Retry Pattern with Exponential Backoff

```java
public class RetryHelper {
    private static final int MAX_RETRIES = 3;
    private static final long INITIAL_BACKOFF_MS = 1000;

    public static <T> T withRetry(java.util.function.Supplier<T> operation) {
        int attempt = 0;
        while (true) {
            try {
                return operation.get();
            } catch (DatabricksException e) {
                if (!isRetryable(e) || attempt >= MAX_RETRIES) {
                    throw e;
                }
                long backoff = INITIAL_BACKOFF_MS * (long) Math.pow(2, attempt);
                System.err.println("Retry " + (attempt + 1) + "/" + MAX_RETRIES 
                    + " after " + backoff + "ms");
                try {
                    Thread.sleep(backoff);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    throw new RuntimeException("Interrupted during retry", ie);
                }
                attempt++;
            }
        }
    }

    private static boolean isRetryable(DatabricksException e) {
        String msg = e.getMessage();
        return msg.contains("TEMPORARILY_UNAVAILABLE") 
            || msg.contains("503")
            || msg.contains("429")  // Rate limited
            || msg.contains("RESOURCE_EXHAUSTED");
    }
}

// Usage:
// TableInfo table = RetryHelper.withRetry(() -> client.tables().get("catalog.schema.table"));
```

---

## Error Classification Reference

### HTTP Status Codes

| Status Code | Meaning | Retryable | Remediation |
|-------------|---------|-----------|-------------|
| `400` | Bad request | No | Fix request parameters |
| `401` | Unauthorized | No | Check credentials |
| `403` | Forbidden | No | Check permissions |
| `404` | Not found | No | Verify resource exists |
| `429` | Rate limited | Yes | Retry with exponential backoff |
| `500` | Internal server error | Yes | Retry with backoff |
| `503` | Service unavailable | Yes | Retry with backoff |

### DatabricksException Error Codes

| Error Code | Meaning | Retryable | Remediation |
|------------|---------|-----------|-------------|
| `INVALID_PARAMETER_VALUE` | Bad input | No | Fix request parameters |
| `PERMISSION_DENIED` | No access | No | Grant required permissions |
| `NOT_FOUND` | Resource missing | No | Verify resource path/ID |
| `TEMPORARILY_UNAVAILABLE` | Service down | Yes | Retry with backoff |
| `RESOURCE_EXHAUSTED` | Rate/quota limited | Yes | Retry with backoff |
| `DEADLINE_EXCEEDED` | Timeout | Yes | Retry with increased timeout |

### Retryable Error Detection

```java
private static boolean isRetryable(DatabricksException e) {
    String msg = e.getMessage();
    return msg.contains("TEMPORARILY_UNAVAILABLE") 
        || msg.contains("503")
        || msg.contains("429")
        || msg.contains("RESOURCE_EXHAUSTED")
        || msg.contains("DEADLINE_EXCEEDED")
        || msg.contains("Connection reset")
        || msg.contains("Read timed out");
}
```

---

## Troubleshooting

### Common Errors

| Error | Cause | Solution |
|-------|-------|----------|
| `more than one authorization method configured` | Multiple auth env vars set | Use `env -i` to pass only vars for one auth type |
| `default auth: cannot configure default credentials` | Incomplete config | Verify host + exactly one auth method's vars are set |
| `Unable to parse response` | Host misconfigured | Verify `DATABRICKS_HOST` is correct |
| `PERMISSION_DENIED` | Insufficient permissions | Grant service principal required permissions |

### OAuth-Specific Errors

| Error | Cause | Solution |
|-------|-------|----------|
| `OAuth application with client_id not available` | Using M2M client_id for U2M | Use `DATABRICKS_U2M_CLIENT_ID` (custom OAuth app) |
| `redirect_uri mismatch` | URI doesn't match registration | Verify URI in App connections matches `DATABRICKS_REDIRECT_URI` |
| `invalid_client` | Wrong client ID/secret | Verify credentials match OAuth app |

### SDK Errors

| Error | Cause | Solution |
|-------|-------|----------|
| `NullPointerException` at `TokenCacheUtils` | Scopes not set | `.setScopes(Arrays.asList("all-apis"))` |
| `NullPointerException` at `Consent.<init>` | HTTP client not initialized | `config.resolve()` before `WorkspaceClient` |
| `DATABRICKS_AUTH_TYPE conflict` | App-level var collides with SDK | Rename to `APP_AUTH_TYPE` |

### U2M Custom OAuth App Errors

| Error | Cause | Solution |
|-------|-------|----------|
| `cannot configure default credentials` | Missing U2M client secret | Include `DATABRICKS_U2M_CLIENT_SECRET` (Java SDK requires it) |
| `OAuth application with client_id not available` | Using M2M client_id | Use `DATABRICKS_U2M_CLIENT_ID` (custom OAuth app) |
| `redirect_uri mismatch` | URI doesn't match app registration | Verify URI in App connections matches `DATABRICKS_REDIRECT_URI` |

### Logging

| Error | Cause | Solution |
|-------|-------|----------|
| `SLF4J: No SLF4J providers were found` | Missing logging dependency | Add `slf4j-simple` or `slf4j-nop` to pom.xml |

---

## Host Normalization

The SDK accepts both `https://myworkspace.cloud.databricks.com` and `myworkspace.cloud.databricks.com`. Being explicit with `https://` is recommended:

```java
private static String ensureScheme(String host) {
    if (!host.startsWith("https://") && !host.startsWith("http://")) {
        return "https://" + host;
    }
    return host;
}
```

---

## Key Differences from Databricks JDBC Driver

| Aspect | Databricks SDK for Java | Databricks JDBC Driver |
|--------|-------------------------|------------------------|
| **Purpose** | REST APIs (UC, Jobs, Clusters) | SQL queries via warehouse |
| **SQL Warehouse** | Not needed | Required (`httpPath`) |
| **Auth Config** | `DatabricksConfig{}` setters | JDBC URL parameters (`AuthMech`, `Auth_Flow`) |
| **User-Agent** | `UserAgent.withProduct()` (global) | `UserAgentEntry` property (per-connection) |
| **Token Refresh** | SDK handles OAuth M2M refresh automatically | Driver handles M2M refresh |
| **Java 17+** | No special flags needed | Needs `--add-opens` for Arrow |
| **Auth Type Selector** | Use `APP_AUTH_TYPE` (SDK reads `DATABRICKS_AUTH_TYPE` internally) | Can use `DATABRICKS_AUTH_TYPE` |

