testcontainers/testcontainers-java · error · IllegalArgumentException
Invalid protocol
Error message
Invalid protocol: ${protocol} What it means
Hidden (the internal r2dbc Testcontainers ConnectionFactory provider) requires a PROTOCOL option of the form '<driverDelegate>:[<protocolDelegate>]' (e.g. 'tc:postgres'). An empty/whitespace-only protocol yields IllegalArgumentException 'Invalid protocol: <protocol>'.
Solutions
- Set the protocol to '<driver>:<protocol>' form, e.g. ConnectionFactoryOptions.valueOf("tc:postgresql") or URL r2dbc:tc:postgresql://localhost/d/testdb
- Ensure the driver segment before the first ':' is non-empty (e.g. 'tc')
- Check that no code path overwrites PROTOCOL with an empty value before create()
Example fix
// before
ConnectionFactoryOptions.builder().option(PROTOCOL, "")...
// after
ConnectionFactoryOptions.builder()
.option(DRIVER, "tc")
.option(PROTOCOL, "postgresql") // resolves to tc:postgresql
... Defensive patterns
Strategy: validation
Validate before calling
String protocol = (String) options.getRequiredValue(ConnectionFactoryOptions.PROTOCOL);
if (protocol == null || protocol.trim().isEmpty() || !protocol.contains(":")) {
throw new IllegalArgumentException("PROTOCOL must be '<driver>:<db>', e.g. tc:postgresql");
} Type guard
boolean isValidTcProtocol(Object p) { return p instanceof String s && !s.trim().isEmpty() && s.contains(":") && !s.startsWith(":"); } Try / catch
try {
connectionFactory.create();
} catch (IllegalArgumentException e) {
if (e.getMessage().startsWith("Invalid protocol")) { /* rebuild options with tc:<db> */ }
throw e;
} Prevention
- Always use r2dbc:tc:<database> URL format so the tc driver segment is present
- Never blank out PROTOCOL when copying ConnectionFactoryOptions
- Add a smoke connection test that exercises create() early in startup
When it happens
Trigger: Creating a connection factory with ConnectionFactoryOptions.PROTOCOL set to "" or whitespace, or omitting the tc prefix portion so parsing yields nothing usable; called from ConnectionFactory creation flow (create → removeProxying).
Common situations: Building r2dbc URLs manually and forgetting the 'tc:' driver segment (e.g. r2dbc:tc:postgresql:///db vs a malformed protocol); copying options maps and clearing the protocol; dynamic config that returns an empty string.
Understand the failure class
Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.
Related errors
- Bucket quota cannot be less than 100MB!
- Database name cannot be null or empty
- Database name cannot be set to xepdb1
- Elasticsearch CA certificate cannot be empty
- Null password is not allowed
AI-assisted analysis of testcontainers/testcontainers-java@8e549514e3 (2026-09-12).
Data as JSON: /api/errors/e54d7b676501b856.
Report an issue: GitHub.
Appendix: source
Thrown at modules/r2dbc/src/main/java/org/testcontainers/r2dbc/Hidden.java:45
Object reusable = options.getValue(R2DBCDatabaseContainerProvider.REUSABLE_OPTION);
if (reusable instanceof String) {
builder.option(R2DBCDatabaseContainerProvider.REUSABLE_OPTION, Boolean.valueOf((String) reusable));
}
return builder.build();
}
private ConnectionFactoryOptions removeProxying(ConnectionFactoryOptions options) {
// To delegate to the next factory provider, inspect the PROTOCOL and convert it to the next DRIVER and PROTOCOL values.
//
// example:
// | Property | Input | Output |
// |----------|-----------------|--------------|
// | DRIVER | tc | postgres |
// | PROTOCOL | postgres | <empty> |
String protocol = (String) options.getRequiredValue(ConnectionFactoryOptions.PROTOCOL);
if (protocol.trim().length() == 0) {
throw new IllegalArgumentException("Invalid protocol: " + protocol);
}
String[] protocols = protocol.split(":", 2);
String driverDelegate = protocols[0];
// when protocol does NOT contain COLON, the length becomes 1
String protocolDelegate = protocols.length == 2 ? protocols[1] : "";
return ConnectionFactoryOptions
.builder()
.from(options)
.option(ConnectionFactoryOptions.DRIVER, driverDelegate)
.option(ConnectionFactoryOptions.PROTOCOL, protocolDelegate)
.build();
}
@Override
public boolean supports(ConnectionFactoryOptions options) {
return DRIVER.equals(options.getValue(ConnectionFactoryOptions.DRIVER));View on GitHub (pinned to 8e549514e3)