apache/incubator-seata · error · IllegalArgumentException

serverMessageSender must not be null

Error message

serverMessageSender must not be null

What it means

DefaultServerMessageListenerImpl.getServerMessageSender() throws if remotingServer was never injected via setServerMessageSender. The listener needs the RemotingServer to push asynchronous responses/requests back to clients; using it before wiring is a bean-lifecycle or configuration error, not a runtime race.

Source

Thrown at core/src/main/java/org/apache/seata/core/rpc/DefaultServerMessageListenerImpl.java:224

        ExecutorService mergeSendExecutorService = ThreadPoolExecutorFactory.newThreadPoolExecutor(
                THREAD_PREFIX,
                MAX_LOG_SEND_THREAD,
                MAX_LOG_SEND_THREAD,
                KEEP_ALIVE_TIME,
                TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>(),
                true);
        mergeSendExecutorService.submit(new BatchLogRunnable());
    }

    /**
     * Gets server message sender.
     *
     * @return the server message sender
     */
    public RemotingServer getServerMessageSender() {
        if (remotingServer == null) {
            throw new IllegalArgumentException("serverMessageSender must not be null");
        }
        return remotingServer;
    }

    /**
     * Sets server message sender.
     *
     * @param remotingServer the remoting server
     */
    public void setServerMessageSender(RemotingServer remotingServer) {
        this.remotingServer = remotingServer;
    }

    /**
     * The type Batch log runnable.
     */
    static class BatchLogRunnable implements Runnable {

View on GitHub (pinned to e01f97c6db)

Solutions

  1. Ensure setServerMessageSender(remotingServer) is called during assembly before any message handling — in Spring, wire it as a required @Autowired setter or constructor arg.
  2. In tests, stub a RemotingServer and inject it in @BeforeEach.
  3. Prefer reusing the framework's server bootstrap rather than reassembling beans by hand.

Example fix

// before
DefaultServerMessageListenerImpl l = new DefaultServerMessageListenerImpl();
l.getServerMessageSender().sendResponse(msg);
// after
RemotingServer stub = mock(RemotingServer.class);
l.setServerMessageSender(stub);
l.getServerMessageSender().sendResponse(msg);
Defensive patterns

Strategy: validation

Validate before calling

if (listener.getServerMessageSender() == null /* would throw */) {
    throw new IllegalStateException("wire setServerMessageSender before use");
}
// simpler: inject via constructor/Spring so it cannot be missing
DefaultServerMessageListenerImpl l = new DefaultServerMessageListenerImpl();
l.setServerMessageSender(remotingServer); // required assembly step

Try / catch

catch (IllegalArgumentException e) {
    if (e.getMessage().contains("must not be null")) { throw new IllegalStateException("listener used before wiring", e); }
}

Prevention

When it happens

Trigger: Manually constructing DefaultServerMessageListenerImpl (e.g. in tests or custom bootstrap) and calling send-back APIs without calling setServerMessageSender; or a Spring/@Bean configuration where the listener bean is created/resolved before the remoting server bean is wired into it.

Common situations: Custom server-side bootstrap replacing the default Seata server assembly; unit tests instantiating the listener directly; @Autowired ordering issues where an eager init consumes the listener pre-wiring.

Related errors


AI-assisted analysis of apache/incubator-seata@e01f97c6db (2026-08-14). Data as JSON: /api/errors/6ffebef05451ada1. Report an issue: GitHub.