elastic/elasticsearch · error · IllegalStateException
Available port ranges have exceeded Range from {} to {}
Error message
Available port ranges have exceeded Range from {} to {} What it means
AvailablePortAllocator hands out fixed-size (DEFAULT_RANGE_SIZE=10) port ranges starting at MIN_PRIVATE_PORT=10300. getNextPortRange computes startPort = 10300 + rangeNumber*10 and rejects any range whose endPort exceeds MAX_PRIVATE_PORT=13300. With 10-port ranges this caps the allocator at 300 concurrent reservations; exceeding it is treated as a leak/exhaustion defect, not a recoverable condition.
Source
Thrown at build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/util/ports/AvailablePortAllocator.java:30
import org.gradle.internal.Pair;
import java.util.ArrayList;
import java.util.List;
public class AvailablePortAllocator {
public static final int MIN_PRIVATE_PORT = 10300;
public static final int MAX_PRIVATE_PORT = 13300;
public static final int DEFAULT_RANGE_SIZE = 10;
private final List<ReservedPortRange> reservations = new ArrayList<ReservedPortRange>();
private ReservedPortRangeFactory portRangeFactory = new DefaultReservedPortRangeFactory();
protected Pair<Integer, Integer> getNextPortRange(int rangeNumber) {
int startPort = MIN_PRIVATE_PORT + (rangeNumber * DEFAULT_RANGE_SIZE);
int endPort = startPort + DEFAULT_RANGE_SIZE - 1;
if (endPort > MAX_PRIVATE_PORT) {
throw new IllegalStateException(
"Available port ranges have exceeded Range from " + MIN_PRIVATE_PORT + " to " + MAX_PRIVATE_PORT
);
}
return Pair.of(startPort, endPort);
}
public ReservedPortRange reservePortRange() {
Pair<Integer, Integer> portRange = getNextPortRange(reservations.size());
ReservedPortRange range = portRangeFactory.getReservedPortRange(portRange.getLeft(), portRange.getRight());
reservations.add(range);
return range;
}
}
View on GitHub (pinned to db6a809a66)
Solutions
- Ensure every reservePortRange() has a matching release/close so reservations are reaped.
- Reduce the number of concurrently running clusters in the test matrix, or stagger them so ranges are reused.
- If the cap is genuinely too low for legitimate use, raise MAX_PRIVATE_PORT (and MIN if needed) in AvailablePortAllocator — but first confirm the leak is not the real bug.
Example fix
// before: ranges never released; reservations.size() climbs past 300
ReservedPortRange r = allocator.reservePortRange();
// ... used but never closed
// after: try-with-resources or explicit release
try (ReservedPortRange r = allocator.reservePortRange()) {
// use r
} Defensive patterns
Strategy: validation
Validate before calling
if (allocator.reservationCount() >= (AvailablePortAllocator.MAX_PRIVATE_PORT - AvailablePortAllocator.MIN_PRIVATE_PORT) / AvailablePortAllocator.DEFAULT_RANGE_SIZE) {
// drain or refuse new reservations before calling reservePortRange()
} Prevention
- Always release ReservedPortRange objects (try-with-resources or explicit close).
- Bound the number of concurrently running clusters in the test matrix.
- Restart long-lived daemons periodically or track reservation growth.
When it happens
Trigger: reservePortRange() is called more than 300 times without releasing ranges (reservations list grows unboundedly), so getNextPortRange(reservations.size()) returns an endPort > 13300. Typical in test-heavy Gradle runs spawning many clusters.
Common situations: A Gradle test orchestration layer leaks ReservedPortRange objects (never releases them); a very large parallel test matrix allocates more than 300 concurrent ranges; long-lived daemon accumulating reservations across builds.
Related errors
- Failed to load version properties
- classname is a required setting for esplugin
- classname is a forbidden for stable esplugin
- invalid deploymentTarget '{}', expected one of {}
- unrecognized classpath entry: {}
AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12).
Data as JSON: /api/errors/7cbf15d6bdcd9d4d.
Report an issue: GitHub.