From 9af5bd326500773d89ce30311af5e3cac2a9b3f5 Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Tue, 29 Sep 2026 06:39:04 +0000
Subject: [PATCH] [#1112] Listen on the configured listen-address in the HTTP connection handler and the JMX RMI connector (#1113)

---
 opendj-server-legacy/src/test/java/org/opends/server/TestCaseUtils.java                                                    |   56 +++
 opendj-server-legacy/src/main/java/org/opends/server/protocols/jmx/JmxConnectionHandler.java                               |   19 
 opendj-server-legacy/src/test/java/org/opends/server/protocols/jmx/JmxListenAddressTestCase.java                           |  398 ++++++++++++++++++++++++
 opendj-server-legacy/src/test/java/org/opends/server/protocols/http/HTTPConnectionHandlerTestCase.java                     |  271 +++++++++++++++-
 opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/JMXConnectionHandlerConfiguration.xml |   11 
 opendj-server-legacy/src/main/java/org/opends/server/protocols/jmx/DirectoryRMIServerSocketFactory.java                    |   18 
 opendj-server-legacy/src/main/java/org/opends/server/protocols/jmx/RmiConnector.java                                       |   82 +++++
 opendj-server-legacy/src/main/java/org/opends/server/protocols/http/HTTPConnectionHandler.java                             |   73 +++-
 8 files changed, 862 insertions(+), 66 deletions(-)

diff --git a/opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/JMXConnectionHandlerConfiguration.xml b/opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/JMXConnectionHandlerConfiguration.xml
index d3a6dd8..c9c66a0 100644
--- a/opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/JMXConnectionHandlerConfiguration.xml
+++ b/opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/JMXConnectionHandlerConfiguration.xml
@@ -14,6 +14,7 @@
 
   Copyright 2007-2009 Sun Microsystems, Inc.
   Portions Copyright 2013-2015 ForgeRock AS.
+  Portions Copyright 2026 3A Systems, LLC.
   ! -->
 <adm:managed-object name="jmx-connection-handler"
   plural-name="jmx-connection-handlers"
@@ -66,7 +67,15 @@
     <adm:description>
       If no value is provided, then the
       <adm:user-friendly-name />
-      listens on all interfaces.
+      listens on all interfaces. The RMI stubs that JMX clients receive
+      name the host to connect to: with a specific address, the server sets
+      the java.rmi.server.hostname system property to that address, unless
+      the property is already set, for instance with
+      -Djava.rmi.server.hostname in the Java arguments of the server. The
+      property applies to every RMI object that the Java virtual machine
+      exports, including those of the platform JMX agent. With several JMX
+      connection handlers on different specific addresses, the last one
+      started sets it.
     </adm:description>
     <adm:requires-admin-action>
       <adm:server-restart />
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/HTTPConnectionHandler.java b/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/HTTPConnectionHandler.java
index 9f94105..a3a61d9 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/HTTPConnectionHandler.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/HTTPConnectionHandler.java
@@ -30,7 +30,6 @@
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -133,8 +132,8 @@
   /** Indicates whether this connection handler is enabled. */
   private boolean enabled;
 
-  /** The set of listeners for this connection handler. */
-  private final List<HostPort> listeners = new LinkedList<>();
+  /** The addresses and the port the HTTP server listens on, or starts with next. */
+  private volatile List<HostPort> listeners = Collections.emptyList();
 
   /** The HTTP server embedded in OpenDJ. */
   private HttpServer httpServer;
@@ -448,11 +447,7 @@
       friendlyName = config.name();
     }
 
-    int listenPort = config.getListenPort();
-    for (InetAddress a : config.getListenAddress())
-    {
-      listeners.add(new HostPort(a.getHostAddress(), listenPort));
-    }
+    listeners = toListeners(config);
 
     handlerName = getHandlerName(config);
 
@@ -770,7 +765,17 @@
     this.httpServer = createHttpServer();
     this.httpServer.getServerConfiguration().addHttpHandler(newGrizzlyHttpHandler(new RootHttpApplication()));
     logger.trace("Starting HTTP server...");
-    this.httpServer.start();
+    try
+    {
+      this.httpServer.start();
+    }
+    catch (IOException | RuntimeException e)
+    {
+      // HttpServer.start() stops at the first listener that cannot bind and leaves the listeners started before it
+      // bound: release their addresses, since the caller only forgets about the server.
+      this.httpServer.shutdownNow();
+      throw e;
+    }
     logger.trace("HTTP server started");
     logger.info(NOTE_CONNHANDLER_STARTED_LISTENING, handlerName);
   }
@@ -790,13 +795,41 @@
       setHttpStatsProbe(server);
     }
 
-    // Configure the network listener
-    final NetworkListener listener = new NetworkListener(
-        "OpenDJ-HTTP", NetworkListener.DEFAULT_NETWORK_HOST, initConfig.getListenPort());
-    server.addListener(listener);
+    // Configure one network listener per listen address, and report them: a configuration change can replace
+    // initConfig after the initialization. HttpServer keys its listeners by name, and each listener owns its
+    // transport, so neither can be shared.
+    listeners = toListeners(initConfig);
+    final int numRequestHandlers = getNumRequestHandlers(currentConfig.getNumRequestHandlers(), friendlyName);
+    for (InetAddress address : initConfig.getListenAddress())
+    {
+      final String host = address.getHostAddress();
+      final NetworkListener listener = new NetworkListener("OpenDJ-HTTP " + host, host, initConfig.getListenPort());
+      server.addListener(listener);
+      configureTransport(listener.getTransport(), numRequestHandlers);
 
-    // Configure the network transport
-    final TCPNIOTransport transport = listener.getTransport();
+      // Configure SSL
+      if (sslEngineConfigurator != null)
+      {
+        listener.setSecure(true);
+        listener.setSSLEngineConfig(sslEngineConfigurator);
+      }
+    }
+
+    return server;
+  }
+
+  private static List<HostPort> toListeners(HTTPConnectionHandlerCfg config)
+  {
+    final List<HostPort> hostPorts = new ArrayList<>();
+    for (InetAddress address : config.getListenAddress())
+    {
+      hostPorts.add(new HostPort(address.getHostAddress(), config.getListenPort()));
+    }
+    return Collections.unmodifiableList(hostPorts);
+  }
+
+  private void configureTransport(TCPNIOTransport transport, int numRequestHandlers)
+  {
     transport.setReuseAddress(currentConfig.isAllowTCPReuseAddress());
     transport.setKeepAlive(currentConfig.isUseTCPKeepAlive());
     transport.setTcpNoDelay(currentConfig.isUseTCPNoDelay());
@@ -807,18 +840,8 @@
     transport.setWriteBufferSize(bufferSize);
     transport.setIOStrategy(SameThreadIOStrategy.getInstance());
 
-    final int numRequestHandlers = getNumRequestHandlers(currentConfig.getNumRequestHandlers(), friendlyName);
     transport.setSelectorRunnersCount(numRequestHandlers);
     transport.setServerConnectionBackLog(currentConfig.getAcceptBacklog());
-
-    // Configure SSL
-    if (sslEngineConfigurator != null)
-    {
-      listener.setSecure(true);
-      listener.setSSLEngineConfig(sslEngineConfigurator);
-    }
-
-    return server;
   }
 
   private void setHttpStatsProbe(HttpServer server)
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/protocols/jmx/DirectoryRMIServerSocketFactory.java b/opendj-server-legacy/src/main/java/org/opends/server/protocols/jmx/DirectoryRMIServerSocketFactory.java
index 8c6d086..82081b9 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/protocols/jmx/DirectoryRMIServerSocketFactory.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/protocols/jmx/DirectoryRMIServerSocketFactory.java
@@ -47,6 +47,9 @@
   /** Indicate if we required the client authentication via SSL. */
   private final boolean needClientCertificate;
 
+  /** The address to listen on, which could be INADDR_ANY. */
+  private final InetAddress listenAddress;
+
   /**
    * Constructs a new <code>DirectoryRMIServerSocketFactory</code> with the
    * specified SSL socket configuration.
@@ -59,13 +62,18 @@
    *            connections accepted by server sockets created by this
    *            factory; <code>false</code> to not require client
    *            authentication.
+   *
+   * @param listenAddress
+   *            the address the server sockets created by this factory
+   *            listen on
    */
   public DirectoryRMIServerSocketFactory(SSLSocketFactory sslSocketFactory,
-      boolean needClientCertificate)
+      boolean needClientCertificate, InetAddress listenAddress)
   {
     // Initialize the configuration parameters.
     this.needClientCertificate = needClientCertificate;
     this.sslSocketFactory = sslSocketFactory;
+    this.listenAddress = listenAddress;
   }
 
   /**
@@ -97,7 +105,7 @@
   @Override
   public ServerSocket createServerSocket(int port) throws IOException
   {
-    return new ServerSocket(port, 0, InetAddress.getByName("0.0.0.0"))
+    return new ServerSocket(port, 0, listenAddress)
     {
       @Override
       public Socket accept() throws IOException
@@ -162,7 +170,8 @@
   private boolean checkParameters(DirectoryRMIServerSocketFactory that)
   {
     return needClientCertificate == that.needClientCertificate
-        && sslSocketFactory.equals(that.sslSocketFactory);
+        && sslSocketFactory.equals(that.sslSocketFactory)
+        && listenAddress.equals(that.listenAddress);
   }
 
   /**
@@ -177,6 +186,7 @@
   {
     return getClass().hashCode()
         + Boolean.valueOf(needClientCertificate).hashCode()
-        + sslSocketFactory.hashCode();
+        + sslSocketFactory.hashCode()
+        + listenAddress.hashCode();
   }
 }
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/protocols/jmx/JmxConnectionHandler.java b/opendj-server-legacy/src/main/java/org/opends/server/protocols/jmx/JmxConnectionHandler.java
index dbbb523..346024e 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/protocols/jmx/JmxConnectionHandler.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/protocols/jmx/JmxConnectionHandler.java
@@ -13,6 +13,7 @@
  *
  * Copyright 2006-2009 Sun Microsystems, Inc.
  * Portions Copyright 2013-2016 ForgeRock AS.
+ * Portions Copyright 2026 3A Systems, LLC.
  */
 package org.opends.server.protocols.jmx;
 
@@ -88,6 +89,14 @@
   private final List<HostPort> listeners = new LinkedList<>();
 
   /**
+   * The address the RMI registry and the RMI connector listen on. A change of
+   * listen-address takes effect when the handler restarts, as its
+   * configuration says: the registry, the connector and the listeners stay
+   * on the same address until then.
+   */
+  private InetAddress listenAddress;
+
+  /**
    * Creates a new instance of this JMX connection handler. It must be
    * initialized before it may be used.
    */
@@ -136,7 +145,7 @@
       }
 
       listeners.clear();
-      listeners.add(HostPort.allAddresses(config.getListenPort()));
+      listeners.add(new HostPort(listenAddress.getHostAddress(), config.getListenPort()));
 
       rmiConnector.finalizeConnectionHandler(portChanged);
       try
@@ -224,13 +233,14 @@
 
 
   /**
-   * Get the JMX connection handler's listen address.
+   * Get the JMX connection handler's listen address: the one it was
+   * initialized with, until it restarts.
    *
    * @return Returns the JMX connection handler's listen address.
    */
   public InetAddress getListenAddress()
   {
-    return currentConfig.getListenAddress();
+    return listenAddress;
   }
 
   /**
@@ -301,8 +311,9 @@
       protocol = "JMX";
     }
 
+    listenAddress = config.getListenAddress();
     listeners.clear();
-    listeners.add(HostPort.allAddresses(config.getListenPort()));
+    listeners.add(new HostPort(listenAddress.getHostAddress(), config.getListenPort()));
     connectionHandlerName = "JMX Connection Handler " + config.getListenPort();
 
     // Create a system property to store the JMX port the server is
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/protocols/jmx/RmiConnector.java b/opendj-server-legacy/src/main/java/org/opends/server/protocols/jmx/RmiConnector.java
index 3b7f82e..626f94d 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/protocols/jmx/RmiConnector.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/protocols/jmx/RmiConnector.java
@@ -22,9 +22,11 @@
 
 import java.io.IOException;
 import java.net.InetAddress;
+import java.net.UnknownHostException;
 import java.rmi.RemoteException;
 import java.rmi.registry.LocateRegistry;
 import java.rmi.registry.Registry;
+import java.rmi.server.RMIServerSocketFactory;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.SortedSet;
@@ -168,6 +170,18 @@
    */
   private String rmiVersion;
 
+  /**
+   * The system property that names the host the stubs of the objects exported
+   * through RMI advertise.
+   */
+  private static final String RMI_SERVER_HOSTNAME = "java.rmi.server.hostname";
+
+  /**
+   * The value this server gave to {@code java.rmi.server.hostname}, or
+   * {@code null} if the server did not set it.
+   */
+  private static String rmiServerHostnameSetByServer;
+
   // ===================================================================
   // CONSTRUCTOR
   // ===================================================================
@@ -319,8 +333,10 @@
       // ---------------------
       // init an ssl context
       // ---------------------
+      // Both server socket factories listen on the configured listen address:
+      // the default RMI factory would listen on every interface.
       SslRMIClientSocketFactory rmiClientSockeyFactory = null;
-      DirectoryRMIServerSocketFactory rmiServerSockeyFactory = null;
+      RMIServerSocketFactory rmiServerSockeyFactory;
       if (jmxConnectionHandler.isUseSSL())
       {
         if (logger.isTraceEnabled())
@@ -355,7 +371,8 @@
         SSLSocketFactory ssf = ctx.getSocketFactory();
 
         // set the Server socket factory in the JMX map
-        rmiServerSockeyFactory = new DirectoryRMIServerSocketFactory(ssf, false);
+        rmiServerSockeyFactory = new DirectoryRMIServerSocketFactory(
+            ssf, false, jmxConnectionHandler.getListenAddress());
         env.put(
             "jmx.remote.rmi.server.socket.factory",
             rmiServerSockeyFactory);
@@ -376,6 +393,8 @@
         {
           logger.trace("UNSECURE CONNECTION");
         }
+        rmiServerSockeyFactory = new OpendsRmiServerSocketFactory(
+            jmxConnectionHandler.getListenAddress());
       }
 
       // specify the rmi JMX authenticator to be used
@@ -399,6 +418,7 @@
       {
         logger.trace("Create and start the JMX RMI connector");
       }
+      advertiseListenAddress(jmxConnectionHandler.getListenAddress());
       OpendsRMIJRMPServerImpl opendsRmiConnectorServer =
           new OpendsRMIJRMPServerImpl(jmxConnectionHandler.getRmiPort(),
               rmiClientSockeyFactory, rmiServerSockeyFactory, env);
@@ -426,6 +446,64 @@
 
   }
 
+  /**
+   * Makes the stub of the RMI connector advertise the listen address.
+   * <p>
+   * The stub that a client gets from the RMI registry advertises the host that
+   * {@code java.rmi.server.hostname} names, or else the address of the local host,
+   * whatever address the connector listens on: a connector bound to another
+   * address refuses the client. The JDK reads the property again at each export.
+   * A value that this server did not set is the operator's, and is kept.
+   *
+   * @param listenAddress
+   *          the address the connector listens on
+   */
+  private static synchronized void advertiseListenAddress(InetAddress listenAddress)
+  {
+    final String hostname = System.getProperty(RMI_SERVER_HOSTNAME);
+    if (hostname != null && !hostname.equals(rmiServerHostnameSetByServer))
+    {
+      return;
+    }
+    if (listenAddress.isAnyLocalAddress())
+    {
+      // Cleared, the property would leave the JDK advertising the host it last
+      // read: the address a connector of this server listened on before, which
+      // a remote client cannot reach if it was a loopback one. Advertise the
+      // local host instead, as the JDK does by default.
+      final InetAddress localHost = getLocalHostOrNull();
+      if (rmiServerHostnameSetByServer != null
+          && localHost != null && !localHost.isLoopbackAddress())
+      {
+        rmiServerHostnameSetByServer = localHost.getHostAddress();
+        System.setProperty(RMI_SERVER_HOSTNAME, rmiServerHostnameSetByServer);
+      }
+      else
+      {
+        System.clearProperty(RMI_SERVER_HOSTNAME);
+        rmiServerHostnameSetByServer = null;
+      }
+    }
+    else
+    {
+      rmiServerHostnameSetByServer = listenAddress.getHostAddress();
+      System.setProperty(RMI_SERVER_HOSTNAME, rmiServerHostnameSetByServer);
+    }
+  }
+
+  private static InetAddress getLocalHostOrNull()
+  {
+    try
+    {
+      return InetAddress.getLocalHost();
+    }
+    catch (UnknownHostException e)
+    {
+      logger.traceException(e);
+      return null;
+    }
+  }
+
   static void configureJmxDeserializationProtection(Map<String, Object> env)
   {
     // Tightly constrain the credentials object exchanged during authentication
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/TestCaseUtils.java b/opendj-server-legacy/src/test/java/org/opends/server/TestCaseUtils.java
index 3d41854..52a49da 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/TestCaseUtils.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/TestCaseUtils.java
@@ -52,7 +52,10 @@
 import java.lang.management.ThreadMXBean;
 import java.net.BindException;
 import java.net.ConnectException;
+import java.net.Inet4Address;
+import java.net.InetAddress;
 import java.net.InetSocketAddress;
+import java.net.NetworkInterface;
 import java.net.ServerSocket;
 import java.net.Socket;
 import java.net.SocketAddress;
@@ -130,6 +133,7 @@
 import org.opends.server.util.DynamicConstants;
 import org.opends.server.util.LDIFReader;
 import org.opends.server.util.TestTimer;
+import org.testng.SkipException;
 
 import com.forgerock.opendj.util.OperatingSystem;
 
@@ -966,6 +970,58 @@
   }
 
   /**
+   * Returns an IPv4 address of the local host other than a loopback one, for the tests of a listen
+   * address: a listener bound to the loopback address refuses connections to it, while a listener
+   * bound to the wildcard address accepts them.
+   *
+   * @return an IPv4 address of an interface of the local host which is up and not a loopback one
+   * @throws IOException
+   *           if the network interfaces cannot be listed
+   * @throws SkipException
+   *           if the local host has no such address
+   */
+  public static InetAddress getNonLoopbackAddress() throws IOException
+  {
+    for (NetworkInterface nif : Collections.list(NetworkInterface.getNetworkInterfaces()))
+    {
+      if (!nif.isUp() || nif.isLoopback())
+      {
+        continue;
+      }
+      for (InetAddress address : Collections.list(nif.getInetAddresses()))
+      {
+        if (address instanceof Inet4Address && !address.isLinkLocalAddress())
+        {
+          return address;
+        }
+      }
+    }
+    throw new SkipException("the local host has no IPv4 address other than a loopback one");
+  }
+
+  /**
+   * Tells whether a connection to the given address and port is accepted right now.
+   *
+   * @param address
+   *          the address to connect to
+   * @param port
+   *          the port to connect to
+   * @return {@code true} if the connection is accepted, {@code false} if it fails
+   */
+  public static boolean isAcceptingConnections(InetAddress address, int port)
+  {
+    try (Socket socket = new Socket())
+    {
+      socket.connect(new InetSocketAddress(address, port), 5000);
+      return true;
+    }
+    catch (IOException e)
+    {
+      return false;
+    }
+  }
+
+  /**
    * Finds a free server socket port on the local host.
    *
    * @return The free port.
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/protocols/http/HTTPConnectionHandlerTestCase.java b/opendj-server-legacy/src/test/java/org/opends/server/protocols/http/HTTPConnectionHandlerTestCase.java
index 456284b..42d97f1 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/protocols/http/HTTPConnectionHandlerTestCase.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/protocols/http/HTTPConnectionHandlerTestCase.java
@@ -15,17 +15,38 @@
  */
 package org.opends.server.protocols.http;
 
+import static java.util.concurrent.TimeUnit.*;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.opends.server.TestCaseUtils.*;
+import static org.opends.server.util.ServerConstants.*;
 import static org.testng.Assert.*;
 
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.TrustManager;
+
 import org.forgerock.i18n.LocalizableMessage;
 import org.forgerock.opendj.server.config.meta.HTTPConnectionHandlerCfgDefn;
 import org.forgerock.opendj.server.config.server.HTTPConnectionHandlerCfg;
+import org.opends.admin.ads.util.BlindTrustManager;
 import org.opends.server.DirectoryServerTestCase;
 import org.opends.server.TestCaseUtils;
 import org.opends.server.core.DirectoryServer;
+import org.opends.server.extensions.DummyAlertHandler;
 import org.opends.server.extensions.InitializationUtils;
 import org.opends.server.types.Entry;
+import org.opends.server.types.HostPort;
+import org.opends.server.util.TestTimer;
 import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
 @SuppressWarnings("javadoc")
@@ -34,6 +55,9 @@
 {
   private static final LocalizableMessage STOP_REASON = LocalizableMessage.raw("Don't need a reason.");
 
+  /** An address of TEST-NET-1 (RFC 5737), which is never assigned to an interface of the host. */
+  private static final String UNASSIGNED_ADDRESS = "192.0.2.1";
+
   @BeforeClass
   public void setUp() throws Exception
   {
@@ -53,32 +77,7 @@
   public void testStartWaitsForListenPort() throws Exception
   {
     final int listenPort = TestCaseUtils.findFreePort();
-    Entry handlerEntry = TestCaseUtils.makeEntry(
-        "dn: cn=HTTP Connection Handler,cn=Connection Handlers,cn=config",
-        "objectClass: top",
-        "objectClass: ds-cfg-connection-handler",
-        "objectClass: ds-cfg-http-connection-handler",
-        "cn: HTTP Connection Handler",
-        "ds-cfg-java-class: org.opends.server.protocols.http.HTTPConnectionHandler",
-        "ds-cfg-enabled: true",
-        "ds-cfg-listen-address: 127.0.0.1",
-        "ds-cfg-listen-port: " + listenPort,
-        "ds-cfg-accept-backlog: 128",
-        "ds-cfg-keep-stats: false",
-        "ds-cfg-use-tcp-keep-alive: true",
-        "ds-cfg-use-tcp-no-delay: true",
-        "ds-cfg-allow-tcp-reuse-address: true",
-        "ds-cfg-max-request-size: 5 megabytes",
-        "ds-cfg-buffer-size: 4096 bytes",
-        "ds-cfg-max-blocked-write-time-limit: 2 minutes",
-        "ds-cfg-use-ssl: false",
-        "ds-cfg-ssl-client-auth-policy: optional",
-        "ds-cfg-ssl-cert-nickname: server-cert");
-    HTTPConnectionHandlerCfg config =
-        InitializationUtils.getConfiguration(HTTPConnectionHandlerCfgDefn.getInstance(), handlerEntry);
-
-    HTTPConnectionHandler handler = new HTTPConnectionHandler();
-    handler.initializeConnectionHandler(DirectoryServer.getInstance().getServerContext(), config);
+    HTTPConnectionHandler handler = newHandler(listenPort, "127.0.0.1");
     try
     {
       handler.start();
@@ -88,10 +87,222 @@
     }
     finally
     {
-      handler.processServerShutdown(STOP_REASON);
-      handler.finalizeConnectionHandler(STOP_REASON);
-      handler.join(10000);
-      assertFalse(handler.isAlive(), "the connection handler thread is still running");
+      stop(handler);
     }
   }
+
+  /** A handler restricted to the loopback address must not answer on the other addresses of the host. */
+  @Test
+  public void listensOnlyOnTheConfiguredListenAddress() throws Exception
+  {
+    final InetAddress external = getNonLoopbackAddress();
+    final int listenPort = TestCaseUtils.findFreePort();
+    HTTPConnectionHandler handler = newHandler(listenPort, "127.0.0.1");
+    try
+    {
+      handler.start();
+
+      assertTrue(isAcceptingConnections(loopback(), listenPort), "the configured address is not listened on");
+      assertFalse(isAcceptingConnections(external, listenPort),
+          "the handler answers on " + external.getHostAddress() + ", which is not one of its listen addresses");
+      assertThat(handler.getListeners()).containsExactly(new HostPort("127.0.0.1", listenPort));
+    }
+    finally
+    {
+      stop(handler);
+    }
+  }
+
+  @DataProvider
+  public Object[][] useSSL()
+  {
+    return new Object[][] { { false }, { true } };
+  }
+
+  /** Each of several listen addresses gets its own listener, with the handler's SSL settings. */
+  @Test(dataProvider = "useSSL")
+  public void listensOnEveryConfiguredListenAddress(boolean useSSL) throws Exception
+  {
+    final InetAddress external = getNonLoopbackAddress();
+    final int listenPort = TestCaseUtils.findFreePort();
+    HTTPConnectionHandler handler = newHandler(useSSL, listenPort, "127.0.0.1", external.getHostAddress());
+    try
+    {
+      handler.start();
+
+      assertAnswers(loopback(), listenPort, useSSL);
+      assertAnswers(external, listenPort, useSSL);
+      assertThat(handler.getListeners()).containsOnly(
+          new HostPort("127.0.0.1", listenPort), new HostPort(external.getHostAddress(), listenPort));
+    }
+    finally
+    {
+      stop(handler);
+    }
+  }
+
+  /**
+   * When one listen address cannot be bound, the handler does not start, and must not keep the
+   * addresses it has already bound: nothing would ever release them.
+   * <p>
+   * The listener of 127.0.0.1 starts before the one of {@value #UNASSIGNED_ADDRESS}: the embedded
+   * server starts its listeners in the order of a hash map of their names.
+   */
+  @Test
+  public void releasesTheBoundListenAddressesWhenAnotherCannotBeBound() throws Exception
+  {
+    final int listenPort = TestCaseUtils.findFreePort();
+    HTTPConnectionHandler handler = newHandler(listenPort, "127.0.0.1", UNASSIGNED_ADDRESS);
+    try
+    {
+      handler.start();
+
+      // The handler thread tries twice, then disables the handler: wait for it to give up.
+      final InetAddress loopback = loopback();
+      new TestTimer.Builder().maxSleep(10, SECONDS).sleepTimes(100, MILLISECONDS).toTimer()
+          .repeatUntilSuccess(new TestTimer.CallableVoid()
+          {
+            @Override
+            public void call() throws Exception
+            {
+              assertFalse(isAcceptingConnections(loopback, listenPort),
+                  "127.0.0.1 is still listened on although the handler could not start");
+            }
+          });
+    }
+    finally
+    {
+      stop(handler);
+    }
+  }
+
+  /**
+   * A handler that could not start starts again after a change of its listen address, and then
+   * reports the address it listens on, not the one it was initialized with.
+   */
+  @Test
+  public void reportsTheListenAddressItStartsWithAfterAChange() throws Exception
+  {
+    final int listenPort = TestCaseUtils.findFreePort();
+    final int givenUp = DummyAlertHandler.getAlertCount(ALERT_TYPE_HTTP_CONNECTION_HANDLER_CONSECUTIVE_FAILURES);
+    HTTPConnectionHandler handler = newHandler(listenPort, UNASSIGNED_ADDRESS);
+    try
+    {
+      handler.start();
+
+      // The handler thread tries twice, then disables the handler: a change applied before it gives up is undone.
+      final TestTimer timer = new TestTimer.Builder().maxSleep(10, SECONDS).sleepTimes(100, MILLISECONDS).toTimer();
+      timer.repeatUntilSuccess(new TestTimer.CallableVoid()
+      {
+        @Override
+        public void call() throws Exception
+        {
+          assertThat(DummyAlertHandler.getAlertCount(ALERT_TYPE_HTTP_CONNECTION_HANDLER_CONSECUTIVE_FAILURES))
+              .as("the handler has not given up starting").isGreaterThan(givenUp);
+        }
+      });
+      final HTTPConnectionHandlerCfg changedConfig = newConfig(false, listenPort, "127.0.0.1");
+      final InetAddress loopback = loopback();
+      timer.repeatUntilSuccess(new TestTimer.CallableVoid()
+          {
+            @Override
+            public void call() throws Exception
+            {
+              // Applied again until it holds: the handler thread disables the handler just after it sends the alert.
+              handler.applyConfigurationChange(changedConfig);
+              assertTrue(isAcceptingConnections(loopback, listenPort), "the new listen address is not listened on");
+            }
+          });
+      assertThat(handler.getListeners()).containsExactly(new HostPort("127.0.0.1", listenPort));
+    }
+    finally
+    {
+      stop(handler);
+    }
+  }
+
+  /**
+   * Asserts that a listener answers on the address: over TLS, a completed handshake, which a
+   * listener without the handler's SSL settings cannot give.
+   */
+  private static void assertAnswers(InetAddress address, int port, boolean useSSL) throws Exception
+  {
+    if (!useSSL)
+    {
+      assertTrue(isAcceptingConnections(address, port), address.getHostAddress() + " is not listened on");
+      return;
+    }
+    final SSLContext client = SSLContext.getInstance("TLS");
+    client.init(null, new TrustManager[] { new BlindTrustManager() }, null);
+    try (SSLSocket socket = (SSLSocket) client.getSocketFactory().createSocket(address, port))
+    {
+      socket.setSoTimeout(10000);
+      socket.startHandshake();
+    }
+    catch (IOException e)
+    {
+      throw new AssertionError(address.getHostAddress() + " does not complete a TLS handshake", e);
+    }
+  }
+
+  private static HTTPConnectionHandler newHandler(int listenPort, String... listenAddresses) throws Exception
+  {
+    return newHandler(false, listenPort, listenAddresses);
+  }
+
+  private static HTTPConnectionHandler newHandler(boolean useSSL, int listenPort, String... listenAddresses)
+      throws Exception
+  {
+    HTTPConnectionHandler handler = new HTTPConnectionHandler();
+    handler.initializeConnectionHandler(DirectoryServer.getInstance().getServerContext(),
+        newConfig(useSSL, listenPort, listenAddresses));
+    return handler;
+  }
+
+  private static HTTPConnectionHandlerCfg newConfig(boolean useSSL, int listenPort, String... listenAddresses)
+      throws Exception
+  {
+    final List<String> ldif = new ArrayList<>();
+    Collections.addAll(ldif,
+        "dn: cn=HTTP Connection Handler,cn=Connection Handlers,cn=config",
+        "objectClass: top",
+        "objectClass: ds-cfg-connection-handler",
+        "objectClass: ds-cfg-http-connection-handler",
+        "cn: HTTP Connection Handler",
+        "ds-cfg-java-class: org.opends.server.protocols.http.HTTPConnectionHandler",
+        "ds-cfg-enabled: true");
+    for (String listenAddress : listenAddresses)
+    {
+      ldif.add("ds-cfg-listen-address: " + listenAddress);
+    }
+    Collections.addAll(ldif,
+        "ds-cfg-listen-port: " + listenPort,
+        "ds-cfg-accept-backlog: 128",
+        "ds-cfg-keep-stats: false",
+        "ds-cfg-use-tcp-keep-alive: true",
+        "ds-cfg-use-tcp-no-delay: true",
+        "ds-cfg-allow-tcp-reuse-address: true",
+        "ds-cfg-max-request-size: 5 megabytes",
+        "ds-cfg-buffer-size: 4096 bytes",
+        "ds-cfg-max-blocked-write-time-limit: 2 minutes",
+        "ds-cfg-use-ssl: " + useSSL,
+        "ds-cfg-key-manager-provider: cn=JKS,cn=Key Manager Providers,cn=config",
+        "ds-cfg-ssl-client-auth-policy: optional",
+        "ds-cfg-ssl-cert-nickname: server-cert");
+    Entry handlerEntry = TestCaseUtils.makeEntry(ldif.toArray(new String[0]));
+    return InitializationUtils.getConfiguration(HTTPConnectionHandlerCfgDefn.getInstance(), handlerEntry);
+  }
+
+  private static InetAddress loopback() throws UnknownHostException
+  {
+    return InetAddress.getByName("127.0.0.1");
+  }
+
+  private static void stop(HTTPConnectionHandler handler) throws InterruptedException
+  {
+    handler.processServerShutdown(STOP_REASON);
+    handler.finalizeConnectionHandler(STOP_REASON);
+    handler.join(10000);
+    assertFalse(handler.isAlive(), "the connection handler thread is still running");
+  }
 }
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/protocols/jmx/JmxListenAddressTestCase.java b/opendj-server-legacy/src/test/java/org/opends/server/protocols/jmx/JmxListenAddressTestCase.java
new file mode 100644
index 0000000..815d886
--- /dev/null
+++ b/opendj-server-legacy/src/test/java/org/opends/server/protocols/jmx/JmxListenAddressTestCase.java
@@ -0,0 +1,398 @@
+/*
+ * The contents of this file are subject to the terms of the Common Development and
+ * Distribution License (the License). You may not use this file except in compliance with the
+ * License.
+ *
+ * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ * specific language governing permission and limitations under the License.
+ *
+ * When distributing Covered Software, include this CDDL Header Notice in each file and include
+ * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ * Header, with the fields enclosed by brackets [] replaced by your own identifying
+ * information: "Portions copyright [year] [name of copyright owner]".
+ *
+ * Copyright 2026 3A Systems, LLC.
+ */
+package org.opends.server.protocols.jmx;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.opends.server.TestCaseUtils.*;
+import static org.testng.Assert.*;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.rmi.registry.LocateRegistry;
+import java.rmi.registry.Registry;
+import java.security.cert.X509Certificate;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.management.remote.JMXConnector;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509ExtendedTrustManager;
+
+import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.opendj.config.server.ConfigChangeResult;
+import org.forgerock.opendj.ldap.ResultCode;
+import org.forgerock.opendj.server.config.meta.JMXConnectionHandlerCfgDefn;
+import org.forgerock.opendj.server.config.server.JMXConnectionHandlerCfg;
+import org.opends.server.DirectoryServerTestCase;
+import org.opends.server.TestCaseUtils;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.extensions.InitializationUtils;
+import org.opends.server.types.Entry;
+import org.opends.server.types.HostPort;
+import org.testng.SkipException;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+/**
+ * The JMX connection handler listens on its listen address only: with both its RMI registry, on
+ * the listen port, and its RMI connector, on the RMI port. A JMX client reaches it there.
+ */
+@SuppressWarnings("javadoc")
+@Test(groups = { "precommit", "jmx" }, sequential = true)
+public class JmxListenAddressTestCase extends DirectoryServerTestCase
+{
+  private static final LocalizableMessage STOP_REASON = LocalizableMessage.raw("Don't need a reason.");
+
+  private static final String USER_DN = "cn=JMX Reader,o=test";
+  private static final String USER_PASSWORD = "password";
+
+  private static final String RMI_SERVER_HOSTNAME = "java.rmi.server.hostname";
+
+  /** The address the handlers listen on. */
+  private InetAddress listenAddress;
+  /** An address of the host the handlers do not listen on. */
+  private InetAddress otherAddress;
+  /** The default SSL context of the JVM before this test replaced it. */
+  private SSLContext defaultSSLContext;
+
+  @BeforeClass
+  public void setUp() throws Exception
+  {
+    TestCaseUtils.startServer();
+    TestCaseUtils.initializeTestBackend(true);
+    TestCaseUtils.addEntries(
+        "dn: " + USER_DN,
+        "objectClass: top",
+        "objectClass: person",
+        "objectClass: organizationalPerson",
+        "objectClass: inetOrgPerson",
+        "cn: JMX Reader",
+        "sn: Reader",
+        "userPassword: " + USER_PASSWORD,
+        "ds-privilege-name: jmx-read",
+        "ds-pwp-password-policy-dn: cn=Clear UserPassword Policy,cn=Password Policies,cn=config");
+
+    // The stub of the RMI connector advertises the local host, unless told otherwise: listen on
+    // the address that is not the local host's, or a session would get through even with a stub
+    // that does not advertise the listen address. When the local host is exactly 127.0.0.1, the
+    // JDK learns its host from the connector's own binding in the registry instead, and no
+    // choice of address makes that difference visible.
+    final InetAddress loopback = InetAddress.getByName("127.0.0.1");
+    final InetAddress external = getNonLoopbackAddress();
+    final boolean localHostIsLoopback =
+        loopback.getHostAddress().equals(InetAddress.getLocalHost().getHostAddress());
+    listenAddress = localHostIsLoopback ? external : loopback;
+    otherAddress = localHostIsLoopback ? loopback : external;
+
+    // The stub of an SSL connector carries an SslRMIClientSocketFactory, which takes the default
+    // SSL context of the JVM the first time anything uses it, and keeps it: that happens as soon as
+    // the connector starts, before any client of this test connects.
+    defaultSSLContext = SSLContext.getDefault();
+    final SSLContext blindContext = SSLContext.getInstance("TLS");
+    blindContext.init(null, new TrustManager[] { new BlindExtendedTrustManager() }, null);
+    SSLContext.setDefault(blindContext);
+  }
+
+  /**
+   * Trusts any server, whatever host name it is reached by: the factory checks the host name of
+   * the server when the JDK asks for it, and a trust manager that is not an extended one gets that
+   * check added by the JDK. The test certificate names no host.
+   */
+  private static final class BlindExtendedTrustManager extends X509ExtendedTrustManager
+  {
+    @Override
+    public void checkClientTrusted(X509Certificate[] chain, String authType)
+    {
+      // Trusts any client.
+    }
+
+    @Override
+    public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket)
+    {
+      // Trusts any client.
+    }
+
+    @Override
+    public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine)
+    {
+      // Trusts any client.
+    }
+
+    @Override
+    public void checkServerTrusted(X509Certificate[] chain, String authType)
+    {
+      // Trusts any server.
+    }
+
+    @Override
+    public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket)
+    {
+      // Trusts any server.
+    }
+
+    @Override
+    public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine)
+    {
+      // Trusts any server.
+    }
+
+    @Override
+    public X509Certificate[] getAcceptedIssuers()
+    {
+      return new X509Certificate[0];
+    }
+  }
+
+  @AfterClass(alwaysRun = true)
+  public void restoreDefaultSSLContext()
+  {
+    if (defaultSSLContext != null)
+    {
+      SSLContext.setDefault(defaultSSLContext);
+    }
+  }
+
+  @DataProvider
+  public Object[][] useSSL()
+  {
+    return new Object[][] { { false }, { true } };
+  }
+
+  @Test(dataProvider = "useSSL")
+  public void listensOnlyOnTheConfiguredListenAddress(boolean useSSL) throws Exception
+  {
+    final int[] ports = TestCaseUtils.findFreePorts(2);
+    final int listenPort = ports[0];
+    final int rmiPort = ports[1];
+
+    JmxConnectionHandler handler = newHandler(listenAddress, listenPort, rmiPort, useSSL);
+    try
+    {
+      // Starts the RMI registry and the RMI connector in this thread.
+      handler.run();
+      assertNotNull(handler.getRMIConnector().jmxRmiConnectorNoClientCertificate, "the RMI connector did not start");
+
+      assertListensOnlyOn(listenAddress, listenPort, rmiPort);
+      assertThat(handler.getListeners()).containsExactly(new HostPort(listenAddress.getHostAddress(), listenPort));
+      assertOpensASession(listenAddress, listenPort);
+    }
+    finally
+    {
+      handler.finalizeConnectionHandler(STOP_REASON);
+    }
+  }
+
+  /** A change that restarts the RMI connector keeps the listen address that is reported. */
+  @Test
+  public void reportsTheListenAddressAfterAChangeOfTheRmiPort() throws Exception
+  {
+    final int[] ports = TestCaseUtils.findFreePorts(3);
+    final int listenPort = ports[0];
+
+    JmxConnectionHandler handler = newHandler(listenAddress, listenPort, ports[1], false);
+    try
+    {
+      handler.run();
+      applyConfigurationChange(handler, newConfig(listenAddress, listenPort, ports[2], false));
+
+      assertListensOnlyOn(listenAddress, listenPort, ports[2]);
+      assertThat(handler.getListeners()).containsExactly(new HostPort(listenAddress.getHostAddress(), listenPort));
+    }
+    finally
+    {
+      handler.finalizeConnectionHandler(STOP_REASON);
+    }
+  }
+
+  /**
+   * A change of listen-address takes effect when the handler restarts: until then, the RMI
+   * registry, the RMI connector restarted by another change and the listeners stay on the address
+   * the handler was initialized with.
+   */
+  @Test
+  public void keepsItsListenAddressUntilItRestarts() throws Exception
+  {
+    final int[] ports = TestCaseUtils.findFreePorts(3);
+    final int listenPort = ports[0];
+
+    JmxConnectionHandler handler = newHandler(listenAddress, listenPort, ports[1], false);
+    try
+    {
+      handler.run();
+      applyConfigurationChange(handler, newConfig(otherAddress, listenPort, ports[2], false));
+
+      assertListensOnlyOn(listenAddress, listenPort, ports[2]);
+      assertThat(handler.getListeners()).containsExactly(new HostPort(listenAddress.getHostAddress(), listenPort));
+      assertOpensASession(listenAddress, listenPort);
+    }
+    finally
+    {
+      handler.finalizeConnectionHandler(STOP_REASON);
+    }
+  }
+
+  /**
+   * A handler restarted on the wildcard address no longer sends clients to the loopback address a
+   * previous one listened on.
+   */
+  @Test
+  public void stopsAdvertisingTheLoopbackAddressOnTheWildcardAddress() throws Exception
+  {
+    if (InetAddress.getLocalHost().isLoopbackAddress())
+    {
+      throw new SkipException("the JDK advertises a loopback address anyway");
+    }
+    final int[] ports = TestCaseUtils.findFreePorts(4);
+    JmxConnectionHandler handler = newHandler(InetAddress.getByName("127.0.0.1"), ports[0], ports[1], false);
+    try
+    {
+      handler.run();
+    }
+    finally
+    {
+      handler.finalizeConnectionHandler(STOP_REASON);
+    }
+
+    handler = newHandler(InetAddress.getByName("0.0.0.0"), ports[2], ports[3], false);
+    try
+    {
+      handler.run();
+
+      final Registry registry = LocateRegistry.getRegistry("127.0.0.1", ports[2]);
+      final String[] names = registry.list();
+      assertThat(names).isNotEmpty();
+      for (String name : names)
+      {
+        assertThat(registry.lookup(name).toString()).doesNotContain("[127.0.0.1:").doesNotContain("[0.0.0.0:");
+      }
+    }
+    finally
+    {
+      handler.finalizeConnectionHandler(STOP_REASON);
+    }
+  }
+
+  /**
+   * The host that the operator gives the stubs to advertise, behind a NAT for instance, is kept.
+   * <p>
+   * The name is one the server never records itself, which advertises addresses only. It is
+   * reachable, since the registry calls the stubs bound in it. Runs last: once cleared, the property
+   * leaves the JDK advertising the host it last read.
+   */
+  @Test(priority = 1)
+  public void keepsTheOperatorsRmiServerHostname() throws Exception
+  {
+    final int[] ports = TestCaseUtils.findFreePorts(2);
+    final String operatorsHostname = "localhost";
+    System.setProperty(RMI_SERVER_HOSTNAME, operatorsHostname);
+    JmxConnectionHandler handler = newHandler(listenAddress, ports[0], ports[1], false);
+    try
+    {
+      handler.run();
+      assertNotNull(handler.getRMIConnector().jmxRmiConnectorNoClientCertificate, "the RMI connector did not start");
+
+      assertEquals(System.getProperty(RMI_SERVER_HOSTNAME), operatorsHostname);
+    }
+    finally
+    {
+      handler.finalizeConnectionHandler(STOP_REASON);
+      System.clearProperty(RMI_SERVER_HOSTNAME);
+    }
+  }
+
+  private void assertListensOnlyOn(InetAddress address, int listenPort, int rmiPort) throws IOException
+  {
+    final InetAddress other = address.equals(listenAddress) ? otherAddress : listenAddress;
+    assertReachable(other);
+
+    assertTrue(isAcceptingConnections(address, listenPort),
+        "the RMI registry does not listen on " + address.getHostAddress());
+    assertFalse(isAcceptingConnections(other, listenPort),
+        "the RMI registry answers on " + other.getHostAddress() + ", which is not the listen address");
+    assertTrue(isAcceptingConnections(address, rmiPort),
+        "the RMI connector does not listen on " + address.getHostAddress());
+    assertFalse(isAcceptingConnections(other, rmiPort),
+        "the RMI connector answers on " + other.getHostAddress() + ", which is not the listen address");
+  }
+
+  /** A refusal on an address proves something only if a listener on every address answers there. */
+  private static void assertReachable(InetAddress address) throws IOException
+  {
+    try (ServerSocket control = new ServerSocket(0))
+    {
+      assertTrue(isAcceptingConnections(address, control.getLocalPort()),
+          address.getHostAddress() + " is not reachable from this host, so a refusal on it proves nothing");
+    }
+  }
+
+  /**
+   * Opens a JMX session through the RMI registry, then calls the RMI connector: the call goes to
+   * the address that the stub of the connector advertises.
+   */
+  private static void assertOpensASession(InetAddress address, int listenPort) throws Exception
+  {
+    final Map<String, Object> env = new HashMap<>();
+    env.put(JMXConnector.CREDENTIALS, new String[] { USER_DN, USER_PASSWORD });
+    env.put("jmx.remote.x.client.connection.check.period", 0);
+
+    try (OpendsJmxConnector connector = new OpendsJmxConnector(address.getHostAddress(), listenPort, env))
+    {
+      connector.connect();
+      assertThat(connector.getMBeanServerConnection().getMBeanCount()).isPositive();
+    }
+  }
+
+  private static void applyConfigurationChange(JmxConnectionHandler handler, JMXConnectionHandlerCfg config)
+  {
+    final ConfigChangeResult result = handler.applyConfigurationChange(config);
+    assertEquals(result.getResultCode(), ResultCode.SUCCESS, String.valueOf(result.getMessages()));
+  }
+
+  private static JmxConnectionHandler newHandler(InetAddress listenAddress, int listenPort, int rmiPort,
+      boolean useSSL) throws Exception
+  {
+    JmxConnectionHandler handler = new JmxConnectionHandler();
+    handler.initializeConnectionHandler(DirectoryServer.getInstance().getServerContext(),
+        newConfig(listenAddress, listenPort, rmiPort, useSSL));
+    return handler;
+  }
+
+  private static JMXConnectionHandlerCfg newConfig(InetAddress listenAddress, int listenPort, int rmiPort,
+      boolean useSSL) throws Exception
+  {
+    Entry handlerEntry = TestCaseUtils.makeEntry(
+        "dn: cn=Listen Address JMX Connection Handler,cn=Connection Handlers,cn=config",
+        "objectClass: top",
+        "objectClass: ds-cfg-connection-handler",
+        "objectClass: ds-cfg-jmx-connection-handler",
+        "cn: Listen Address JMX Connection Handler",
+        "ds-cfg-java-class: org.opends.server.protocols.jmx.JmxConnectionHandler",
+        "ds-cfg-enabled: true",
+        "ds-cfg-listen-address: " + listenAddress.getHostAddress(),
+        "ds-cfg-listen-port: " + listenPort,
+        "ds-cfg-rmi-port: " + rmiPort,
+        "ds-cfg-use-ssl: " + useSSL,
+        "ds-cfg-key-manager-provider: cn=JKS,cn=Key Manager Providers,cn=config",
+        "ds-cfg-ssl-cert-nickname: server-cert");
+    return InitializationUtils.getConfiguration(JMXConnectionHandlerCfgDefn.getInstance(), handlerEntry);
+  }
+}

--
Gitblit v1.10.0