apache/pulsar · error · IOException

Failed to bind extension `${extensionName}` on ${address}

Error message

Failed to bind extension `${extensionName}` on ${address}

What it means

ProxyService.startProxyExtension binds a Netty bootstrap for each configured proxy extension at its advertised address. If bind fails, the IOException names the extension and address so the operator knows which add-on listener is broken. Fail-fast behavior ensures extensions are not silently unavailable while the core proxy runs.

Source

Thrown at pulsar-proxy/src/main/java/org/apache/pulsar/proxy/server/ProxyService.java:457

            bootstrap.childOption(ChannelOption.TCP_NODELAY, true);
            bootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR,
                    new AdaptiveRecvByteBufAllocator(1024, 16 * 1024, 1 * 1024 * 1024));

            EventLoopUtil.enableTriggeredMode(bootstrap);
            DefaultThreadFactory defaultThreadFactory = new DefaultThreadFactory("pulsar-ext-" + extensionName);
            EventLoopGroup dedicatedWorkerGroup =
                    EventLoopUtil.newEventLoopGroup(proxyConfig.getNumIOThreads(), false, defaultThreadFactory);
            extensionsWorkerGroups.add(dedicatedWorkerGroup);
            bootstrap.channel(EventLoopUtil.getServerSocketChannelClass(dedicatedWorkerGroup));
            bootstrap.group(this.acceptorGroup, dedicatedWorkerGroup);
        } else {
            bootstrap = serverBootstrap.clone();
        }
        bootstrap.childHandler(initializer);
        try {
            bootstrap.bind(address).sync();
        } catch (Exception e) {
            throw new IOException("Failed to bind extension `" + extensionName + "` on " + address, e);
        }
        log.info()
                .attr("extensionName", extensionName)
                .attr("address", address)
                .log("Successfully bound extension");
    }

    public BrokerDiscoveryProvider getDiscoveryProvider() {
        return discoveryProvider;
    }

    public void close() throws IOException {
        if (listenChannel != null) {
            try {
                listenChannel.close().sync();
            } catch (InterruptedException e) {
                log.info("Shutdown of listenChannel interrupted");
                Thread.currentThread().interrupt();

View on GitHub (pinned to 820761864e)

Solutions

  1. Check the extension's configured bind address/port and free the conflicting port (kill the stale process or pick a new port)
  2. Ensure no two proxy extensions or core proxy ports overlap in configuration
  3. Verify the bind address is a valid local interface and adjust if running in a container/VM
  4. Re-run the proxy after correcting the extension port config

Example fix

// before
proxyExtensions=org.apache.pulsar.proxy.extension.socks5.Socks5ProxyService
proxyExtensionPort0=1080  # occupied by a running SOCKS proxy

// after
proxyExtensionPort0=1081
Defensive patterns

Strategy: validation

Validate before calling

InetSocketAddress addr = resolveExtensionAddress(extensionName);
try (ServerSocket ss = new ServerSocket()) {
    ss.bind(addr);
} catch (IOException e) {
    throw new IllegalStateException("Extension " + extensionName + " port " + addr.getPort()
        + " unavailable: " + e.getMessage());
}

Type guard

boolean extensionPortUnique(ProxyConfiguration cfg, int port) {
    return port != cfg.getServicePort().orElse(-1)
        && port != cfg.getServicePortTls().orElse(-1)
        && port != cfg.getWebServicePort().orElse(-1);
}

Try / catch

try {
    proxyService.start();
} catch (IOException e) {
    if (e.getMessage() != null && e.getMessage().contains("Failed to bind extension")) {
        LOG.error("Proxy extension bind failed: {} (cause: {})", e.getMessage(), e.getCause());
    }
    throw e;
}

Prevention

When it happens

Trigger: Calling ProxyService.start (which calls startProxyExtensions -> startProxyExtension) when an extension's configured bind address/port is already occupied, is a privileged port without permission, or the address does not exist on the host; the underlying bootstrap.bind(address).sync() throws and is wrapped in IOException.

Common situations: Two extensions configured with the same bind port; extension port colliding with a core proxy or broker port; leftover process from a previous test run holding the port; configuring a hostname that doesn't resolve to a local interface in a container.

Related errors


AI-assisted analysis of apache/pulsar@820761864e (2026-09-06). Data as JSON: /api/errors/44adccb0efef19c7. Report an issue: GitHub.