Eliminating Flaky Java Integration Tests with Bitbucket Service Containers
Stop fighting flaky integration tests. Learn how to use Bitbucket Pipelines service containers to run isolated PostgreSQL or Redis instances for deterministic Java Maven builds.
18 Jul 2026, 20:06 UTC

The Problem: The "It Works on My Machine" Integration Gap
Integration tests that rely on external databases or message brokers often become the primary source of "flaky" builds. When these services are hosted on a shared staging server, network latency or concurrent test runs can cause random failures. Conversely, mocking these services entirely often misses critical bugs related to schema mismatches or query performance.
The solution is to move the dependency into the pipeline itself. By using service containers in Bitbucket Pipelines, you can spin up a fresh, isolated instance of your database or cache for every single build, ensuring a deterministic environment where tests start from a known state.
Defining Dependencies in bitbucket-pipelines.yml
In Bitbucket Pipelines, the definitions section allows you to declare services. These are Docker containers that run in the background, sharing the same network namespace as your main build container. This means your Java application can reach the service using localhost.
When defining a service, you must specify the Docker image and any required environment variables (such as passwords or database names) that the image expects for initialization.
Connecting Maven to the Service
For Java projects using Maven, the maven-surefire-plugin (for unit tests) or maven-failsafe-plugin (for integration tests) needs to know where the database lives. Since the service container shares the network, you point your JDBC connection string to localhost.
To avoid "Connection Refused" errors, your build script should include a wait loop. While the container is "started," the database engine inside it may take several seconds to actually accept connections.
Worked Example: PostgreSQL Integration
This configuration spins up a PostgreSQL 13 instance and runs a Maven verification cycle. This file should be placed in the root of your repository as bitbucket-pipelines.yml.
image: maven:3.8.6-openjdk-11
definitions:
services:
postgres:
image: postgres:13
environment:
POSTGRES_DB: testdb
POSTGRES_USER: user
POSTGRES_PASSWORD: password
pipelines:
default:
- step:
name: Integration Tests
services: [postgres]
script:
# Wait for Postgres to be ready before starting Maven
- apt-get update && apt-get install -y postgresql-client
- until pg_isready -h localhost -p 5432; do echo "Waiting for database..."; sleep 2; done
- mvn verify -Ddb.url=jdbc:postgresql://localhost:5432/testdb -Ddb.user=user -Ddb.password=password
Execution Details:
- Permissions: The pipeline runner executes as root by default, allowing
apt-getto install thepostgresql-clientfor thepg_isreadycheck. - Placeholders: Replace
testdb,user, andpasswordwith your project's specific requirements. - Expected Result: The console will show "Waiting for database..." a few times, followed by the standard Maven build output and a
BUILD SUCCESSmessage.
Trade-offs and Resource Constraints
While service containers increase reliability, they introduce two primary constraints:
- Startup Overhead: Every service adds to the total pipeline duration. If you spin up a heavy Oracle DB or a complex Kafka cluster, your build time will increase.
- Memory and Disk Limits: Bitbucket Cloud runners have finite resources. Large images or services that write massive amounts of data to disk can hit the default 2 GB limit, causing the pipeline to crash with an
Out of MemoryorNo space left on deviceerror. - Port Conflicts: Because services share the network namespace, you cannot run two different services that both listen on port 5432.
Verification and Rollback
To verify this setup, push the configuration to a feature branch and monitor the Pipeline Log. Ensure that the pg_isready loop terminates and that the Maven tests are actually hitting the database rather than a mock.
Rollback: If the service causes the pipeline to hang or fail, remove the services: [postgres] line from the step and the definitions block. This reverts the pipeline to a standard build container without sidecar dependencies.
Actionable Next Steps
- Identify your most unstable integration test and the service it requires.
- Implement a single service container with a corresponding
untilhealthcheck loop. - Measure the increase in pipeline duration; if it exceeds 5 minutes, consider optimizing the Docker image size.
- Gradually add additional services (e.g., Redis) only as the test suite expands.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.