blog.suje.sh

Using Oracle Database with Docker for Spring Boot Integration Testing

Introduction

Testing against the same database you use in production is crucial for catching environment-specific bugs early. However, setting up Oracle locally has traditionally been painful—complex licensing, heavy resource requirements, and tedious configuration.

Enter Oracle Free (formerly Oracle XE) with Docker. Oracle now provides lightweight, free Docker images that make local Oracle development surprisingly easy. Combined with Spring Boot’s Docker Compose support, you can spin up a real Oracle database automatically when your application starts.

This guide shows how to set up Oracle in Docker for integration testing in a Spring Boot application.

The Setup

What We’re Building

┌─────────────────────────────────────────────────────────┐
│ Spring Boot Application │
│ ┌───────────────────────────────────────────────────┐ │
│ │ spring-boot-docker-compose │ │
│ │ (Auto-starts Oracle when app runs) │ │
│ └───────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ Docker Compose │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ Oracle Free 23c (gvenzl/oracle-free) │ │ │
│ │ │ - Lightweight (~500MB vs 8GB+ for full) │ │ │
│ │ │ - Fast startup with "faststart" variant │ │ │
│ │ │ - Persistent volume for data │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘

Step 1: Docker Compose Configuration

Create docker/compose.yml in your project:

services:
  oracle:
    image: gvenzl/oracle-free:23-slim-faststart
    container_name: demo-oracle
    environment:
      ORACLE_PASSWORD: OracleAdmin123 # SYS/SYSTEM password
      APP_USER: DEMO                  # Application schema user
      APP_USER_PASSWORD: demo_local   # Application user password
    ports:
      - "1521:1521"
    volumes:
      - oracle-data:/opt/oracle/oradata # Persist data between restarts
    healthcheck:
      test: ["CMD", "healthcheck.sh"]
      interval: 10s
      timeout: 5s
      retries: 10
      start_period: 30s
      start_interval: 1s

volumes:
  oracle-data:
    name: demo-oracle-data

Understanding the Image Choice

gvenzl/oracle-free:23-slim-faststart - Let’s break this down:

Tag ComponentMeaning
23Oracle 23c Free (latest free version)
slimSmaller image (~500MB vs 1.5GB full) - no sample schemas
faststartPre-built database files - boots in seconds, not minutes

Image variants available:

gvenzl/oracle-free:23 # Full (~1.5GB, slower start)
gvenzl/oracle-free:23-slim # Smaller (~500MB, slower start)
gvenzl/oracle-free:23-faststart # Full + fast (~3GB, instant start)
gvenzl/oracle-free:23-slim-faststart # Smaller + fast (~1.5GB, instant start) ✓ Best choice

Environment Variables

VariablePurpose
ORACLE_PASSWORDPassword for SYS and SYSTEM users
APP_USERAutomatically creates this user with full privileges
APP_USER_PASSWORDPassword for the application user

The APP_USER feature is incredibly useful—Oracle automatically creates the user with CREATE SESSION, RESOURCE, UNLIMITED TABLESPACE privileges. No manual user setup required!

Health Check Configuration

healthcheck:
  test: ["CMD", "healthcheck.sh"]
  interval: 10s
  timeout: 5s
  retries: 10
  start_period: 30s   # Wait 30s before first check
  start_interval: 1s  # Check every 1s during startup

This ensures Docker reports the container as healthy only when Oracle is truly ready to accept connections—crucial for Spring Boot’s Docker Compose integration.

Step 2: Maven Configuration

Add the Spring Boot Docker Compose dependency. We use a Maven profile to make it optional:

<profiles>
  <profile>
    <id>oracle</id>
    <dependencies>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-docker-compose</artifactId>
        <optional>true</optional>
      </dependency>
    </dependencies>
  </profile>
</profiles>

<!-- Always needed: Oracle JDBC driver and Flyway support -->
<dependencies>
  <dependency>
    <groupId>com.oracle.database.jdbc</groupId>
    <artifactId>ojdbc10</artifactId>
  </dependency>

  <dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-database-oracle</artifactId>
  </dependency>
</dependencies>

Why a profile?

  • Unit tests use H2 (fast, in-memory)
  • Integration tests with Oracle only when needed: mvn test -Poracle
  • CI/CD can choose which database to test against

Step 3: Application Properties

For Local Development with Oracle

Create application-oracle.properties:

# Oracle connection via Docker
spring.datasource.url=jdbc:oracle:thin:@//localhost:1521/FREEPDB1
spring.datasource.username=DEMO
spring.datasource.password=demo_local
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver

# JPA settings for Oracle
spring.jpa.database-platform=org.hibernate.dialect.OracleDialect
spring.jpa.hibernate.ddl-auto=validate

# Flyway migrations
spring.flyway.locations=classpath:db/migration/common,classpath:db/migration/oracle
spring.flyway.baseline-on-migrate=true

# Docker Compose settings
spring.docker.compose.file=docker/compose.yml
spring.docker.compose.lifecycle-management=start_and_stop

For Unit Tests (H2)

Keep your fast H2 setup for unit tests in application-test.properties:

# H2 in Oracle compatibility mode
spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;MODE=Oracle
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=

spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=create-drop

Step 4: Flyway Migrations

Organize migrations by database type:

src/main/resources/db/migration/
├── common/ # Runs on all databases
│ └── V1__initialSetup.sql
├── h2/ # H2-specific syntax
│ ├── V2__alterDemoIdSize.sql
│ └── V3__addIndex.sql
└── oracle/ # Oracle-specific syntax
├── V2__alterDemoIdSize.sql
└── V3__addIndex.sql

Example: Same migration, different syntax

h2/V2__alterDemoIdSize.sql:

ALTER TABLE demo_table ALTER COLUMN DEMO_ID SET DATA TYPE VARCHAR2(200);

oracle/V2__alterDemoIdSize.sql:

ALTER TABLE demo_table MODIFY DEMO_ID VARCHAR2(200);

Step 5: Running the Application

Option A: Manual Docker Start

# Start Oracle
cd docker
docker compose up -d

# Wait for healthy status
docker compose ps # Check STATUS shows "healthy"

# Run application
mvn spring-boot:run -Dspring.profiles.active=oracle
# Just run the app - Spring Boot starts Docker automatically!
mvn spring-boot:run -Poracle -Dspring.profiles.active=oracle

Spring Boot will:

  1. Detect docker/compose.yml
  2. Run docker compose up
  3. Wait for Oracle healthcheck to pass
  4. Configure datasource properties automatically
  5. Start your application
  6. Run docker compose down on shutdown

Step 6: Integration Test Configuration

For integration tests that need a real Oracle database:

@SpringBootTest
@ActiveProfiles("oracle")
@Testcontainers
class OracleIntegrationTest {

  @Container
  static OracleContainer oracle =
      new OracleContainer("gvenzl/oracle-free:23-slim-faststart")
          .withUsername("TEST_USER")
          .withPassword("test_password");

  @DynamicPropertySource
  static void configureProperties(DynamicPropertyRegistry registry) {
    registry.add("spring.datasource.url", oracle::getJdbcUrl);
    registry.add("spring.datasource.username", oracle::getUsername);
    registry.add("spring.datasource.password", oracle::getPassword);
  }

  @Test
  void shouldPersistToOracle() {
    // Your test here - running against real Oracle!
  }
}

Add Testcontainers dependency:

<dependency>
  <groupId>org.testcontainers</groupId>
  <artifactId>oracle-free</artifactId>
  <scope>test</scope>
</dependency>

<dependency>
  <groupId>org.testcontainers</groupId>
  <artifactId>junit-jupiter</artifactId>
  <scope>test</scope>
</dependency>

Performance Tips

1. Use faststart Images

The faststart variants include pre-built database files. Startup time comparison:

ImageFirst StartSubsequent Starts
23-slim~2-3 minutes~30-60 seconds
23-slim-faststart~10-15 seconds~5-10 seconds

2. Persist Data with Volumes

volumes:
  - oracle-data:/opt/oracle/oradata

Without volumes, Oracle recreates the database on every container start. With volumes:

  • Data persists between restarts
  • Subsequent starts are much faster
  • Your test data survives container recreation

3. Use H2 for Unit Tests

Reserve Oracle for integration tests. Unit tests should be fast:

// Unit test - uses H2
@DataJpaTest
@ActiveProfiles("test")
class RepositoryTest {
  // repository tests here
}

// Integration test - uses Oracle
@SpringBootTest
@ActiveProfiles("oracle")
class FullIntegrationTest {
  // full integration tests here
}

4. Connection Pool Tuning

# Reduce pool size for local development
spring.datasource.hikari.maximum-pool-size=5
spring.datasource.hikari.minimum-idle=2

Troubleshooting

Container Won’t Start

# Check logs
docker logs demo-oracle

# Common issues:
# - Port 1521 already in use
# - Insufficient memory (Oracle needs ~1GB RAM)
# - Volume permission issues

Connection Refused

# Verify container is healthy
docker compose ps

# Test connection manually
docker exec -it demo-oracle sqlplus demo/demo_local@//localhost:1521/FREEPDB1

Slow Startup

  1. Switch to faststart image variant
  2. Increase Docker memory allocation
  3. Use SSD storage for Docker volumes

Conclusion

With Oracle Free Docker images and Spring Boot’s Docker Compose integration, testing against Oracle locally is now straightforward:

  1. Zero manual setup – Docker Compose handles everything
  2. Production parity – test against real Oracle locally
  3. Developer-friendly – fast startup, clean shutdown, CI-ready

← All posts