← Files Azure Cosmos DBARCHIVED FILE

skills/cosmosdb-best-practices/rules/sdk-java-cosmos-config.md

10.3 KB · Oct 4, 2026 · 12:19 UTC

↓ Download file

---
title: Use dependent @Bean methods for Cosmos DB initialization in Spring Boot
impact: HIGH
impactDescription: prevents circular dependency, startup failures, class name collisions, and compile errors
tags: sdk, java, spring-boot, configuration, cosmos-config, bean, postconstruct, AbstractCosmosConfiguration
---

## Use Dependent @Bean Methods for Cosmos DB Initialization in Spring Boot

When configuring `CosmosClient`, `CosmosDatabase`, and `CosmosContainer` beans in a Spring Boot `@Configuration` class, use dependent `@Bean` methods with parameter injection instead of `@PostConstruct`. Calling a `@Bean` method from `@PostConstruct` in the same class creates a circular dependency that crashes the application on startup.

Follow these additional rules to avoid common startup failures:

1. **Do not name your configuration class `CosmosConfig`.** This collides with `com.azure.spring.data.cosmos.config.CosmosConfig` in the Spring Data Cosmos SDK, causing cascading compile errors. Use `CosmosDbConfig`, `CosmosConfiguration`, or `AppCosmosConfig` instead.

2. **Always call `createDatabaseIfNotExists()` before `createContainerIfNotExists()`.** On a fresh Cosmos DB instance (including the emulator), the database does not exist. Calling `createContainerIfNotExists()` without first ensuring the database exists throws `CosmosException: NotFound`.

3. **When extending `AbstractCosmosConfiguration`, do not annotate `cosmosClientBuilder()` with `@Override`.** It is not declared as overridable in `AbstractCosmosConfiguration`. Provide it as a `@Bean` method instead. The only method you should override is `getDatabaseName()`.

**Incorrect (@PostConstruct calling @Bean — circular dependency):**

```java
// ❌ Anti-pattern: @PostConstruct + @Bean in same class causes circular dependency
@Configuration
public class CosmosDbConfig {

    @Value("${azure.cosmos.endpoint}")
    private String endpoint;

    @Value("${azure.cosmos.key}")
    private String key;

    @Bean
    public CosmosClient cosmosClient() {
        return new CosmosClientBuilder()
            .endpoint(endpoint)
            .key(key)
            .consistencyLevel(ConsistencyLevel.SESSION)
            .buildClient();
    }

    @PostConstruct  // ❌ This calls cosmosClient() which is a @Bean — circular!
    public void initializeDatabase() {
        CosmosClient client = cosmosClient(); // Triggers proxy interception loop
        client.createDatabaseIfNotExists("mydb");
        CosmosDatabase db = client.getDatabase("mydb");
        db.createContainerIfNotExists(
            new CosmosContainerProperties("items", "/partitionKey"),
            ThroughputProperties.createAutoscaledThroughput(4000));
    }

    @Bean
    public CosmosDatabase cosmosDatabase() {
        return cosmosClient().getDatabase("mydb");
    }

    @Bean
    public CosmosContainer cosmosContainer() {
        return cosmosDatabase().getContainer("items");
    }
}
// Runtime error: BeanCurrentlyInCreationException — circular dependency detected
```

**Correct (dependent @Bean chain with parameter injection):**

```java
// ✅ Correct: Use @Bean dependency injection chain — initialization in bean methods
@Configuration
public class CosmosDbConfig {

    @Value("${azure.cosmos.endpoint}")
    private String endpoint;

    @Value("${azure.cosmos.key}")
    private String key;

    @Value("${azure.cosmos.database}")
    private String databaseName;

    @Value("${azure.cosmos.container}")
    private String containerName;

    @Bean(destroyMethod = "close")
    public CosmosClient cosmosClient() {
        DirectConnectionConfig directConfig = DirectConnectionConfig.getDefaultConfig();
        GatewayConnectionConfig gatewayConfig = GatewayConnectionConfig.getDefaultConfig();

        // Use Gateway for emulator, Direct for production
        CosmosClientBuilder builder = new CosmosClientBuilder()
            .endpoint(endpoint)
            .key(key)
            .consistencyLevel(ConsistencyLevel.SESSION)
            .contentResponseOnWriteEnabled(true);

        if (endpoint.contains("localhost") || endpoint.contains("127.0.0.1")) {
            builder.gatewayMode(gatewayConfig);
        } else {
            builder.directMode(directConfig);
        }

        return builder.buildClient();
    }

    @Bean  // ✅ Spring injects cosmosClient from the bean above
    public CosmosDatabase cosmosDatabase(CosmosClient cosmosClient) {
        // Database initialization happens here — no @PostConstruct needed
        cosmosClient.createDatabaseIfNotExists(databaseName);
        return cosmosClient.getDatabase(databaseName);
    }

    @Bean  // ✅ Spring injects cosmosDatabase from the bean above
    public CosmosContainer cosmosContainer(CosmosDatabase cosmosDatabase) {
        CosmosContainerProperties props = new CosmosContainerProperties(
            containerName, "/partitionKey");

        cosmosDatabase.createContainerIfNotExists(
            props,
            ThroughputProperties.createAutoscaledThroughput(4000));

        return cosmosDatabase.getContainer(containerName);
    }
}
```

**Why this works:**
- Spring resolves the dependency graph: `cosmosClient()` → `cosmosDatabase(CosmosClient)` → `cosmosContainer(CosmosDatabase)`
- Database and container creation happens naturally during bean initialization
- No circular reference because each method receives its dependency as a parameter
- `destroyMethod = "close"` ensures `CosmosClient` is properly shut down

**With Hierarchical Partition Keys:**

```java
@Bean
public CosmosContainer cosmosContainer(CosmosDatabase cosmosDatabase) {
    // Hierarchical partition key definition
    List<String> partitionKeyPaths = Arrays.asList(
        "/tenantId", "/type", "/projectId");

    CosmosContainerProperties props = new CosmosContainerProperties(
        containerName,
        partitionKeyPaths,
        PartitionKeyDefinitionVersion.V2,
        PartitionKind.MULTI_HASH);

    cosmosDatabase.createContainerIfNotExists(
        props,
        ThroughputProperties.createAutoscaledThroughput(4000));

    return cosmosDatabase.getContainer(containerName);
}
```

**Alternative: `SmartInitializingSingleton` for post-init logic:**

```java
// If you need to run logic AFTER all beans are created
@Bean
public SmartInitializingSingleton cosmosInitializer(CosmosContainer container) {
    return () -> {
        // Seed data, verify connectivity, warm up, etc.
        logger.info("Cosmos container ready: {}", container.getId());
    };
}
```

**Common mistake: Missing `createDatabaseIfNotExists()` before container creation:**

```java
// ❌ Crashes on a fresh Cosmos DB instance — database doesn't exist yet
@EventListener(ApplicationReadyEvent.class)
public void initializeCosmosDb() {
    CosmosAsyncClient client = cosmosAsyncClient();
    CosmosAsyncDatabase db = client.getDatabase(databaseName);
    db.createContainerIfNotExists(containerName,
        "/partitionKey").block();  // CosmosException: Database not found
}
```

```java
// ✅ Always create the database first
@EventListener(ApplicationReadyEvent.class)
public void initializeCosmosDb() {
    CosmosAsyncClient client = cosmosAsyncClient();
    client.createDatabaseIfNotExists(databaseName).block();  // ← required
    CosmosAsyncDatabase db = client.getDatabase(databaseName);
    db.createContainerIfNotExists(containerName,
        "/partitionKey").block();
}
```

**When extending `AbstractCosmosConfiguration`:**

```java
// ❌ cosmosClientBuilder() is not overridable — compile error
@Configuration
@EnableCosmosRepositories
public class CosmosDbConfig extends AbstractCosmosConfiguration {

    @Override  // ❌ "method does not override or implement a method from a supertype"
    public CosmosClientBuilder cosmosClientBuilder() {
        return new CosmosClientBuilder()
            .endpoint(endpoint)
            .key(key);
    }

    @Override
    protected String getDatabaseName() {
        return databaseName;
    }
}
```

```java
// ✅ Provide cosmosClientBuilder() as a @Bean, only override getDatabaseName()
@Configuration
@EnableCosmosRepositories
public class CosmosDbConfig extends AbstractCosmosConfiguration {

    @Bean  // ✅ Not an override — declare as a bean
    public CosmosClientBuilder cosmosClientBuilder() {
        return new CosmosClientBuilder()
            .endpoint(endpoint)
            .key(key)
            .consistencyLevel(ConsistencyLevel.SESSION)
            .contentResponseOnWriteEnabled(true);
    }

    @Override  // ✅ getDatabaseName() is the only overridable method
    protected String getDatabaseName() {
        return databaseName;
    }
}
```

**Key Points:**
- Never call `@Bean` methods from `@PostConstruct` in the same `@Configuration` class
- Use parameter injection in `@Bean` methods to express initialization order
- Always set `destroyMethod = "close"` on `CosmosClient` bean
- Keep `CosmosClient` as a singleton `@Bean` (Rule 4.16)
- Set `contentResponseOnWriteEnabled(true)` in the builder (Rule 4.9)
- Do not name your configuration class `CosmosConfig` — it collides with `com.azure.spring.data.cosmos.config.CosmosConfig`
- Always call `createDatabaseIfNotExists()` before `createContainerIfNotExists()`
- When extending `AbstractCosmosConfiguration`, use `@Bean` (not `@Override`) on `cosmosClientBuilder()`

**Global Jackson fallback for Cosmos system metadata:**

When entity classes miss `@JsonIgnoreProperties(ignoreUnknown = true)`, reads can fail with `UnrecognizedPropertyException` on Cosmos system fields (for example `_rid`, `_self`, `_etag`, `_ts`). Add a global fallback in Spring Boot:

```yaml
spring:
    jackson:
        deserialization:
            fail-on-unknown-properties: false
```

This is a defense-in-depth safety net and does not replace correct entity annotations.

References:
- [Spring Framework @Bean documentation](https://docs.spring.io/spring-framework/reference/core/beans/java/bean-annotation.html)
- [`CosmosAsyncClient.createDatabaseIfNotExists()` Javadoc](https://learn.microsoft.com/java/api/com.azure.cosmos.cosmosasyncclient?view=azure-java-stable)
- [`AbstractCosmosConfiguration` Javadoc](https://learn.microsoft.com/java/api/com.azure.spring.data.cosmos.config.abstractcosmosconfiguration?view=azure-java-stable)

SHA-256: 58a6a7a1708b15195afd8fe3ed016842d9dd73ef6460fb012bf2228deca883fb