Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Copy this file to .env and update with your actual values
# DO NOT commit .env file to git - it's already in .gitignore
#
# These environment variables are used for database connection
# and correspond to the Spring Boot 3.5+ Couchbase properties:
# - spring.couchbase.connection-string
# - spring.couchbase.username
# - spring.couchbase.password

DB_CONN_STR=couchbases://your-cluster-url.cloud.couchbase.com
DB_USERNAME=your-username
DB_PASSWORD=your-password
3 changes: 2 additions & 1 deletion .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ on:
branches: [main]
schedule:
- cron: "10 9 * * *"
workflow_dispatch:

jobs:
run_tests:
Expand All @@ -31,7 +32,7 @@ jobs:
uses: actions/setup-java@v4
with:
java-version: ${{ matrix.java-version }}
distribution: "adopt"
distribution: "temurin"
cache: "maven"

- name: Run Maven Tests
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
*.iml
.docker/
.vscode/
.env

# maven target directory
target/
Expand Down
23 changes: 13 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,20 +63,24 @@ All configuration for communication with the database is read from the applicati
```properties
spring.mvc.pathmatch.matching-strategy=ANT_PATH_MATCHER
spring.couchbase.bucket.name=travel-sample
spring.couchbase.bootstrap-hosts=DB_CONN_STR
spring.couchbase.bucket.user=DB_USERNAME
spring.couchbase.bucket.password=DB_PASSWORD
spring.couchbase.connection-string=${DB_CONN_STR}
spring.couchbase.username=${DB_USERNAME}
spring.couchbase.password=${DB_PASSWORD}
```

Instead of the DB_CONN_STR, DB_USERNAME and DB_PASSWORD, you need to add the values for the Couchbase connection.
The application uses environment variables for database configuration. You can set these in your system environment or create a `.env` file in the project root:

> Note: The connection string expects the `couchbases://` or `couchbase://` part.
```env
DB_CONN_STR=couchbases://your-cluster.cloud.couchbase.com
DB_USERNAME=your-username
DB_PASSWORD=your-password
```

You can also use your system environment variables to set the properties. The properties are read from the environment variables if they are set. The properties are read from the `application.properties` file if the environment variables are not set.
> Note: The connection string expects the `couchbases://` or `couchbase://` part.

## Running The Application

You can add environment variables DB_CONN_STR, DB_USERNAME and DB_PASSWORD to your system environment variables or you can update the `application.properties` file in the `src/main/resources` folder.
The application will automatically load environment variables from a `.env` file if present, or use system environment variables.

### Directly on Machine

Expand All @@ -100,8 +104,7 @@ Run the Docker image
docker run -d --name springboot-container -p 9440:8080 java-springboot-quickstart -e DB_CONN_STR=<connection_string> -e DB_USERNAME=<username> -e DB_PASSWORD=<password>
```

Note: The `application.properties` file has the connection information to connect to your Capella cluster. You can also pass the connection information as environment variables to the Docker container.
If you choose not to pass the environment variables, you can update the `application.properties` file in the `src/main/resources` folder.
Note: You can pass the connection information as environment variables to the Docker container or include a `.env` file in your Docker build context.

### Verifying the Application

Expand Down Expand Up @@ -168,7 +171,7 @@ If you would like to add another entity to the APIs, these are the steps to foll

If you are running this quickstart with a self-managed Couchbase cluster, you need to [load](https://docs.couchbase.com/server/current/manage/manage-settings/install-sample-buckets.html) the travel-sample data bucket in your cluster and generate the credentials for the bucket.

You need to update the connection string and the credentials in the `application.properties` file in the `src/main/resources` folder.
You need to set the connection string and credentials using environment variables or a `.env` file as described above.

Note: Couchbase Server version 7 or higher must be installed and running before running the Spring Boot Java app.

Expand Down
13 changes: 7 additions & 6 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.3</version>
<version>3.5.5</version>
</parent>

<groupId>org.couchbase</groupId>
Expand Down Expand Up @@ -61,6 +61,12 @@
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- Environment variable loading from .env file -->
<dependency>
<groupId>io.github.cdimascio</groupId>
<artifactId>dotenv-java</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
Expand Down Expand Up @@ -128,11 +134,6 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.2</version>
<configuration>
<excludes>
<exclude>**/*IntegrationTest.java</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package org.couchbase.quickstart.springboot.config;

import io.github.cdimascio.dotenv.Dotenv;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;

import java.util.HashMap;
import java.util.Map;

public class DotEnvConfiguration implements ApplicationContextInitializer<ConfigurableApplicationContext> {

private static final Logger log = LoggerFactory.getLogger(DotEnvConfiguration.class);

@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();

try {
// Load .env file if it exists
Dotenv dotenv = Dotenv.configure()
.directory(".")
.ignoreIfMalformed()
.ignoreIfMissing()
.load();

// Create a property source from .env entries
Map<String, Object> envMap = new HashMap<>();
dotenv.entries().forEach(entry -> {
String key = entry.getKey();
String value = entry.getValue();

// Only add if not already set by system environment
if (System.getenv(key) == null) {
envMap.put(key, value);
log.debug("Loaded from .env: {}", key);
}
});

if (!envMap.isEmpty()) {
environment.getPropertySources().addFirst(new MapPropertySource("dotenv", envMap));
log.info("Environment variables loaded from .env file: {}", envMap.keySet());
}

} catch (Exception e) {
log.error("Could not load .env file", e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ public class CouchbaseConfig {

private static final Logger log = LoggerFactory.getLogger(CouchbaseConfig.class);

@Value("#{systemEnvironment['DB_CONN_STR'] ?: '${spring.couchbase.bootstrap-hosts:localhost}'}")
@Value("#{systemEnvironment['DB_CONN_STR'] ?: '${spring.couchbase.connection-string:localhost}'}")
private String host;

@Value("#{systemEnvironment['DB_USERNAME'] ?: '${spring.couchbase.bucket.user:Administrator}'}")
@Value("#{systemEnvironment['DB_USERNAME'] ?: '${spring.couchbase.username:Administrator}'}")
private String username;

@Value("#{systemEnvironment['DB_PASSWORD'] ?: '${spring.couchbase.bucket.password:password}'}")
@Value("#{systemEnvironment['DB_PASSWORD'] ?: '${spring.couchbase.password:password}'}")
private String password;

@Value("${spring.couchbase.bucket.name:travel-sample}")
Expand All @@ -51,14 +51,15 @@ public class CouchbaseConfig {
*/
@Bean(destroyMethod = "disconnect")
Cluster getCouchbaseCluster() {
Cluster cluster = null;
try {
log.debug("Connecting to Couchbase cluster at " + host);
Cluster cluster = Cluster.connect(host, username, password);
cluster.waitUntilReady(Duration.ofSeconds(15));
cluster = Cluster.connect(host, username, password);
cluster.waitUntilReady(Duration.ofSeconds(30));
return cluster;
} catch (UnambiguousTimeoutException e) {
log.error("Connection to Couchbase cluster at " + host + " timed out");
throw e;
log.warn("Connection to Couchbase cluster at " + host + " timed out, but continuing with partial connectivity");
return cluster;
} catch (Exception e) {
log.error(e.getClass().getName());
log.error("Could not connect to Couchbase cluster at " + host);
Expand All @@ -75,7 +76,7 @@ Bucket getCouchbaseBucket(Cluster cluster) {
throw new BucketNotFoundException("Bucket " + bucketName + " does not exist");
}
Bucket bucket = cluster.bucket(bucketName);
bucket.waitUntilReady(Duration.ofSeconds(15));
bucket.waitUntilReady(Duration.ofSeconds(30));
return bucket;
} catch (UnambiguousTimeoutException e) {
log.error("Connection to bucket " + bucketName + " timed out");
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/META-INF/spring.factories
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.springframework.context.ApplicationContextInitializer=org.couchbase.quickstart.springboot.config.DotEnvConfiguration
14 changes: 11 additions & 3 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Spring MVC configuration
spring.mvc.pathmatch.matching-strategy=ANT_PATH_MATCHER

# Modern Couchbase configuration (Spring Boot 3.5+)
spring.couchbase.connection-string=${DB_CONN_STR}
spring.couchbase.username=${DB_USERNAME}
spring.couchbase.password=${DB_PASSWORD}
spring.couchbase.bucket.name=travel-sample
spring.couchbase.bucket.user=DB_USERNAME
spring.couchbase.bootstrap-hosts=DB_CONN_STR
spring.couchbase.bucket.password=DB_PASSWORD

# Couchbase connection optimizations
spring.couchbase.env.timeouts.query=30000ms
spring.couchbase.env.timeouts.key-value=5000ms
spring.couchbase.env.timeouts.connect=10000ms
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ private void deleteAirline(String airlineId, String cleanupTiming) {
} catch (DocumentNotFoundException | DataRetrievalFailureException | ResourceAccessException e) {
log.warn("Document " + airlineId + " not present " + cleanupTiming);
} catch (Exception e) {
log.error("Error deleting test data", e.getMessage());
log.debug("Cleanup: Could not delete test airline {}: {} (this is expected during test cleanup)", airlineId, e.getMessage());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ private void deleteAirport(String airportId, String cleanupTiming) {
} catch (DocumentNotFoundException | DataRetrievalFailureException | ResourceAccessException e) {
log.warn("Document " + airportId + " not present " + cleanupTiming);
} catch (Exception e) {
log.error("Error deleting test data", e.getMessage());
log.debug("Cleanup: Could not delete test airport {}: {} (this is expected during test cleanup)", airportId, e.getMessage());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ private void deleteRoute(String routeId, String cleanupTiming) {
} catch (DocumentNotFoundException | DataRetrievalFailureException | ResourceAccessException e) {
log.warn("Document " + routeId + " not present " + cleanupTiming);
} catch (Exception e) {
log.error("Error deleting test data", e.getMessage());
log.debug("Cleanup: Could not delete test route {}: {} (this is expected during test cleanup)", routeId, e.getMessage());
}
}

Expand Down
48 changes: 48 additions & 0 deletions src/test/resources/logback-test.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- Console appender for test output -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>

<!-- Set root logger to INFO level (reduces noise) -->
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>

<!-- Suppress noisy Spring Framework warnings during tests -->
<logger name="org.springframework.context.support.AbstractApplicationContext" level="ERROR"/>
<logger name="org.springframework.beans.factory.support.DefaultListableBeanFactory" level="ERROR"/>
<logger name="org.springframework.boot.autoconfigure.AutoConfigurationPackages" level="ERROR"/>
<logger name="org.springframework.data.repository.config.RepositoryConfigurationDelegate" level="ERROR"/>

<!-- Suppress Couchbase client noise -->
<logger name="com.couchbase" level="WARN"/>
<logger name="com.couchbase.tracing" level="ERROR"/>
<logger name="com.couchbase.client.core.compression.snappy.SnappyHelper" level="ERROR"/>

<!-- Suppress Spring Data pagination warning -->
<logger name="org.springframework.data.web.config.PageableHandlerMethodArgumentResolverCustomizer" level="ERROR"/>
<logger name="org.springframework.data.web.config.HateoasAwareSpringDataWebConfiguration" level="ERROR"/>

<!-- Suppress Jakarta persistence warnings -->
<logger name="jakarta.persistence.spi" level="ERROR"/>

<!-- Suppress Spring Boot DevTools noise -->
<logger name="org.springframework.boot.devtools" level="ERROR"/>

<!-- Suppress Tomcat initialization noise in tests -->
<logger name="org.apache.catalina" level="WARN"/>
<logger name="org.springframework.boot.web.embedded.tomcat" level="WARN"/>

<!-- Keep our application logs at appropriate levels -->
<logger name="org.couchbase.quickstart.springboot" level="INFO"/>

<!-- Show test-related logs for debugging when needed -->
<logger name="org.couchbase.quickstart.springboot.controllers" level="WARN"/>

<!-- Suppress JVM warnings -->
<logger name="jdk.internal" level="ERROR"/>
</configuration>
Loading