Repository: eclipse/leshan
Pull request #826 (2020-04-07 09:59:11)

Merge Server ans  ServerIdentity class
After my cleaning PR about javadoc (#824), I discover API inconsistency which bring me to modify the client API a lot ... :disappointed: 

But I think this is for the best. 

So this PR aims to : 
- make the API more consistency by using only one object which represent a server.
- this implies merging of `org.eclipse.leshan.client.servers.Server` and `ServerIdentity`
- this removes the conflict name between `org.eclipse.leshan.client.servers.Server` and `org.eclipse.leshan.client.object.Server`
- this fix some security issue by ensuring we talk to the expected server.
- this make easier to implement execute resource like triggerUpdate(/1/0/8)
- this should make easier to add support of multi server at client side.
- this should make easier to add support of ACL at client side.


Patch:
diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/CaliforniumEndpointsManager.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/CaliforniumEndpointsManager.java
index 23766d59..23046ce6 100644
--- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/CaliforniumEndpointsManager.java
+++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/CaliforniumEndpointsManager.java
@@ -42,7 +42,8 @@ import org.eclipse.californium.scandium.dtls.rpkstore.TrustedRpkStore;
 import org.eclipse.californium.scandium.dtls.x509.CertificateVerifier;
 import org.eclipse.leshan.SecurityMode;
 import org.eclipse.leshan.client.EndpointsManager;
-import org.eclipse.leshan.client.servers.Server;
+import org.eclipse.leshan.client.servers.ServerIdentity;
+import org.eclipse.leshan.client.servers.ServerIdentity.Role;
 import org.eclipse.leshan.client.servers.ServerInfo;
 import org.eclipse.leshan.core.californium.EndpointContextUtil;
 import org.eclipse.leshan.core.californium.EndpointFactory;
@@ -51,7 +52,8 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * An {@link EndpointsManager} based on Californium(CoAP implementation) and Scandium (DTLS implementation).
+ * An {@link EndpointsManager} based on Californium(CoAP implementation) and Scandium (DTLS implementation) which
+ * supports only 1 server.
  */
 public class CaliforniumEndpointsManager implements EndpointsManager {
 
@@ -59,24 +61,29 @@ public class CaliforniumEndpointsManager implements EndpointsManager {
 
     protected boolean started = false;
 
+    protected ServerIdentity currentServer;
     protected CoapEndpoint currentEndpoint;
+
     protected Builder dtlsConfigbuilder;
     protected NetworkConfig coapConfig;
     protected InetSocketAddress localAddress;
     protected CoapServer coapServer;
     protected EndpointFactory endpointFactory;
 
-    public CaliforniumEndpointsManager(CoapServer coapServer, InetSocketAddress localAddress, NetworkConfig coapConfig,
+    public CaliforniumEndpointsManager(InetSocketAddress localAddress, NetworkConfig coapConfig,
             Builder dtlsConfigBuilder, EndpointFactory endpointFactory) {
-        this.coapServer = coapServer;
         this.localAddress = localAddress;
         this.coapConfig = coapConfig;
         this.dtlsConfigbuilder = dtlsConfigBuilder;
         this.endpointFactory = endpointFactory;
     }
 
+    public void setCoapServer(CoapServer coapServer) {
+        this.coapServer = coapServer;
+    }
+
     @Override
-    public synchronized Server createEndpoint(ServerInfo serverInfo) {
+    public synchronized ServerIdentity createEndpoint(ServerInfo serverInfo) {
         // Clear previous endpoint
         if (currentEndpoint != null) {
             coapServer.getEndpoints().remove(currentEndpoint);
@@ -166,34 +173,43 @@ public class CaliforniumEndpointsManager implements EndpointsManager {
         coapServer.addEndpoint(currentEndpoint);
 
         // Start endpoint if needed
-        Server server = new Server(serverIdentity, serverInfo.serverId);
+        if (serverInfo.bootstrap) {
+            currentServer = new ServerIdentity(serverIdentity, serverInfo.serverId, Role.LWM2M_BOOTSTRAP_SERVER);
+        } else {
+            currentServer = new ServerIdentity(serverIdentity, serverInfo.serverId);
+        }
         if (started) {
             coapServer.start();
             try {
                 currentEndpoint.start();
-                LOG.info("New endpoint created for server {} at {}", server.getUri(), currentEndpoint.getUri());
+                LOG.info("New endpoint created for server {} at {}", currentServer.getUri(), currentEndpoint.getUri());
             } catch (IOException e) {
                 throw new RuntimeException("Unable to start endpoint", e);
             }
         }
-        return server;
+        return currentServer;
     }
 
     @Override
-    public synchronized Collection<Server> createEndpoints(Collection<? extends ServerInfo> serverInfo) {
+    public synchronized Collection<ServerIdentity> createEndpoints(Collection<? extends ServerInfo> serverInfo) {
         if (serverInfo == null || serverInfo.isEmpty())
             return null;
         else {
             // TODO support multi server
+            if (serverInfo.size() > 1) {
+                LOG.warn(
+                        "CaliforniumEndpointsManager support only connection to 1 LWM2M server, first server will be used from the server list of {}",
+                        serverInfo.size());
+            }
             ServerInfo firstServer = serverInfo.iterator().next();
-            Collection<Server> servers = new ArrayList<>(1);
+            Collection<ServerIdentity> servers = new ArrayList<>(1);
             servers.add(createEndpoint(firstServer));
             return servers;
         }
     }
 
     @Override
-    public long getMaxCommunicationPeriodFor(Server server, long lifetimeInMs) {
+    public long getMaxCommunicationPeriodFor(ServerIdentity server, long lifetimeInMs) {
         // See https://github.com/OpenMobileAlliance/OMA_LwM2M_for_Developers/issues/283 to better understand.
         // TODO For DTLS, worst Handshake scenario should be taking into account too.
 
@@ -215,8 +231,11 @@ public class CaliforniumEndpointsManager implements EndpointsManager {
     }
 
     @Override
-    public synchronized void forceReconnection(Server server, boolean resume) {
+    public synchronized void forceReconnection(ServerIdentity server, boolean resume) {
         // TODO support multi server
+        if (server == null || !server.equals(currentServer))
+            return;
+
         Connector connector = currentEndpoint.getConnector();
         if (connector instanceof DTLSConnector) {
             if (resume) {
@@ -230,9 +249,9 @@ public class CaliforniumEndpointsManager implements EndpointsManager {
 
     }
 
-    public synchronized Endpoint getEndpoint(Identity server) {
+    public synchronized Endpoint getEndpoint(ServerIdentity server) {
         // TODO support multi server
-        if (currentEndpoint.isStarted())
+        if (server != null && server.equals(currentServer) && currentEndpoint.isStarted())
             return currentEndpoint;
         return null;
     }
diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LeshanClient.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LeshanClient.java
index c405ec76..cbf6e934 100644
--- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LeshanClient.java
+++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LeshanClient.java
@@ -42,6 +42,7 @@ import org.eclipse.leshan.client.resource.LwM2mObjectEnabler;
 import org.eclipse.leshan.client.resource.LwM2mObjectTree;
 import org.eclipse.leshan.client.resource.listener.ObjectListener;
 import org.eclipse.leshan.client.resource.listener.ObjectsListenerAdapter;
+import org.eclipse.leshan.client.servers.ServerIdentity;
 import org.eclipse.leshan.core.californium.EndpointFactory;
 import org.eclipse.leshan.core.node.codec.LwM2mNodeDecoder;
 import org.eclipse.leshan.core.node.codec.LwM2mNodeEncoder;
@@ -80,14 +81,15 @@ public class LeshanClient implements LwM2mClient {
         objectTree = createObjectTree(objectEnablers);
         observers = createClientObserverDispatcher();
         bootstrapHandler = createBoostrapHandler(objectTree);
-        coapServer = createCoapServer(coapConfig, sharedExecutor);
-        linkObjectTreeToCoapServer(coapServer, bootstrapHandler, objectTree, encoder, decoder);
-        coapServer.add(createBootstrapResource(bootstrapHandler));
-        endpointsManager = createEndpointsManager(coapServer, localAddress, coapConfig, dtlsConfigBuilder,
-                endpointFactory);
+        endpointsManager = createEndpointsManager(localAddress, coapConfig, dtlsConfigBuilder, endpointFactory);
         requestSender = createRequestSender(endpointsManager, sharedExecutor);
         engine = engineFactory.createRegistratioEngine(endpoint, objectTree, endpointsManager, requestSender,
                 bootstrapHandler, observers, additionalAttributes, sharedExecutor);
+
+        coapServer = createCoapServer(coapConfig, sharedExecutor);
+        coapServer.add(createBootstrapResource(engine, bootstrapHandler));
+        endpointsManager.setCoapServer(coapServer);
+        linkObjectTreeToCoapServer(coapServer, engine, objectTree, encoder, decoder);
         createRegistrationUpdateHandler(engine, endpointsManager, bootstrapHandler, objectTree);
 
         coapApi = new CoapAPI();
@@ -111,7 +113,7 @@ public class LeshanClient implements LwM2mClient {
             @Override
             protected Resource createRoot() {
                 // Use to handle Delete on "/"
-                return new org.eclipse.leshan.client.californium.RootResource(bootstrapHandler, this);
+                return new org.eclipse.leshan.client.californium.RootResource(engine, bootstrapHandler, this);
             }
         };
 
@@ -128,12 +130,12 @@ public class LeshanClient implements LwM2mClient {
         return coapServer;
     }
 
-    protected void linkObjectTreeToCoapServer(final CoapServer coapServer, final BootstrapHandler bootstrapHandler,
+    protected void linkObjectTreeToCoapServer(final CoapServer coapServer, final RegistrationEngine registrationEngine,
             LwM2mObjectTree objectTree, final LwM2mNodeEncoder encoder, final LwM2mNodeDecoder decoder) {
 
         // Create CoAP resources for each lwm2m Objects.
         for (LwM2mObjectEnabler enabler : objectTree.getObjectEnablers().values()) {
-            CoapResource clientObject = createObjectResource(enabler, bootstrapHandler, encoder, decoder);
+            CoapResource clientObject = createObjectResource(enabler, registrationEngine, encoder, decoder);
             coapServer.add(clientObject);
         }
 
@@ -141,7 +143,7 @@ public class LeshanClient implements LwM2mClient {
         objectTree.addListener(new ObjectsListenerAdapter() {
             @Override
             public void objectAdded(LwM2mObjectEnabler object) {
-                CoapResource clientObject = createObjectResource(object, bootstrapHandler, encoder, decoder);
+                CoapResource clientObject = createObjectResource(object, registrationEngine, encoder, decoder);
                 coapServer.add(clientObject);
             }
 
@@ -156,19 +158,18 @@ public class LeshanClient implements LwM2mClient {
         });
     }
 
-    protected CoapResource createObjectResource(LwM2mObjectEnabler enabler, BootstrapHandler bootstrapHandler,
+    protected CoapResource createObjectResource(LwM2mObjectEnabler enabler, RegistrationEngine registrationEngine,
             LwM2mNodeEncoder encoder, LwM2mNodeDecoder decoder) {
-        return new ObjectResource(enabler, bootstrapHandler, encoder, decoder);
+        return new ObjectResource(enabler, registrationEngine, encoder, decoder);
     }
 
-    protected CoapResource createBootstrapResource(BootstrapHandler bootstrapHandler) {
-        return new BootstrapResource(bootstrapHandler);
+    protected CoapResource createBootstrapResource(RegistrationEngine engine, BootstrapHandler bootstrapHandler) {
+        return new BootstrapResource(engine, bootstrapHandler);
     }
 
-    protected CaliforniumEndpointsManager createEndpointsManager(CoapServer coapServer, InetSocketAddress localAddress,
+    protected CaliforniumEndpointsManager createEndpointsManager(InetSocketAddress localAddress,
             NetworkConfig coapConfig, Builder dtlsConfigBuilder, EndpointFactory endpointFactory) {
-        return new CaliforniumEndpointsManager(coapServer, localAddress, coapConfig, dtlsConfigBuilder,
-                endpointFactory);
+        return new CaliforniumEndpointsManager(localAddress, coapConfig, dtlsConfigBuilder, endpointFactory);
     }
 
     protected CaliforniumLwM2mRequestSender createRequestSender(CaliforniumEndpointsManager endpointsManager,
@@ -221,6 +222,11 @@ public class LeshanClient implements LwM2mClient {
         engine.triggerRegistrationUpdate();
     }
 
+    @Override
+    public void triggerRegistrationUpdate(ServerIdentity server) {
+        engine.triggerRegistrationUpdate(server);
+    }
+
     /**
      * A CoAP API, generally needed if you want to access to underlying CoAP layer.
      */
@@ -237,15 +243,12 @@ public class LeshanClient implements LwM2mClient {
         }
 
         /**
-         * Returns the current {@link CoapEndpoint} used to communicate with the server.
-         * <p>
-         * A different endpoint address should be used by connected server, so this method only make sense as current
-         * implementation supports only one LWM2M server.
+         * Returns the current {@link CoapEndpoint} used to communicate with the given server.
          * 
          * @return the {@link CoapEndpoint} used to communicate to LWM2M server.
          */
-        public CoapEndpoint getEndpoint() {
-            return (CoapEndpoint) endpointsManager.getEndpoint(null);
+        public CoapEndpoint getEndpoint(ServerIdentity server) {
+            return (CoapEndpoint) endpointsManager.getEndpoint(server);
         }
     }
 
@@ -264,26 +267,27 @@ public class LeshanClient implements LwM2mClient {
     }
 
     /**
-     * Returns the current registration Id.
-     * <p>
-     * Client should have 1 registration Id by connected server, so this method only make sense as current
-     * implementation supports only one LWM2M server.
+     * Returns the registration Id for the given server.
      * 
      * @return the client registration Id or <code>null</code> if the client is not registered
      */
-    public String getRegistrationId() {
-        return engine.getRegistrationId();
+    public String getRegistrationId(ServerIdentity server) {
+        return engine.getRegistrationId(server);
+    }
+
+    /**
+     * @return All the registered Server indexed by the corresponding registration id;
+     */
+    public Map<String, ServerIdentity> getRegisteredServers() {
+        return engine.getRegisteredServers();
     }
 
     /**
-     * Returns the current {@link InetSocketAddress} use to communicate with the server.
-     * <p>
-     * A different endpoint/socket address should be used by connected server, so this method only make sense as current
-     * implementation supports only one LWM2M server.
+     * Returns the current {@link InetSocketAddress} use to communicate with the given server.
      * 
      * @return the address used to connect to the server or <code>null</code> if the client is not started.
      */
-    public InetSocketAddress getAddress() {
-        return endpointsManager.getEndpoint(null).getAddress();
+    public InetSocketAddress getAddress(ServerIdentity server) {
+        return endpointsManager.getEndpoint(server).getAddress();
     }
 }
diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LeshanClientBuilder.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LeshanClientBuilder.java
index 0dcd1d52..3c69db7d 100644
--- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LeshanClientBuilder.java
+++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LeshanClientBuilder.java
@@ -25,7 +25,6 @@ import org.eclipse.californium.core.network.CoapEndpoint;
 import org.eclipse.californium.core.network.config.NetworkConfig;
 import org.eclipse.californium.core.network.config.NetworkConfig.Keys;
 import org.eclipse.californium.elements.Connector;
-import org.eclipse.californium.elements.EndpointContextMatcher;
 import org.eclipse.californium.elements.UDPConnector;
 import org.eclipse.californium.scandium.DTLSConnector;
 import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
@@ -247,11 +246,6 @@ public class LeshanClientBuilder {
         }
         if (endpointFactory == null) {
             endpointFactory = new DefaultEndpointFactory("LWM2M Client") {
-                @Override
-                protected EndpointContextMatcher createSecuredContextMatcher() {
-                    return null; // use default californium one.
-                }
-
                 @Override
                 protected Connector createSecuredConnector(DtlsConnectorConfig dtlsConfig) {
                     DTLSConnector dtlsConnector = new DTLSConnector(dtlsConfig);
diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LwM2mClientCoapResource.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LwM2mClientCoapResource.java
index 8d27ab42..773f182c 100644
--- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LwM2mClientCoapResource.java
+++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LwM2mClientCoapResource.java
@@ -16,12 +16,11 @@
 package org.eclipse.leshan.client.californium;
 
 import org.eclipse.californium.core.CoapResource;
+import org.eclipse.californium.core.coap.CoAP.ResponseCode;
 import org.eclipse.californium.core.network.Exchange;
 import org.eclipse.californium.core.server.resources.CoapExchange;
-import org.eclipse.californium.elements.EndpointContext;
-import org.eclipse.leshan.client.bootstrap.BootstrapHandler;
-import org.eclipse.leshan.client.request.ServerIdentity;
-import org.eclipse.leshan.core.californium.EndpointContextUtil;
+import org.eclipse.leshan.client.engine.RegistrationEngine;
+import org.eclipse.leshan.client.servers.ServerIdentity;
 import org.eclipse.leshan.core.californium.LwM2mCoapResource;
 import org.eclipse.leshan.core.request.Identity;
 
@@ -30,43 +29,45 @@ import org.eclipse.leshan.core.request.Identity;
  */
 public class LwM2mClientCoapResource extends LwM2mCoapResource {
 
-    protected final BootstrapHandler bootstrapHandler;
+    protected final RegistrationEngine registrationEngine;
 
-    public LwM2mClientCoapResource(String name, BootstrapHandler bootstrapHandler) {
+    public LwM2mClientCoapResource(String name, RegistrationEngine registrationEngine) {
         super(name);
-        this.bootstrapHandler = bootstrapHandler;
+        this.registrationEngine = registrationEngine;
     }
 
-    @Override
-    protected Identity extractIdentity(EndpointContext context) {
-        return extractServerIdentity(context);
+    /**
+     * @return the server identity of a registered or bootstrap server, return null if this identity does match to any
+     *         server for which we are in communication.
+     */
+    protected ServerIdentity getServer(Identity identity) {
+        return registrationEngine.getServer(identity);
     }
 
     /**
-     * Create Leshan {@link ServerIdentity} from Californium {@link EndpointContext}.
-     * 
-     * @param context The Californium {@link EndpointContext} to convert.
-     * @return The corresponding Leshan {@link ServerIdentity}.
-     * @throws IllegalStateException if we are not able to extract {@link ServerIdentity}.
+     * Extract the {@link ServerIdentity} for this exchange. If there is no corresponding server currently in
+     * communication with this client. Answer with an {@link ResponseCode#INTERNAL_SERVER_ERROR}.
      */
-    protected ServerIdentity extractServerIdentity(EndpointContext context) {
-        Identity identity = EndpointContextUtil.extractIdentity(context);
+    protected ServerIdentity getServerOrRejectRequest(CoapExchange exchange) {
+        Identity extractedIdentity = extractIdentity(exchange);
 
-        if (bootstrapHandler.isBootstrapServer(identity)) {
-            return ServerIdentity.createLwm2mBootstrapServerIdentity(identity);
-        }
+        // search if we are in communication with this server.
+        ServerIdentity server = getServer(extractedIdentity);
+        if (server != null)
+            return server;
 
-        return ServerIdentity.createLwm2mServerIdentity(identity);
+        exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR, "unknown server");
+        return null;
     }
 
     /**
-     * Create Leshan {@link ServerIdentity} from Californium {@link Exchange}.
+     * Create Leshan {@link Identity} from Californium {@link Exchange}.
      * 
      * @param exchange The Californium {@link Exchange} containing the request for which we search sender identity.
-     * @return The corresponding Leshan {@link ServerIdentity}.
-     * @throws IllegalStateException if we are not able to extract {@link ServerIdentity}.
+     * @return The corresponding Leshan {@link Identity}.
+     * @throws IllegalStateException if we are not able to extract {@link Identity}.
      */
-    protected ServerIdentity extractServerIdentity(CoapExchange exchange) {
-        return extractServerIdentity(exchange.advanced().getRequest().getSourceContext());
+    protected Identity extractIdentity(CoapExchange exchange) {
+        return extractIdentity(exchange.advanced().getRequest().getSourceContext());
     }
 }
diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/RootResource.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/RootResource.java
index 8c4c859f..67413c5f 100644
--- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/RootResource.java
+++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/RootResource.java
@@ -26,7 +26,8 @@ import org.eclipse.californium.core.coap.CoAP.ResponseCode;
 import org.eclipse.californium.core.network.Endpoint;
 import org.eclipse.californium.core.server.resources.CoapExchange;
 import org.eclipse.leshan.client.bootstrap.BootstrapHandler;
-import org.eclipse.leshan.client.request.ServerIdentity;
+import org.eclipse.leshan.client.engine.RegistrationEngine;
+import org.eclipse.leshan.client.servers.ServerIdentity;
 import org.eclipse.leshan.core.request.BootstrapDeleteRequest;
 import org.eclipse.leshan.core.response.BootstrapDeleteResponse;
 import org.eclipse.leshan.util.StringUtils;
@@ -37,9 +38,12 @@ import org.eclipse.leshan.util.StringUtils;
 public class RootResource extends LwM2mClientCoapResource {
 
     protected CoapServer coapServer;
+    protected BootstrapHandler bootstrapHandler;
 
-    public RootResource(BootstrapHandler bootstrapHandler, CoapServer coapServer) {
-        super("", bootstrapHandler);
+    public RootResource(RegistrationEngine registrationEngine, BootstrapHandler bootstrapHandler,
+            CoapServer coapServer) {
+        super("", registrationEngine);
+        this.bootstrapHandler = bootstrapHandler;
         setVisible(false);
         this.coapServer = coapServer;
     }
@@ -56,7 +60,10 @@ public class RootResource extends LwM2mClientCoapResource {
             return;
         }
 
-        ServerIdentity identity = extractServerIdentity(exchange);
+        ServerIdentity identity = getServerOrRejectRequest(exchange);
+        if (identity == null)
+            return;
+
         BootstrapDeleteResponse response = bootstrapHandler.delete(identity, new BootstrapDeleteRequest());
         exchange.respond(toCoapResponseCode(response.getCode()), response.getErrorMessage());
     }
diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/SecurityObjectPskStore.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/SecurityObjectPskStore.java
index 5c9dbce5..0303a553 100644
--- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/SecurityObjectPskStore.java
+++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/SecurityObjectPskStore.java
@@ -17,7 +17,7 @@
 package org.eclipse.leshan.client.californium;
 
 import static org.eclipse.leshan.LwM2mId.*;
-import static org.eclipse.leshan.client.request.ServerIdentity.SYSTEM;
+import static org.eclipse.leshan.client.servers.ServerIdentity.SYSTEM;
 
 import java.net.InetSocketAddress;
 import java.net.URI;
diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/bootstrap/BootstrapResource.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/bootstrap/BootstrapResource.java
index 27eb9d92..1469a8d0 100644
--- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/bootstrap/BootstrapResource.java
+++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/bootstrap/BootstrapResource.java
@@ -25,7 +25,8 @@ import org.eclipse.californium.core.server.resources.CoapExchange;
 import org.eclipse.californium.core.server.resources.Resource;
 import org.eclipse.leshan.client.bootstrap.BootstrapHandler;
 import org.eclipse.leshan.client.californium.LwM2mClientCoapResource;
-import org.eclipse.leshan.client.request.ServerIdentity;
+import org.eclipse.leshan.client.engine.RegistrationEngine;
+import org.eclipse.leshan.client.servers.ServerIdentity;
 import org.eclipse.leshan.core.request.BootstrapFinishRequest;
 import org.eclipse.leshan.core.response.BootstrapFinishResponse;
 import org.eclipse.leshan.core.response.SendableResponse;
@@ -35,15 +36,21 @@ import org.eclipse.leshan.core.response.SendableResponse;
  */
 public class BootstrapResource extends LwM2mClientCoapResource {
 
-    public BootstrapResource(BootstrapHandler bootstrapHandler) {
-        super("bs", bootstrapHandler);
+    protected BootstrapHandler bootstrapHandler;
+
+    public BootstrapResource(RegistrationEngine registrationEngine, BootstrapHandler bootstrapHandler) {
+        super("bs", registrationEngine);
+        this.bootstrapHandler = bootstrapHandler;
         this.setVisible(false);
     }
 
     @Override
     public void handlePOST(CoapExchange exchange) {
         // Handle bootstrap request
-        ServerIdentity identity = extractServerIdentity(exchange);
+        ServerIdentity identity = getServerOrRejectRequest(exchange);
+        if (identity == null)
+            return;
+
         // Acknowledge bootstrap finished request
         exchange.accept();
         final SendableResponse<BootstrapFinishResponse> sendableResponse = bootstrapHandler.finished(identity,
diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/object/ObjectResource.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/object/ObjectResource.java
index 6fa5a447..25a20877 100644
--- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/object/ObjectResource.java
+++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/object/ObjectResource.java
@@ -28,11 +28,11 @@ import org.eclipse.californium.core.coap.MediaTypeRegistry;
 import org.eclipse.californium.core.server.resources.CoapExchange;
 import org.eclipse.californium.core.server.resources.Resource;
 import org.eclipse.leshan.Link;
-import org.eclipse.leshan.client.bootstrap.BootstrapHandler;
 import org.eclipse.leshan.client.californium.LwM2mClientCoapResource;
-import org.eclipse.leshan.client.request.ServerIdentity;
+import org.eclipse.leshan.client.engine.RegistrationEngine;
 import org.eclipse.leshan.client.resource.LwM2mObjectEnabler;
 import org.eclipse.leshan.client.resource.listener.ObjectListener;
+import org.eclipse.leshan.client.servers.ServerIdentity;
 import org.eclipse.leshan.core.attributes.AttributeSet;
 import org.eclipse.leshan.core.model.LwM2mModel;
 import org.eclipse.leshan.core.model.StaticModel;
@@ -77,9 +77,9 @@ public class ObjectResource extends LwM2mClientCoapResource implements ObjectLis
     protected final LwM2mNodeEncoder encoder;
     protected final LwM2mNodeDecoder decoder;
 
-    public ObjectResource(LwM2mObjectEnabler nodeEnabler, BootstrapHandler bootstrapHandler, LwM2mNodeEncoder encoder,
-            LwM2mNodeDecoder decoder) {
-        super(Integer.toString(nodeEnabler.getId()), bootstrapHandler);
+    public ObjectResource(LwM2mObjectEnabler nodeEnabler, RegistrationEngine registrationEngine,
+            LwM2mNodeEncoder encoder, LwM2mNodeDecoder decoder) {
+        super(Integer.toString(nodeEnabler.getId()), registrationEngine);
         this.nodeEnabler = nodeEnabler;
         this.nodeEnabler.addListener(this);
         this.encoder = encoder;
@@ -89,7 +89,10 @@ public class ObjectResource extends LwM2mClientCoapResource implements ObjectLis
 
     @Override
     public void handleGET(CoapExchange exchange) {
-        ServerIdentity identity = extractServerIdentity(exchange);
+        ServerIdentity identity = getServerOrRejectRequest(exchange);
+        if (identity == null)
+            return;
+
         String URI = exchange.getRequestOptions().getUriPathString();
 
         // Manage Discover Request
@@ -162,7 +165,10 @@ public class ObjectResource extends LwM2mClientCoapResource implements ObjectLis
 
     @Override
     public void handlePUT(CoapExchange coapExchange) {
-        ServerIdentity identity = extractServerIdentity(coapExchange);
+        ServerIdentity identity = getServerOrRejectRequest(coapExchange);
+        if (identity == null)
+            return;
+
         String URI = coapExchange.getRequestOptions().getUriPathString();
 
         // get Observe Spec
@@ -230,7 +236,10 @@ public class ObjectResource extends LwM2mClientCoapResource implements ObjectLis
 
     @Override
     public void handlePOST(CoapExchange exchange) {
-        ServerIdentity identity = extractServerIdentity(exchange);
+        ServerIdentity identity = getServerOrRejectRequest(exchange);
+        if (identity == null)
+            return;
+
         String URI = exchange.getRequestOptions().getUriPathString();
 
         LwM2mPath path = new LwM2mPath(URI);
@@ -321,7 +330,9 @@ public class ObjectResource extends LwM2mClientCoapResource implements ObjectLis
     public void handleDELETE(CoapExchange coapExchange) {
         // Manage Delete Request
         String URI = coapExchange.getRequestOptions().getUriPathString();
-        ServerIdentity identity = extractServerIdentity(coapExchange);
+        ServerIdentity identity = getServerOrRejectRequest(coapExchange);
+        if (identity == null)
+            return;
 
         if (identity.isLwm2mBootstrapServer()) {
             BootstrapDeleteResponse response = nodeEnabler.delete(identity, new BootstrapDeleteRequest(URI));
diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/CaliforniumLwM2mRequestSender.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/CaliforniumLwM2mRequestSender.java
index 69d180b9..684e4954 100644
--- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/CaliforniumLwM2mRequestSender.java
+++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/CaliforniumLwM2mRequestSender.java
@@ -15,7 +15,6 @@
  *******************************************************************************/
 package org.eclipse.leshan.client.californium.request;
 
-import java.net.InetSocketAddress;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
@@ -25,6 +24,7 @@ import org.eclipse.californium.core.coap.Request;
 import org.eclipse.californium.core.coap.Response;
 import org.eclipse.leshan.client.californium.CaliforniumEndpointsManager;
 import org.eclipse.leshan.client.request.LwM2mRequestSender;
+import org.eclipse.leshan.client.servers.ServerIdentity;
 import org.eclipse.leshan.core.californium.AsyncRequestObserver;
 import org.eclipse.leshan.core.californium.SyncRequestObserver;
 import org.eclipse.leshan.core.request.UplinkRequest;
@@ -59,10 +59,10 @@ public class CaliforniumLwM2mRequestSender implements LwM2mRequestSender {
     }
 
     @Override
-    public <T extends LwM2mResponse> T send(InetSocketAddress serverAddress, boolean secure,
-            final UplinkRequest<T> request, long timeout) throws InterruptedException {
+    public <T extends LwM2mResponse> T send(ServerIdentity server, final UplinkRequest<T> request, long timeout)
+            throws InterruptedException {
         // Create the CoAP request from LwM2m request
-        CoapRequestBuilder coapClientRequestBuilder = new CoapRequestBuilder(serverAddress);
+        CoapRequestBuilder coapClientRequestBuilder = new CoapRequestBuilder(server.getIdentity());
         request.accept(coapClientRequestBuilder);
         Request coapRequest = coapClientRequestBuilder.getRequest();
 
@@ -79,18 +79,17 @@ public class CaliforniumLwM2mRequestSender implements LwM2mRequestSender {
         coapRequest.addMessageObserver(syncMessageObserver);
 
         // Send CoAP request asynchronously
-        endpointsManager.getEndpoint(null).sendRequest(coapRequest);
+        endpointsManager.getEndpoint(server).sendRequest(coapRequest);
 
         // Wait for response, then return it
         return syncMessageObserver.waitForResponse();
     }
 
     @Override
-    public <T extends LwM2mResponse> void send(InetSocketAddress serverAddress, boolean secure,
-            final UplinkRequest<T> request, long timeout, ResponseCallback<T> responseCallback,
-            ErrorCallback errorCallback) {
+    public <T extends LwM2mResponse> void send(ServerIdentity server, final UplinkRequest<T> request, long timeout,
+            ResponseCallback<T> responseCallback, ErrorCallback errorCallback) {
         // Create the CoAP request from LwM2m request
-        CoapRequestBuilder coapClientRequestBuilder = new CoapRequestBuilder(serverAddress);
+        CoapRequestBuilder coapClientRequestBuilder = new CoapRequestBuilder(server.getIdentity());
         request.accept(coapClientRequestBuilder);
         Request coapRequest = coapClientRequestBuilder.getRequest();
 
@@ -108,7 +107,7 @@ public class CaliforniumLwM2mRequestSender implements LwM2mRequestSender {
         coapRequest.addMessageObserver(obs);
 
         // Send CoAP request asynchronously
-        endpointsManager.getEndpoint(null).sendRequest(coapRequest);
+        endpointsManager.getEndpoint(server).sendRequest(coapRequest);
     }
 
     @Override
diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/CoapRequestBuilder.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/CoapRequestBuilder.java
index a605e182..4b0307a5 100644
--- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/CoapRequestBuilder.java
+++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/CoapRequestBuilder.java
@@ -15,17 +15,18 @@
  *******************************************************************************/
 package org.eclipse.leshan.client.californium.request;
 
-import java.net.InetSocketAddress;
 import java.util.HashMap;
 import java.util.Map.Entry;
 
 import org.eclipse.californium.core.coap.Request;
-import org.eclipse.californium.elements.AddressEndpointContext;
+import org.eclipse.californium.elements.EndpointContext;
 import org.eclipse.leshan.Link;
+import org.eclipse.leshan.core.californium.EndpointContextUtil;
 import org.eclipse.leshan.core.request.BindingMode;
 import org.eclipse.leshan.core.request.BootstrapRequest;
 import org.eclipse.leshan.core.request.ContentFormat;
 import org.eclipse.leshan.core.request.DeregisterRequest;
+import org.eclipse.leshan.core.request.Identity;
 import org.eclipse.leshan.core.request.RegisterRequest;
 import org.eclipse.leshan.core.request.UpdateRequest;
 import org.eclipse.leshan.core.request.UplinkRequest;
@@ -39,10 +40,10 @@ import org.eclipse.leshan.core.request.UplinkRequestVisitor;
 public class CoapRequestBuilder implements UplinkRequestVisitor {
 
     protected Request coapRequest;
-    protected final InetSocketAddress serverAddress;
+    protected final Identity server;
 
-    public CoapRequestBuilder(InetSocketAddress serverAddress) {
-        this.serverAddress = serverAddress;
+    public CoapRequestBuilder(Identity server) {
+        this.server = server;
     }
 
     @Override
@@ -128,6 +129,7 @@ public class CoapRequestBuilder implements UplinkRequestVisitor {
     }
 
     protected void buildRequestSettings() {
-        coapRequest.setDestinationContext(new AddressEndpointContext(serverAddress));
+        EndpointContext context = EndpointContextUtil.extractContext(server, true);
+        coapRequest.setDestinationContext(context);
     }
 }
diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/EndpointsManager.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/EndpointsManager.java
index 068b8242..b82924ee 100644
--- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/EndpointsManager.java
+++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/EndpointsManager.java
@@ -17,7 +17,7 @@ package org.eclipse.leshan.client;
 
 import java.util.Collection;
 
-import org.eclipse.leshan.client.servers.Server;
+import org.eclipse.leshan.client.servers.ServerIdentity;
 import org.eclipse.leshan.client.servers.ServerInfo;
 
 /**
@@ -29,18 +29,18 @@ public interface EndpointsManager {
     /**
      * Create the endpoint/connector/socket based on given {@link ServerInfo}.
      * 
-     * @return a {@link Server} object which could be used to {@link #forceReconnection(Server, boolean)} or
-     *         {@link #getMaxCommunicationPeriodFor(Server, long)}
+     * @return a {@link ServerIdentity} object which could be used to {@link #forceReconnection(ServerIdentity, boolean)} or
+     *         {@link #getMaxCommunicationPeriodFor(ServerIdentity, long)}
      */
-    Server createEndpoint(ServerInfo serverInfo);
+    ServerIdentity createEndpoint(ServerInfo serverInfo);
 
     /**
      * Create an endpoint/connector/socket for each given {@link ServerInfo}.
      * 
-     * @return a collection of {@link Server} object which could be used to {@link #forceReconnection(Server, boolean)}
-     *         or {@link #getMaxCommunicationPeriodFor(Server, long)}
+     * @return a collection of {@link ServerIdentity} object which could be used to {@link #forceReconnection(ServerIdentity, boolean)}
+     *         or {@link #getMaxCommunicationPeriodFor(ServerIdentity, long)}
      */
-    Collection<Server> createEndpoints(Collection<? extends ServerInfo> serverInfo);
+    Collection<ServerIdentity> createEndpoints(Collection<? extends ServerInfo> serverInfo);
 
     /**
      * Return the longest possible communication period based on given lifetime for the given server.
@@ -51,7 +51,7 @@ public interface EndpointsManager {
      * 
      * @return the longest possible communication period based on given lifetime for the given server.
      */
-    long getMaxCommunicationPeriodFor(Server server, long lifetimeInSeconds);
+    long getMaxCommunicationPeriodFor(ServerIdentity server, long lifetimeInSeconds);
 
     /**
      * Force a "reconnection" to the server. "reconnection" meaning changes depending of transport layer used to
@@ -61,7 +61,7 @@ public interface EndpointsManager {
      * 
      * @param resume True if we must try to resume the connection (e.g. Abbreviated Handshake for DTLS)
      */
-    void forceReconnection(Server server, boolean resume);
+    void forceReconnection(ServerIdentity server, boolean resume);
 
     /**
      * Starts the manager.
diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/LwM2mClient.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/LwM2mClient.java
index 64169fb3..1feb7480 100644
--- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/LwM2mClient.java
+++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/LwM2mClient.java
@@ -16,6 +16,7 @@
 package org.eclipse.leshan.client;
 
 import org.eclipse.leshan.client.resource.LwM2mObjectTree;
+import org.eclipse.leshan.client.servers.ServerIdentity;
 
 /**
  * A Lightweight M2M client.
@@ -42,10 +43,15 @@ public interface LwM2mClient {
     void destroy(boolean deregister);
 
     /**
-     * Trigger a registration update.
+     * Trigger a registration update to all registered Server.
      */
     void triggerRegistrationUpdate();
 
+    /**
+     * Trigger a registration update to the given server.
+     */
+    void triggerRegistrationUpdate(ServerIdentity server);
+
     /**
      * @return the {@link LwM2mObjectTree} containing all the object implemented by this client.
      */
diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/RegistrationUpdateHandler.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/RegistrationUpdateHandler.java
index 2ef48dfe..848c09c9 100644
--- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/RegistrationUpdateHandler.java
+++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/RegistrationUpdateHandler.java
@@ -21,6 +21,7 @@ import org.eclipse.leshan.client.engine.RegistrationEngine;
 import org.eclipse.leshan.client.resource.LwM2mObjectEnabler;
 import org.eclipse.leshan.client.resource.LwM2mObjectTree;
 import org.eclipse.leshan.client.resource.listener.ObjectsListener;
+import org.eclipse.leshan.client.servers.ServerIdentity;
 import org.eclipse.leshan.client.servers.ServersInfoExtractor;
 import org.eclipse.leshan.client.util.LinkFormatHelper;
 import org.eclipse.leshan.core.request.BindingMode;
@@ -82,8 +83,18 @@ public class RegistrationUpdateHandler {
                                 bindingMode = ServersInfoExtractor.getBindingMode(object, instanceId);
                             }
                         }
-                        engine.triggerRegistrationUpdate(
-                                new RegistrationUpdate(lifetime, null, bindingMode, null, null));
+
+                        if (bindingMode != null || lifetime != null) {
+                            Long serverId = null;
+                            serverId = ServersInfoExtractor.getServerId(object, instanceId);
+                            if (serverId != null) {
+                                ServerIdentity server = engine.getRegisteredServer(serverId);
+                                if (server != null)
+                                    engine.triggerRegistrationUpdate(server,
+                                            new RegistrationUpdate(lifetime, null, bindingMode, null, null));
+                            }
+                        }
+
                     }
             }
         });
diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/bootstrap/BootstrapHandler.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/bootstrap/BootstrapHandler.java
index dfc7dd22..eae4010c 100644
--- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/bootstrap/BootstrapHandler.java
+++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/bootstrap/BootstrapHandler.java
@@ -20,12 +20,10 @@ import java.util.Map;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
-import org.eclipse.leshan.client.request.ServerIdentity;
 import org.eclipse.leshan.client.resource.LwM2mObjectEnabler;
-import org.eclipse.leshan.client.servers.ServerInfo;
+import org.eclipse.leshan.client.servers.ServerIdentity;
 import org.eclipse.leshan.core.request.BootstrapDeleteRequest;
 import org.eclipse.leshan.core.request.BootstrapFinishRequest;
-import org.eclipse.leshan.core.request.Identity;
 import org.eclipse.leshan.core.response.BootstrapDeleteResponse;
 import org.eclipse.leshan.core.response.BootstrapFinishResponse;
 import org.eclipse.leshan.core.response.SendableResponse;
@@ -38,18 +36,17 @@ public class BootstrapHandler {
     private boolean bootstrapping = false;
     private CountDownLatch bootstrappingLatch = new CountDownLatch(1);
 
-    private ServerInfo bootstrapServerInfo;
     private final Map<Integer, LwM2mObjectEnabler> objects;
 
     public BootstrapHandler(Map<Integer, LwM2mObjectEnabler> objectEnablers) {
         objects = objectEnablers;
     }
 
-    public synchronized SendableResponse<BootstrapFinishResponse> finished(ServerIdentity identity,
+    public synchronized SendableResponse<BootstrapFinishResponse> finished(ServerIdentity server,
             BootstrapFinishRequest finishedRequest) {
         if (bootstrapping) {
             // only if the request is from the bootstrap server
-            if (!isBootstrapServer(identity)) {
+            if (!server.isLwm2mBootstrapServer()) {
                 return new SendableResponse<>(BootstrapFinishResponse.badRequest("not from a bootstrap server"));
             }
             // TODO delete bootstrap server (see 5.2.5.2 Bootstrap Delete)
@@ -67,16 +64,16 @@ public class BootstrapHandler {
         }
     }
 
-    public synchronized BootstrapDeleteResponse delete(ServerIdentity identity, BootstrapDeleteRequest deleteRequest) {
+    public synchronized BootstrapDeleteResponse delete(ServerIdentity server, BootstrapDeleteRequest deleteRequest) {
         if (bootstrapping) {
             // Only if the request is from the bootstrap server
-            if (!isBootstrapServer(identity)) {
+            if (!server.isLwm2mBootstrapServer()) {
                 return BootstrapDeleteResponse.badRequest("not from a bootstrap server");
             }
 
             // Delete all device management server
             for (LwM2mObjectEnabler enabler : objects.values()) {
-                enabler.delete(identity, new BootstrapDeleteRequest(enabler.getId()));
+                enabler.delete(server, new BootstrapDeleteRequest(enabler.getId()));
             }
 
             return BootstrapDeleteResponse.success();
@@ -85,9 +82,8 @@ public class BootstrapHandler {
         }
     }
 
-    public synchronized boolean tryToInitSession(ServerInfo bootstrapServerInfo) {
+    public synchronized boolean tryToInitSession() {
         if (!bootstrapping) {
-            this.bootstrapServerInfo = bootstrapServerInfo;
             bootstrappingLatch = new CountDownLatch(1);
             bootstrapping = true;
             return true;
@@ -104,20 +100,7 @@ public class BootstrapHandler {
     }
 
     public synchronized void closeSession() {
-        bootstrapServerInfo = null;
         bootstrappingLatch = null;
         bootstrapping = false;
     }
-
-    /**
-     * @return <code>true</code> if the given request sender identity is the bootstrap server (same IP address)
-     */
-    public synchronized boolean isBootstrapServer(Identity identity) {
-        if (bootstrapServerInfo == null) {
-            return false;
-        }
-
-        return bootstrapServerInfo.getAddress().getAddress() != null
-                && bootstrapServerInfo.getAddress().getAddress().equals(identity.getPeerAddress().getAddress());
-    }
 }
diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngine.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngine.java
index 584a5231..0d95eb02 100644
--- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngine.java
+++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngine.java
@@ -15,15 +15,18 @@
  *******************************************************************************/
 package org.eclipse.leshan.client.engine;
 
-import java.util.Collection;
-import java.util.Iterator;
+import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.TreeMap;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
 
 import org.eclipse.leshan.LwM2m;
 import org.eclipse.leshan.ResponseCode;
@@ -35,13 +38,13 @@ import org.eclipse.leshan.client.request.LwM2mRequestSender;
 import org.eclipse.leshan.client.resource.LwM2mObjectEnabler;
 import org.eclipse.leshan.client.resource.LwM2mObjectTree;
 import org.eclipse.leshan.client.servers.DmServerInfo;
-import org.eclipse.leshan.client.servers.Server;
+import org.eclipse.leshan.client.servers.ServerIdentity;
 import org.eclipse.leshan.client.servers.ServerInfo;
-import org.eclipse.leshan.client.servers.ServersInfo;
 import org.eclipse.leshan.client.servers.ServersInfoExtractor;
 import org.eclipse.leshan.client.util.LinkFormatHelper;
 import org.eclipse.leshan.core.request.BootstrapRequest;
 import org.eclipse.leshan.core.request.DeregisterRequest;
+import org.eclipse.leshan.core.request.Identity;
 import org.eclipse.leshan.core.request.RegisterRequest;
 import org.eclipse.leshan.core.request.UpdateRequest;
 import org.eclipse.leshan.core.request.exception.SendFailedException;
@@ -69,6 +72,7 @@ public class DefaultRegistrationEngine implements RegistrationEngine {
     private static final Logger LOG = LoggerFactory.getLogger(DefaultRegistrationEngine.class);
 
     private static final long NOW = 0;
+    private static final ServerIdentity ALL = new ServerIdentity(null, null);
 
     // Timeout for bootstrap/register/update request
     private final long requestTimeoutInMs;
@@ -93,7 +97,9 @@ public class DefaultRegistrationEngine implements RegistrationEngine {
     private final String endpoint;
     private final Map<String, String> additionalAttributes;
     private final Map<Integer /* objectId */, LwM2mObjectEnabler> objectEnablers;
-    private final Map<String /* registrationId */, Server> registeredServers;
+    private final Map<String /* registrationId */, ServerIdentity> registeredServers;
+    private final List<ServerIdentity> registeringServers;
+    private final AtomicReference<ServerIdentity> currentBoostrapServer;
 
     // helpers
     private final LwM2mRequestSender sender;
@@ -122,6 +128,8 @@ public class DefaultRegistrationEngine implements RegistrationEngine {
         this.observer = observer;
         this.additionalAttributes = additionalAttributes;
         this.registeredServers = new ConcurrentHashMap<>();
+        this.registeringServers = new CopyOnWriteArrayList<>();
+        this.currentBoostrapServer = new AtomicReference<>();
         this.requestTimeoutInMs = requestTimeoutInMs;
         this.deregistrationTimeoutInMs = deregistrationTimeoutInMs;
         this.bootstrapSessionTimeoutInSec = bootstrapSessionTimeoutInSec;
@@ -151,31 +159,28 @@ public class DefaultRegistrationEngine implements RegistrationEngine {
         synchronized (this) {
             started = true;
             // Try factory bootstrap
-            Collection<Server> dmServers = factoryBootstrap();
+            // TODO support multi server
+            ServerIdentity dmServer = factoryBootstrap();
 
-            if (dmServers == null || dmServers.isEmpty()) {
+            if (dmServer == null) {
                 // If it failed try client initiated bootstrap
                 if (!scheduleClientInitiatedBootstrap(NOW))
                     throw new IllegalStateException("Unable to start client : No valid server available!");
             } else {
-                // Try to register to dm servers.
-                // TODO we currently support only one dm server.
-                Server dmServer = dmServers.iterator().next();
                 registerFuture = schedExecutor.submit(new RegistrationTask(dmServer));
             }
         }
     }
 
-    public Collection<Server> factoryBootstrap() {
-        ServersInfo serversInfo = ServersInfoExtractor.getInfo(objectEnablers);
-        if (!serversInfo.deviceManagements.isEmpty()) {
-            Collection<Server> servers = endpointsManager.createEndpoints(serversInfo.deviceManagements.values());
-            return servers;
+    private ServerIdentity factoryBootstrap() {
+        ServerInfo serverInfo = selectServer(ServersInfoExtractor.getInfo(objectEnablers).deviceManagements);
+        if (serverInfo != null) {
+            return endpointsManager.createEndpoint(serverInfo);
         }
         return null;
     }
 
-    private Collection<Server> clientInitiatedBootstrap() throws InterruptedException {
+    private ServerIdentity clientInitiatedBootstrap() throws InterruptedException {
         ServerInfo bootstrapServerInfo = ServersInfoExtractor.getBootstrapServerInfo(objectEnablers);
 
         if (bootstrapServerInfo == null) {
@@ -183,14 +188,17 @@ public class DefaultRegistrationEngine implements RegistrationEngine {
             return null;
         }
 
-        if (bootstrapHandler.tryToInitSession(bootstrapServerInfo)) {
+        if (bootstrapHandler.tryToInitSession()) {
             LOG.info("Trying to start bootstrap session to {} ...", bootstrapServerInfo.getFullUri());
 
             // Clear all registered server, cancel all current task and recreate all endpoints
             registeredServers.clear();
             cancelRegistrationTask();
             cancelUpdateTask(true);
-            Server bootstrapServer = endpointsManager.createEndpoint(bootstrapServerInfo);
+            ServerIdentity bootstrapServer = endpointsManager.createEndpoint(bootstrapServerInfo);
+            if (bootstrapServer != null) {
+                currentBoostrapServer.set(bootstrapServer);
+            }
 
             // Send bootstrap request
             BootstrapRequest request = null;
@@ -199,8 +207,7 @@ public class DefaultRegistrationEngine implements RegistrationEngine {
                 if (observer != null) {
                     observer.onBootstrapStarted(bootstrapServer, request);
                 }
-                BootstrapResponse response = sender.send(bootstrapServerInfo.getAddress(),
-                        bootstrapServerInfo.isSecure(), request, requestTimeoutInMs);
+                BootstrapResponse response = sender.send(bootstrapServer, request, requestTimeoutInMs);
                 if (response == null) {
                     LOG.info("Unable to start bootstrap session: Timeout.");
                     if (observer != null) {
@@ -218,16 +225,17 @@ public class DefaultRegistrationEngine implements RegistrationEngine {
                         }
                         return null;
                     } else {
-                        LOG.info("Bootstrap finished {}.", bootstrapServerInfo);
-                        ServersInfo serverInfos = ServersInfoExtractor.getInfo(objectEnablers);
-                        Collection<Server> dmServers = null;
-                        if (!serverInfos.deviceManagements.isEmpty()) {
-                            dmServers = endpointsManager.createEndpoints(serverInfos.deviceManagements.values());
+                        LOG.info("Bootstrap finished {}.", bootstrapServer.getUri());
+                        ServerInfo serverInfo = selectServer(
+                                ServersInfoExtractor.getInfo(objectEnablers).deviceManagements);
+                        ServerIdentity dmServer = null;
+                        if (serverInfo != null) {
+                            dmServer = endpointsManager.createEndpoint(serverInfo);
                         }
                         if (observer != null) {
                             observer.onBootstrapSuccess(bootstrapServer, request);
                         }
-                        return dmServers;
+                        return dmServer;
                     }
                 } else {
                     LOG.info("Bootstrap failed: {} {}.", response.getCode(), response.getErrorMessage());
@@ -244,6 +252,7 @@ public class DefaultRegistrationEngine implements RegistrationEngine {
                 }
                 return null;
             } finally {
+                currentBoostrapServer.set(null);
                 bootstrapHandler.closeSession();
             }
         } else {
@@ -252,7 +261,7 @@ public class DefaultRegistrationEngine implements RegistrationEngine {
         }
     }
 
-    private boolean registerWithRetry(Server server) throws InterruptedException {
+    private boolean registerWithRetry(ServerIdentity server) throws InterruptedException {
         Status registerStatus = register(server);
         if (registerStatus == Status.TIMEOUT) {
             // if register timeout maybe server lost the session,
@@ -263,7 +272,7 @@ public class DefaultRegistrationEngine implements RegistrationEngine {
         return registerStatus == Status.SUCCESS;
     }
 
-    private Status register(Server server) throws InterruptedException {
+    private Status register(ServerIdentity server) throws InterruptedException {
         DmServerInfo dmInfo = ServersInfoExtractor.getDMServerInfo(objectEnablers, server.getId());
 
         if (dmInfo == null) {
@@ -280,8 +289,8 @@ public class DefaultRegistrationEngine implements RegistrationEngine {
             if (observer != null) {
                 observer.onRegistrationStarted(server, request);
             }
-            RegisterResponse response = sender.send(dmInfo.getAddress(), dmInfo.isSecure(), request,
-                    requestTimeoutInMs);
+            registeringServers.add(server);
+            RegisterResponse response = sender.send(server, request, requestTimeoutInMs);
 
             if (response == null) {
                 LOG.info("Registration failed: Timeout.");
@@ -317,19 +326,15 @@ public class DefaultRegistrationEngine implements RegistrationEngine {
                 observer.onRegistrationFailure(server, request, null, null, e);
             }
             return Status.FAILURE;
+        } finally {
+            registeringServers.remove(server);
         }
     }
 
-    private boolean deregister(Server server, String registrationID) throws InterruptedException {
+    private boolean deregister(ServerIdentity server, String registrationID) throws InterruptedException {
         if (registrationID == null)
             return true;
 
-        DmServerInfo dmInfo = ServersInfoExtractor.getDMServerInfo(objectEnablers, server.getId());
-        if (dmInfo == null) {
-            LOG.info("Trying to deregister device but there is no LWM2M server config.");
-            return false;
-        }
-
         // Send deregister request
         LOG.info("Trying to deregister to {} ...", server.getUri());
         DeregisterRequest request = null;
@@ -338,8 +343,7 @@ public class DefaultRegistrationEngine implements RegistrationEngine {
             if (observer != null) {
                 observer.onDeregistrationStarted(server, request);
             }
-            DeregisterResponse response = sender.send(server.getIdentity().getPeerAddress(),
-                    server.getIdentity().isSecure(), request, deregistrationTimeoutInMs);
+            DeregisterResponse response = sender.send(server, request, deregistrationTimeoutInMs);
             if (response == null) {
                 registrationID = null;
                 LOG.info("Deregistration failed: Timeout.");
@@ -378,7 +382,7 @@ public class DefaultRegistrationEngine implements RegistrationEngine {
         }
     }
 
-    private boolean updateWithRetry(Server server, String registrationId, RegistrationUpdate registrationUpdate)
+    private boolean updateWithRetry(ServerIdentity server, String registrationId, RegistrationUpdate registrationUpdate)
             throws InterruptedException {
 
         Status updateStatus = update(server, registrationId, registrationUpdate);
@@ -391,7 +395,7 @@ public class DefaultRegistrationEngine implements RegistrationEngine {
         return updateStatus == Status.SUCCESS;
     }
 
-    private Status update(Server server, String registrationID, RegistrationUpdate registrationUpdate)
+    private Status update(ServerIdentity server, String registrationID, RegistrationUpdate registrationUpdate)
             throws InterruptedException {
         DmServerInfo dmInfo = ServersInfoExtractor.getDMServerInfo(objectEnablers, server.getId());
         if (dmInfo == null) {
@@ -412,7 +416,7 @@ public class DefaultRegistrationEngine implements RegistrationEngine {
             if (reconnectOnUpdate) {
                 endpointsManager.forceReconnection(server, resumeOnConnect);
             }
-            UpdateResponse response = sender.send(dmInfo.getAddress(), dmInfo.isSecure(), request, requestTimeoutInMs);
+            UpdateResponse response = sender.send(server, request, requestTimeoutInMs);
             if (response == null) {
                 registrationID = null;
                 LOG.info("Registration update failed: Timeout.");
@@ -446,7 +450,7 @@ public class DefaultRegistrationEngine implements RegistrationEngine {
         }
     }
 
-    private long calculateNextUpdate(Server server, long lifetimeInSeconds) {
+    private long calculateNextUpdate(ServerIdentity server, long lifetimeInSeconds) {
         long maxComminucationPeriod = endpointsManager.getMaxCommunicationPeriodFor(server, lifetimeInSeconds * 1000);
         if (communicationPeriodInMs != null) {
             return Math.min(communicationPeriodInMs, maxComminucationPeriod);
@@ -484,15 +488,14 @@ public class DefaultRegistrationEngine implements RegistrationEngine {
         public void run() {
             synchronized (taskLock) {
                 try {
-                    Collection<Server> dmServers = clientInitiatedBootstrap();
-                    if (dmServers == null || dmServers.isEmpty()) {
+                    ServerIdentity dmServer = clientInitiatedBootstrap();
+                    if (dmServer == null) {
                         // clientInitiatatedBootstrapTask is considered as finished.
                         // see https://github.com/eclipse/leshan/issues/701
                         bootstrapFuture = null;
                         // last thing to do reschedule a new bootstrap.
                         scheduleClientInitiatedBootstrap(retryWaitingTimeInMs);
                     } else {
-                        Server dmServer = dmServers.iterator().next();
                         if (!registerWithRetry(dmServer))
                             scheduleRegistrationTask(dmServer, retryWaitingTimeInMs);
                     }
@@ -505,7 +508,7 @@ public class DefaultRegistrationEngine implements RegistrationEngine {
         }
     }
 
-    private synchronized void scheduleRegistrationTask(Server dmServer, long timeInMs) {
+    private synchronized void scheduleRegistrationTask(ServerIdentity dmServer, long timeInMs) {
         if (!started)
             return;
 
@@ -519,9 +522,9 @@ public class DefaultRegistrationEngine implements RegistrationEngine {
     }
 
     private class RegistrationTask implements Runnable {
-        private final Server server;
+        private final ServerIdentity server;
 
-        public RegistrationTask(Server server) {
+        public RegistrationTask(ServerIdentity server) {
             this.server = server;
         }
 
@@ -544,7 +547,7 @@ public class DefaultRegistrationEngine implements RegistrationEngine {
 
     }
 
-    private synchronized void scheduleUpdate(Server server, String registrationId,
+    private synchronized void scheduleUpdate(ServerIdentity server, String registrationId,
             RegistrationUpdate registrationUpdate, long timeInMs) {
         if (!started)
             return;
@@ -560,11 +563,12 @@ public class DefaultRegistrationEngine implements RegistrationEngine {
     }
 
     private class UpdateRegistrationTask implements Runnable {
-        private final Server server;
+        private final ServerIdentity server;
         private final String registrationId;
         private final RegistrationUpdate registrationUpdate;
 
-        public UpdateRegistrationTask(Server server, String registrationId, RegistrationUpdate registrationUpdate) {
+        public UpdateRegistrationTask(ServerIdentity server, String registrationId,
+                RegistrationUpdate registrationUpdate) {
             this.server = server;
             this.registrationId = registrationId;
             this.registrationUpdate = registrationUpdate;
@@ -621,10 +625,10 @@ public class DefaultRegistrationEngine implements RegistrationEngine {
         }
         try {
             if (deregister) {
-                // TODO we currently support only one dm server.
                 if (!registeredServers.isEmpty()) {
-                    Entry<String, Server> currentServer = registeredServers.entrySet().iterator().next();
-                    deregister(currentServer.getValue(), currentServer.getKey());
+                    for (Entry<String, ServerIdentity> registeredServer : registeredServers.entrySet()) {
+                        deregister(registeredServer.getValue(), registeredServer.getKey());
+                    }
                 }
             }
         } catch (InterruptedException e) {
@@ -650,10 +654,10 @@ public class DefaultRegistrationEngine implements RegistrationEngine {
                 cancelBootstrapTask();
             }
             if (wasStarted && deregister) {
-                // TODO we currently support only one dm server.
                 if (!registeredServers.isEmpty()) {
-                    Entry<String, Server> currentServer = registeredServers.entrySet().iterator().next();
-                    deregister(currentServer.getValue(), currentServer.getKey());
+                    for (Entry<String, ServerIdentity> registeredServer : registeredServers.entrySet()) {
+                        deregister(registeredServer.getValue(), registeredServer.getKey());
+                    }
                 }
             }
         } catch (InterruptedException e) {
@@ -663,19 +667,28 @@ public class DefaultRegistrationEngine implements RegistrationEngine {
     private class QueueUpdateTask implements Runnable {
 
         private RegistrationUpdate registrationUpdate;
+        private ServerIdentity server;
 
-        public QueueUpdateTask(RegistrationUpdate registrationUpdate) {
+        public QueueUpdateTask(ServerIdentity server, RegistrationUpdate registrationUpdate) {
             this.registrationUpdate = registrationUpdate;
+            this.server = server;
         }
 
         @Override
         public void run() {
             synchronized (taskLock) {
                 cancelUpdateTask(true);
-                // TODO we currently support only one dm server.
-                Entry<String, Server> currentServer = registeredServers.entrySet().iterator().next();
-                if (currentServer != null) {
-                    scheduleUpdate(currentServer.getValue(), currentServer.getKey(), registrationUpdate, NOW);
+                if (ALL.equals(server)) {
+                    // TODO support multi server
+                    Entry<String, ServerIdentity> currentServer = registeredServers.entrySet().iterator().next();
+                    if (currentServer != null) {
+                        scheduleUpdate(currentServer.getValue(), currentServer.getKey(), registrationUpdate, NOW);
+                    }
+                } else {
+                    String registrationId = getRegistrationId(server);
+                    if (registrationId != null) {
+                        scheduleUpdate(server, registrationId, registrationUpdate, NOW);
+                    }
                 }
             }
         }
@@ -694,7 +707,29 @@ public class DefaultRegistrationEngine implements RegistrationEngine {
                 if (registeredServers.isEmpty()) {
                     LOG.info("No server registered!");
                 } else {
-                    schedExecutor.submit(new QueueUpdateTask(registrationUpdate));
+                    schedExecutor.submit(new QueueUpdateTask(ALL, registrationUpdate));
+                }
+            }
+        }
+    }
+
+    @Override
+    public void triggerRegistrationUpdate(ServerIdentity server) {
+        triggerRegistrationUpdate(server, new RegistrationUpdate());
+    }
+
+    @Override
+    public void triggerRegistrationUpdate(ServerIdentity server, RegistrationUpdate registrationUpdate) {
+        if (server == null)
+            return;
+
+        synchronized (this) {
+            if (started) {
+                LOG.info("Triggering registration update...");
+                if (registeredServers.isEmpty()) {
+                    LOG.info("No server registered!");
+                } else {
+                    schedExecutor.submit(new QueueUpdateTask(server, registrationUpdate));
                 }
             }
         }
@@ -714,15 +749,76 @@ public class DefaultRegistrationEngine implements RegistrationEngine {
         LOG.info("{} : {}", message, e.getMessage());
     }
 
+    @Override
+    public String getRegistrationId(ServerIdentity server) {
+        if (server == null)
+            return null;
+        for (Entry<String, ServerIdentity> entry : registeredServers.entrySet()) {
+            if (server.equals(entry.getValue())) {
+                return entry.getKey();
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Map<String, ServerIdentity> getRegisteredServers() {
+        return Collections.unmodifiableMap(registeredServers);
+    }
+
+    @Override
+    public ServerIdentity getRegisteredServer(long serverId) {
+        for (ServerIdentity server : registeringServers) {
+            if (server != null && server.getId() == serverId) {
+                return server;
+            }
+        }
+        for (Entry<String, ServerIdentity> entry : registeredServers.entrySet()) {
+            ServerIdentity server = entry.getValue();
+            if (server != null && server.getId() == serverId) {
+                return server;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public ServerIdentity getServer(Identity identity) {
+        if (identity == null)
+            return null;
+        ServerIdentity bootstrapServer = currentBoostrapServer.get();
+        if (bootstrapServer != null && identity.equals(bootstrapServer.getIdentity())) {
+            return bootstrapServer;
+        } else {
+            for (ServerIdentity server : registeringServers) {
+                if (identity.equals(server.getIdentity())) {
+                    return server;
+                }
+            }
+            for (ServerIdentity server : registeredServers.values()) {
+                if (identity.equals(server.getIdentity())) {
+                    return server;
+                }
+            }
+        }
+        return null;
+    }
+
     /**
-     * @return the current registration Id or <code>null</code> if the client is not registered.
+     * This class support to be connected to only one LWM2M server. This methods select the server to be used. Default
+     * implementation select the first one.
      */
-    @Override
-    public String getRegistrationId() {
-        // TODO we currently support only one dm server.
-        Iterator<String> it = registeredServers.keySet().iterator();
-        if (it.hasNext()) {
-            return it.next();
+    protected DmServerInfo selectServer(Map<Long, DmServerInfo> servers) {
+        if (servers != null && !servers.isEmpty()) {
+            if (servers.size() > 1) {
+                LOG.warn(
+                        "DefaultRegistrationEngine support only connection to 1 LWM2M server, first server will be used from the server list of {}",
+                        servers.size());
+                TreeMap<Long, DmServerInfo> sortedServers = new TreeMap<>(servers);
+                return sortedServers.values().iterator().next();
+            } else {
+                return servers.values().iterator().next();
+            }
         }
         return null;
     }
diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/RegistrationEngine.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/RegistrationEngine.java
index 7d9358d1..008e6523 100644
--- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/RegistrationEngine.java
+++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/RegistrationEngine.java
@@ -15,7 +15,11 @@
  *******************************************************************************/
 package org.eclipse.leshan.client.engine;
 
+import java.util.Map;
+
 import org.eclipse.leshan.client.RegistrationUpdate;
+import org.eclipse.leshan.client.servers.ServerIdentity;
+import org.eclipse.leshan.core.request.Identity;
 
 /**
  * Manage the registration life-cycle:
@@ -34,20 +38,43 @@ public interface RegistrationEngine {
      */
     void triggerRegistrationUpdate();
 
+    /**
+     * Trigger an "empty" registration update to the given servers.
+     */
+    void triggerRegistrationUpdate(ServerIdentity server);
+
     /**
      * Trigger the given registration update to all servers.
      */
     void triggerRegistrationUpdate(RegistrationUpdate registrationUpdate);
 
     /**
-     * Returns the current registration Id.
-     * <p>
-     * Client should have 1 registration Id by connected server, so this method only make sense as current
-     * implementation supports only one LWM2M server.
+     * Trigger the given registration update to the given servers.
+     */
+    void triggerRegistrationUpdate(ServerIdentity server, RegistrationUpdate registrationUpdate);
+
+    /**
+     * Returns the current registration Id for this server.
      * 
-     * @return the client registration Id or <code>null</code> if the client is not registered
+     * @return the client registration Id or <code>null</code> if the client is not registered to this server.
      */
-    String getRegistrationId();
+    String getRegistrationId(ServerIdentity server);
+
+    /**
+     * @return All the registered Server indexed by the corresponding registration id;
+     */
+    Map<String, ServerIdentity> getRegisteredServers();
+
+    /**
+     * @return The registered server identified by the given id or null if there is no corresponding server registered.
+     */
+    ServerIdentity getRegisteredServer(long serverId);
+
+    /**
+     * @return The registered LWM2M Server or Bootstrap server for the given identity or null if there is no
+     *         corresponding server registered.
+     */
+    ServerIdentity getServer(Identity identity);
 
     /**
      * @return the endpoint name of the LWM2M client
@@ -72,4 +99,5 @@ public interface RegistrationEngine {
      * @param deregister True if client should deregister itself before to be destroyed.
      */
     void destroy(boolean deregister);
-}
+
+}
\ No newline at end of file
diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/object/Device.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/object/Device.java
index a8a691ab..60666100 100644
--- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/object/Device.java
+++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/object/Device.java
@@ -24,9 +24,9 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.TimeZone;
 
-import org.eclipse.leshan.client.request.ServerIdentity;
 import org.eclipse.leshan.client.resource.BaseInstanceEnabler;
 import org.eclipse.leshan.client.resource.LwM2mInstanceEnabler;
+import org.eclipse.leshan.client.servers.ServerIdentity;
 import org.eclipse.leshan.core.model.ObjectModel;
 import org.eclipse.leshan.core.model.ResourceModel.Type;
 import org.eclipse.leshan.core.node.LwM2mResource;
diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/object/Security.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/object/Security.java
index 174c6487..48aff51b 100644
--- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/object/Security.java
+++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/object/Security.java
@@ -21,9 +21,9 @@ import java.util.Arrays;
 import java.util.List;
 
 import org.eclipse.leshan.SecurityMode;
-import org.eclipse.leshan.client.request.ServerIdentity;
 import org.eclipse.leshan.client.resource.BaseInstanceEnabler;
 import org.eclipse.leshan.client.resource.LwM2mInstanceEnabler;
+import org.eclipse.leshan.client.servers.ServerIdentity;
 import org.eclipse.leshan.core.model.ObjectModel;
 import org.eclipse.leshan.core.model.ResourceModel.Type;
 import org.eclipse.leshan.core.node.LwM2mResource;
diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/object/Server.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/object/Server.java
index a2d23ccd..d184b732 100644
--- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/object/Server.java
+++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/object/Server.java
@@ -21,9 +21,9 @@ import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
 
-import org.eclipse.leshan.client.request.ServerIdentity;
 import org.eclipse.leshan.client.resource.BaseInstanceEnabler;
 import org.eclipse.leshan.client.resource.LwM2mInstanceEnabler;
+import org.eclipse.leshan.client.servers.ServerIdentity;
 import org.eclipse.leshan.core.model.ObjectModel;
 import org.eclipse.leshan.core.model.ResourceModel.Type;
 import org.eclipse.leshan.core.node.LwM2mResource;
@@ -173,8 +173,7 @@ public class Server extends BaseInstanceEnabler {
     public ExecuteResponse execute(ServerIdentity identity, int resourceid, String params) {
         LOG.debug("Execute on Server resource /{}/{}/{}", getModel().id, getId(), resourceid);
         if (resourceid == 8) {
-            // TODO we currently support only one dm server.
-            getLwM2mClient().triggerRegistrationUpdate();
+            getLwM2mClient().triggerRegistrationUpdate(identity);
             return ExecuteResponse.success();
         } else {
             return super.execute(identity, resourceid, params);
diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/observer/LwM2mClientObserver.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/observer/LwM2mClientObserver.java
index 0b129529..406fbbc7 100644
--- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/observer/LwM2mClientObserver.java
+++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/observer/LwM2mClientObserver.java
@@ -16,7 +16,7 @@
 package org.eclipse.leshan.client.observer;
 
 import org.eclipse.leshan.ResponseCode;
-import org.eclipse.leshan.client.servers.Server;
+import org.eclipse.leshan.client.servers.ServerIdentity;
 import org.eclipse.leshan.core.request.BootstrapRequest;
 import org.eclipse.leshan.core.request.DeregisterRequest;
 import org.eclipse.leshan.core.request.RegisterRequest;
@@ -28,45 +28,45 @@ import org.eclipse.leshan.core.request.UpdateRequest;
 public interface LwM2mClientObserver {
     // ============== Bootstrap =================
 
-    void onBootstrapStarted(Server bsserver, BootstrapRequest request);
+    void onBootstrapStarted(ServerIdentity bsserver, BootstrapRequest request);
 
-    void onBootstrapSuccess(Server bsserver, BootstrapRequest request);
+    void onBootstrapSuccess(ServerIdentity bsserver, BootstrapRequest request);
 
-    void onBootstrapFailure(Server bsserver, BootstrapRequest request, ResponseCode responseCode, String errorMessage,
+    void onBootstrapFailure(ServerIdentity bsserver, BootstrapRequest request, ResponseCode responseCode, String errorMessage,
             Exception cause);
 
-    void onBootstrapTimeout(Server bsserver, BootstrapRequest request);
+    void onBootstrapTimeout(ServerIdentity bsserver, BootstrapRequest request);
 
     // ============== Registration =================
 
-    void onRegistrationStarted(Server server, RegisterRequest request);
+    void onRegistrationStarted(ServerIdentity server, RegisterRequest request);
 
-    void onRegistrationSuccess(Server server, RegisterRequest request, String registrationID);
+    void onRegistrationSuccess(ServerIdentity server, RegisterRequest request, String registrationID);
 
-    void onRegistrationFailure(Server server, RegisterRequest request, ResponseCode responseCode, String errorMessage,
+    void onRegistrationFailure(ServerIdentity server, RegisterRequest request, ResponseCode responseCode, String errorMessage,
             Exception cause);
 
-    void onRegistrationTimeout(Server server, RegisterRequest request);
+    void onRegistrationTimeout(ServerIdentity server, RegisterRequest request);
 
     // ============== Registration Update =================
 
-    void onUpdateStarted(Server server, UpdateRequest request);
+    void onUpdateStarted(ServerIdentity server, UpdateRequest request);
 
-    void onUpdateSuccess(Server server, UpdateRequest request);
+    void onUpdateSuccess(ServerIdentity server, UpdateRequest request);
 
-    void onUpdateFailure(Server server, UpdateRequest request, ResponseCode responseCode, String errorMessage,
+    void onUpdateFailure(ServerIdentity server, UpdateRequest request, ResponseCode responseCode, String errorMessage,
             Exception cause);
 
-    void onUpdateTimeout(Server server, UpdateRequest request);
+    void onUpdateTimeout(ServerIdentity server, UpdateRequest request);
 
     // ============== Deregistration Update =================
 
-    void onDeregistrationStarted(Server server, DeregisterRequest request);
+    void onDeregistrationStarted(ServerIdentity server, DeregisterRequest request);
 
-    void onDeregistrationSuccess(Server server, DeregisterRequest request);
+    void onDeregistrationSuccess(ServerIdentity server, DeregisterRequest request);
 
-    void onDeregistrationFailure(Server server, DeregisterRequest request, ResponseCode responseCode,
+    void onDeregistrationFailure(ServerIdentity server, DeregisterRequest request, ResponseCode responseCode,
             String errorMessage, Exception cause);
 
-    void onDeregistrationTimeout(Server server, DeregisterRequest request);
+    void onDeregistrationTimeout(ServerIdentity server, DeregisterRequest request);
 }
diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/observer/LwM2mClientObserverAdapter.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/observer/LwM2mClientObserverAdapter.java
index 4c9b0134..816010b0 100644
--- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/observer/LwM2mClientObserverAdapter.java
+++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/observer/LwM2mClientObserverAdapter.java
@@ -16,7 +16,7 @@
 package org.eclipse.leshan.client.observer;
 
 import org.eclipse.leshan.ResponseCode;
-import org.eclipse.leshan.client.servers.Server;
+import org.eclipse.leshan.client.servers.ServerIdentity;
 import org.eclipse.leshan.core.request.BootstrapRequest;
 import org.eclipse.leshan.core.request.DeregisterRequest;
 import org.eclipse.leshan.core.request.RegisterRequest;
@@ -29,70 +29,70 @@ import org.eclipse.leshan.core.request.UpdateRequest;
 public class LwM2mClientObserverAdapter implements LwM2mClientObserver {
 
     @Override
-    public void onBootstrapStarted(Server bsserver, BootstrapRequest request) {
+    public void onBootstrapStarted(ServerIdentity bsserver, BootstrapRequest request) {
     }
 
     @Override
-    public void onBootstrapSuccess(Server bsserver, BootstrapRequest request) {
+    public void onBootstrapSuccess(ServerIdentity bsserver, BootstrapRequest request) {
     }
 
     @Override
-    public void onBootstrapFailure(Server bsserver, BootstrapRequest request, ResponseCode responseCode,
+    public void onBootstrapFailure(ServerIdentity bsserver, BootstrapRequest request, ResponseCode responseCode,
             String errorMessage, Exception cause) {
     }
 
     @Override
-    public void onBootstrapTimeout(Server bsserver, BootstrapRequest request) {
+    public void onBootstrapTimeout(ServerIdentity bsserver, BootstrapRequest request) {
     }
 
     @Override
-    public void onRegistrationStarted(Server server, RegisterRequest request) {
+    public void onRegistrationStarted(ServerIdentity server, RegisterRequest request) {
     }
 
     @Override
-    public void onRegistrationSuccess(Server server, RegisterRequest request, String registrationID) {
+    public void onRegistrationSuccess(ServerIdentity server, RegisterRequest request, String registrationID) {
     }
 
     @Override
-    public void onRegistrationFailure(Server server, RegisterRequest request, ResponseCode responseCode,
+    public void onRegistrationFailure(ServerIdentity server, RegisterRequest request, ResponseCode responseCode,
             String errorMessage, Exception cause) {
     }
 
     @Override
-    public void onRegistrationTimeout(Server server, RegisterRequest request) {
+    public void onRegistrationTimeout(ServerIdentity server, RegisterRequest request) {
     }
 
     @Override
-    public void onUpdateStarted(Server server, UpdateRequest request) {
+    public void onUpdateStarted(ServerIdentity server, UpdateRequest request) {
     }
 
     @Override
-    public void onUpdateSuccess(Server server, UpdateRequest request) {
+    public void onUpdateSuccess(ServerIdentity server, UpdateRequest request) {
     }
 
     @Override
-    public void onUpdateFailure(Server server, UpdateRequest request, ResponseCode responseCode, String errorMessage,
+    public void onUpdateFailure(ServerIdentity server, UpdateRequest request, ResponseCode responseCode, String errorMessage,
             Exception cause) {
     }
 
     @Override
-    public void onUpdateTimeout(Server server, UpdateRequest request) {
+    public void onUpdateTimeout(ServerIdentity server, UpdateRequest request) {
     }
 
     @Override
-    public void onDeregistrationStarted(Server server, DeregisterRequest request) {
+    public void onDeregistrationStarted(ServerIdentity server, DeregisterRequest request) {
     }
 
     @Override
-    public void onDeregistrationSuccess(Server server, DeregisterRequest request) {
+    public void onDeregistrationSuccess(ServerIdentity server, DeregisterRequest request) {
     }
 
     @Override
-    public void onDeregistrationFailure(Server server, DeregisterRequest request, ResponseCode responseCode,
+    public void onDeregistrationFailure(ServerIdentity server, DeregisterRequest request, ResponseCode responseCode,
             String errorMessage, Exception cause) {
     }
 
     @Override
-    public void onDeregistrationTimeout(Server server, DeregisterRequest request) {
+    public void onDeregistrationTimeout(ServerIdentity server, DeregisterRequest request) {
     }
 }
diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/observer/LwM2mClientObserverDispatcher.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/observer/LwM2mClientObserverDispatcher.java
index 23069726..7091caca 100644
--- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/observer/LwM2mClientObserverDispatcher.java
+++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/observer/LwM2mClientObserverDispatcher.java
@@ -14,7 +14,7 @@ package org.eclipse.leshan.client.observer;
 import java.util.concurrent.CopyOnWriteArrayList;
 
 import org.eclipse.leshan.ResponseCode;
-import org.eclipse.leshan.client.servers.Server;
+import org.eclipse.leshan.client.servers.ServerIdentity;
 import org.eclipse.leshan.core.request.BootstrapRequest;
 import org.eclipse.leshan.core.request.DeregisterRequest;
 import org.eclipse.leshan.core.request.RegisterRequest;
@@ -36,21 +36,21 @@ public class LwM2mClientObserverDispatcher implements LwM2mClientObserver {
     }
 
     @Override
-    public void onBootstrapStarted(Server bsserver, BootstrapRequest request) {
+    public void onBootstrapStarted(ServerIdentity bsserver, BootstrapRequest request) {
         for (LwM2mClientObserver observer : observers) {
             observer.onBootstrapStarted(bsserver, request);
         }
     }
 
     @Override
-    public void onBootstrapSuccess(Server bsserver, BootstrapRequest request) {
+    public void onBootstrapSuccess(ServerIdentity bsserver, BootstrapRequest request) {
         for (LwM2mClientObserver observer : observers) {
             observer.onBootstrapSuccess(bsserver, request);
         }
     }
 
     @Override
-    public void onBootstrapFailure(Server bsserver, BootstrapRequest request, ResponseCode responseCode,
+    public void onBootstrapFailure(ServerIdentity bsserver, BootstrapRequest request, ResponseCode responseCode,
             String errorMessage, Exception cause) {
         for (LwM2mClientObserver observer : observers) {
             observer.onBootstrapFailure(bsserver, request, responseCode, errorMessage, cause);
@@ -58,28 +58,28 @@ public class LwM2mClientObserverDispatcher implements LwM2mClientObserver {
     }
 
     @Override
-    public void onBootstrapTimeout(Server bsserver, BootstrapRequest request) {
+    public void onBootstrapTimeout(ServerIdentity bsserver, BootstrapRequest request) {
         for (LwM2mClientObserver observer : observers) {
             observer.onBootstrapTimeout(bsserver, request);
         }
     }
 
     @Override
-    public void onRegistrationStarted(Server server, RegisterRequest request) {
+    public void onRegistrationStarted(ServerIdentity server, RegisterRequest request) {
         for (LwM2mClientObserver observer : observers) {
             observer.onRegistrationStarted(server, request);
         }
     }
 
     @Override
-    public void onRegistrationSuccess(Server server, RegisterRequest request, String registrationID) {
+    public void onRegistrationSuccess(ServerIdentity server, RegisterRequest request, String registrationID) {
         for (LwM2mClientObserver observer : observers) {
             observer.onRegistrationSuccess(server, request, registrationID);
         }
     }
 
     @Override
-    public void onRegistrationFailure(Server server, RegisterRequest request, ResponseCode responseCode,
+    public void onRegistrationFailure(ServerIdentity server, RegisterRequest request, ResponseCode responseCode,
             String errorMessage, Exception cause) {
         for (LwM2mClientObserver observer : observers) {
             observer.onRegistrationFailure(server, request, responseCode, errorMessage, cause);
@@ -87,28 +87,28 @@ public class LwM2mClientObserverDispatcher implements LwM2mClientObserver {
     }
 
     @Override
-    public void onRegistrationTimeout(Server server, RegisterRequest request) {
+    public void onRegistrationTimeout(ServerIdentity server, RegisterRequest request) {
         for (LwM2mClientObserver observer : observers) {
             observer.onRegistrationTimeout(server, request);
         }
     }
 
     @Override
-    public void onUpdateStarted(Server server, UpdateRequest request) {
+    public void onUpdateStarted(ServerIdentity server, UpdateRequest request) {
         for (LwM2mClientObserver observer : observers) {
             observer.onUpdateStarted(server, request);
         }
     }
 
     @Override
-    public void onUpdateSuccess(Server server, UpdateRequest request) {
+    public void onUpdateSuccess(ServerIdentity server, UpdateRequest request) {
         for (LwM2mClientObserver observer : observers) {
             observer.onUpdateSuccess(server, request);
         }
     }
 
     @Override
-    public void onUpdateFailure(Server server, UpdateRequest request, ResponseCode responseCode, String errorMessage,
+    public void onUpdateFailure(ServerIdentity server, UpdateRequest request, ResponseCode responseCode, String errorMessage,
             Exception cause) {
         for (LwM2mClientObserver observer : observers) {
             observer.onUpdateFailure(server, request, responseCode, errorMessage, cause);
@@ -116,28 +116,28 @@ public class LwM2mClientObserverDispatcher implements LwM2mClientObserver {
     }
 
     @Override
-    public void onUpdateTimeout(Server server, UpdateRequest request) {
+    public void onUpdateTimeout(ServerIdentity server, UpdateRequest request) {
         for (LwM2mClientObserver observer : observers) {
             observer.onUpdateTimeout(server, request);
         }
     }
 
     @Override
-    public void onDeregistrationStarted(Server server, DeregisterRequest request) {
+    public void onDeregistrationStarted(ServerIdentity server, DeregisterRequest request) {
         for (LwM2mClientObserver observer : observers) {
             observer.onDeregistrationStarted(server, request);
         }
     }
 
     @Override
-    public void onDeregistrationSuccess(Server server, DeregisterRequest request) {
+    public void onDeregistrationSuccess(ServerIdentity server, DeregisterRequest request) {
         for (LwM2mClientObserver observer : observers) {
             observer.onDeregistrationSuccess(server, request);
         }
     }
 
     @Override
-    public void onDeregistrationFailure(Server server, DeregisterRequest request, ResponseCode responseCode,
+    public void onDeregistrationFailure(ServerIdentity server, DeregisterRequest request, ResponseCode responseCode,
             String errorMessage, Exception e) {
         for (LwM2mClientObserver observer : observers) {
             observer.onDeregistrationFailure(server, request, responseCode, errorMessage, e);
@@ -145,7 +145,7 @@ public class LwM2mClientObserverDispatcher implements LwM2mClientObserver {
     }
 
     @Override
-    public void onDeregistrationTimeout(Server server, DeregisterRequest request) {
+    public void onDeregistrationTimeout(ServerIdentity server, DeregisterRequest request) {
         for (LwM2mClientObserver observer : observers) {
             observer.onDeregistrationTimeout(server, request);
         }
diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/request/LwM2mRequestSender.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/request/LwM2mRequestSender.java
index e0bd295e..6a9df669 100644
--- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/request/LwM2mRequestSender.java
+++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/request/LwM2mRequestSender.java
@@ -15,8 +15,7 @@
  *******************************************************************************/
 package org.eclipse.leshan.client.request;
 
-import java.net.InetSocketAddress;
-
+import org.eclipse.leshan.client.servers.ServerIdentity;
 import org.eclipse.leshan.core.node.codec.CodecException;
 import org.eclipse.leshan.core.request.UplinkRequest;
 import org.eclipse.leshan.core.request.exception.ClientSleepingException;
@@ -31,7 +30,7 @@ import org.eclipse.leshan.core.response.LwM2mResponse;
 import org.eclipse.leshan.core.response.ResponseCallback;
 
 /**
- * A {@link LwM2mRequestSender} is responsible to send LWM2M {@link UplinkRequest} for a given Server.
+ * A {@link LwM2mRequestSender} is responsible to send LWM2M {@link UplinkRequest} for a given {@link ServerIdentity}.
  */
 public interface LwM2mRequestSender {
 
@@ -58,8 +57,8 @@ public interface LwM2mRequestSender {
      * @throws UnconnectedPeerException if client is not connected (no dtls connection available).
      * @throws ClientSleepingException if client is currently sleeping.
      */
-    <T extends LwM2mResponse> T send(InetSocketAddress server, boolean secure, UplinkRequest<T> request,
-            long timeoutInMs) throws InterruptedException;
+    <T extends LwM2mResponse> T send(ServerIdentity server, UplinkRequest<T> request, long timeoutInMs)
+            throws InterruptedException;
 
     /**
      * Send a Lightweight M2M {@link UplinkRequest} asynchronously to a LWM2M Server.
@@ -88,8 +87,8 @@ public interface LwM2mRequestSender {
      *        This callback MUST NOT be null.
      * @throws CodecException if request payload can not be encoded.
      */
-    <T extends LwM2mResponse> void send(InetSocketAddress server, boolean secure, UplinkRequest<T> request,
-            long timeoutInMs, ResponseCallback<T> responseCallback, ErrorCallback errorCallback);
+    <T extends LwM2mResponse> void send(ServerIdentity server, UplinkRequest<T> request, long timeoutInMs,
+            ResponseCallback<T> responseCallback, ErrorCallback errorCallback);
 
     /**
      * Destroy the sender. Free all resources. Sender can not be used anymore.
diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/BaseInstanceEnabler.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/BaseInstanceEnabler.java
index cacec613..d642d8ee 100644
--- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/BaseInstanceEnabler.java
+++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/BaseInstanceEnabler.java
@@ -24,7 +24,7 @@ import java.util.List;
 import java.util.Map;
 
 import org.eclipse.leshan.client.LwM2mClient;
-import org.eclipse.leshan.client.request.ServerIdentity;
+import org.eclipse.leshan.client.servers.ServerIdentity;
 import org.eclipse.leshan.core.model.ObjectModel;
 import org.eclipse.leshan.core.model.ResourceModel;
 import org.eclipse.leshan.core.node.LwM2mObjectInstance;
diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/BaseObjectEnabler.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/BaseObjectEnabler.java
index 70034e32..364a42e4 100644
--- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/BaseObjectEnabler.java
+++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/BaseObjectEnabler.java
@@ -30,8 +30,8 @@ import java.util.Set;
 import org.eclipse.leshan.Link;
 import org.eclipse.leshan.LwM2mId;
 import org.eclipse.leshan.client.LwM2mClient;
-import org.eclipse.leshan.client.request.ServerIdentity;
 import org.eclipse.leshan.client.resource.listener.ObjectListener;
+import org.eclipse.leshan.client.servers.ServerIdentity;
 import org.eclipse.leshan.client.util.LinkFormatHelper;
 import org.eclipse.leshan.core.model.ObjectModel;
 import org.eclipse.leshan.core.model.ResourceModel;
@@ -111,6 +111,9 @@ public abstract class BaseObjectEnabler implements LwM2mObjectEnabler {
                 if (id == LwM2mId.SECURITY) {
                     return CreateResponse.notFound();
                 }
+            } else if (identity.isLwm2mBootstrapServer()) {
+                // create is not supported for bootstrap
+                CreateResponse.methodNotAllowed();
             }
 
             if (request.unknownObjectInstanceId()) {
@@ -238,7 +241,7 @@ public abstract class BaseObjectEnabler implements LwM2mObjectEnabler {
     public synchronized BootstrapWriteResponse write(ServerIdentity identity, BootstrapWriteRequest request) {
 
         // We should not get a bootstrapWriteRequest from a LWM2M server
-        if (!identity.isLwm2mBootstrapServer()) {
+        if (identity.isLwm2mServer()) {
             return BootstrapWriteResponse.internalServerError("bootstrap write request from LWM2M server");
         }
 
@@ -253,6 +256,8 @@ public abstract class BaseObjectEnabler implements LwM2mObjectEnabler {
     @Override
     public synchronized DeleteResponse delete(ServerIdentity identity, DeleteRequest request) {
         if (!identity.isSystem()) {
+            if (identity.isLwm2mBootstrapServer())
+                return DeleteResponse.methodNotAllowed();
 
             // delete the security object is forbidden
             if (id == LwM2mId.SECURITY) {
@@ -275,6 +280,9 @@ public abstract class BaseObjectEnabler implements LwM2mObjectEnabler {
     @Override
     public synchronized BootstrapDeleteResponse delete(ServerIdentity identity, BootstrapDeleteRequest request) {
         if (!identity.isSystem()) {
+            if (identity.isLwm2mServer()) {
+                return BootstrapDeleteResponse.internalServerError("bootstrap delete request from LWM2M server");
+            }
             if (id == LwM2mId.DEVICE) {
                 return BootstrapDeleteResponse.badRequest("Device object instance is not deletable");
             }
@@ -325,6 +333,10 @@ public abstract class BaseObjectEnabler implements LwM2mObjectEnabler {
     @Override
     public synchronized WriteAttributesResponse writeAttributes(ServerIdentity identity,
             WriteAttributesRequest request) {
+        // execute is not supported for bootstrap
+        if (identity.isLwm2mBootstrapServer()) {
+            return WriteAttributesResponse.methodNotAllowed();
+        }
         // TODO should be implemented here to be available for all object enabler
         // This should be a not implemented error, but this is not defined in the spec.
         return WriteAttributesResponse.internalServerError("not implemented");
diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/DummyInstanceEnabler.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/DummyInstanceEnabler.java
index 075d02a1..31542186 100644
--- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/DummyInstanceEnabler.java
+++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/DummyInstanceEnabler.java
@@ -22,7 +22,7 @@ import java.util.Date;
 import java.util.HashMap;
 import java.util.Map;
 
-import org.eclipse.leshan.client.request.ServerIdentity;
+import org.eclipse.leshan.client.servers.ServerIdentity;
 import org.eclipse.leshan.core.model.ObjectModel;
 import org.eclipse.leshan.core.model.ResourceModel;
 import org.eclipse.leshan.core.node.LwM2mMultipleResource;
diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/LwM2mInstanceEnabler.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/LwM2mInstanceEnabler.java
index 3e5f63ad..2570fdcf 100644
--- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/LwM2mInstanceEnabler.java
+++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/LwM2mInstanceEnabler.java
@@ -21,7 +21,7 @@ package org.eclipse.leshan.client.resource;
 import java.util.List;
 
 import org.eclipse.leshan.client.LwM2mClient;
-import org.eclipse.leshan.client.request.ServerIdentity;
+import org.eclipse.leshan.client.servers.ServerIdentity;
 import org.eclipse.leshan.core.model.ObjectModel;
 import org.eclipse.leshan.core.node.LwM2mObjectInstance;
 import org.eclipse.leshan.core.node.LwM2mResource;
diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/LwM2mObjectEnabler.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/LwM2mObjectEnabler.java
index 1e419dfc..e4f4234e 100644
--- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/LwM2mObjectEnabler.java
+++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/LwM2mObjectEnabler.java
@@ -19,8 +19,8 @@ package org.eclipse.leshan.client.resource;
 import java.util.List;
 
 import org.eclipse.leshan.client.LwM2mClient;
-import org.eclipse.leshan.client.request.ServerIdentity;
 import org.eclipse.leshan.client.resource.listener.ObjectListener;
+import org.eclipse.leshan.client.servers.ServerIdentity;
 import org.eclipse.leshan.core.model.ObjectModel;
 import org.eclipse.leshan.core.request.BootstrapDeleteRequest;
 import org.eclipse.leshan.core.request.BootstrapWriteRequest;
diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/ObjectEnabler.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/ObjectEnabler.java
index dc21687a..6e33ee96 100644
--- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/ObjectEnabler.java
+++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/ObjectEnabler.java
@@ -27,7 +27,7 @@ import java.util.Map.Entry;
 
 import org.eclipse.leshan.LwM2mId;
 import org.eclipse.leshan.client.LwM2mClient;
-import org.eclipse.leshan.client.request.ServerIdentity;
+import org.eclipse.leshan.client.servers.ServerIdentity;
 import org.eclipse.leshan.client.servers.ServersInfoExtractor;
 import org.eclipse.leshan.core.model.ObjectModel;
 import org.eclipse.leshan.core.node.LwM2mObject;
diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/SimpleInstanceEnabler.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/SimpleInstanceEnabler.java
index 4b211591..7f6befc4 100644
--- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/SimpleInstanceEnabler.java
+++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/SimpleInstanceEnabler.java
@@ -23,7 +23,7 @@ import java.util.Date;
 import java.util.HashMap;
 import java.util.Map;
 
-import org.eclipse.leshan.client.request.ServerIdentity;
+import org.eclipse.leshan.client.servers.ServerIdentity;
 import org.eclipse.leshan.core.model.ObjectModel;
 import org.eclipse.leshan.core.model.ResourceModel;
 import org.eclipse.leshan.core.node.LwM2mMultipleResource;
diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/Server.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/Server.java
deleted file mode 100644
index a2f7f6de..00000000
--- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/Server.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2017 Sierra Wireless and others.
- * 
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v2.0
- * and Eclipse Distribution License v1.0 which accompany this distribution.
- * 
- * The Eclipse Public License is available at
- *    http://www.eclipse.org/legal/epl-v20.html
- * and the Eclipse Distribution License is available at
- *    http://www.eclipse.org/org/documents/edl-v10.html.
- * 
- * Contributors:
- *     Sierra Wireless - initial API and implementation
- *******************************************************************************/
-package org.eclipse.leshan.client.servers;
-
-import org.eclipse.leshan.core.request.Identity;
-
-/**
- * A Bean which identify a LWM2M Server.
- */
-public class Server {
-
-    private final Identity identity;
-    private final Long id;
-
-    public Server(Identity identity, Long id) {
-        this.identity = identity;
-        this.id = id;
-    }
-
-    public Identity getIdentity() {
-        return identity;
-    }
-
-    public Long getId() {
-        return id;
-    }
-
-    public String getUri() {
-        StringBuilder uri = new StringBuilder();
-        if (identity.isSecure())
-            uri.append("coaps://");
-        else
-            uri.append("coap://");
-        uri.append(identity.getPeerAddress().getHostString());
-        uri.append(":");
-        uri.append(identity.getPeerAddress().getPort());
-        return uri.toString();
-    }
-}
diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/request/ServerIdentity.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/ServerIdentity.java
similarity index 50%
rename from leshan-client-core/src/main/java/org/eclipse/leshan/client/request/ServerIdentity.java
rename to leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/ServerIdentity.java
index fb5c6c90..1c20d99d 100644
--- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/request/ServerIdentity.java
+++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/ServerIdentity.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2016 Bosch Software Innovations GmbH and others.
+ * Copyright (c) 2017 Sierra Wireless and others.
  * 
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v2.0
@@ -11,25 +11,24 @@
  *    http://www.eclipse.org/org/documents/edl-v10.html.
  * 
  * Contributors:
- *    Achim Kraus (Bosch Software Innovations GmbH) - Initial creation
- ******************************************************************************/
-
-package org.eclipse.leshan.client.request;
+ *     Sierra Wireless - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.leshan.client.servers;
 
 import java.net.InetSocketAddress;
 
 import org.eclipse.leshan.core.request.Identity;
 
 /**
- * Extend the identity with client relevant additional information. Introduce roles for the identity and makes those
- * roles accessible within the processing context of the Identity.
- * 
- * @author Achim Kraus (Bosch Software Innovations GmbH)
- *
+ * A Bean which identify a LWM2M Server.
  */
-public class ServerIdentity extends Identity {
+public class ServerIdentity {
 
-    private static final long serialVersionUID = 1L;
+    /**
+     * Identity for system calls.
+     */
+    public final static ServerIdentity SYSTEM = new ServerIdentity(
+            Identity.unsecure(InetSocketAddress.createUnresolved(Role.SYSTEM.toString(), 1)), null, Role.SYSTEM);
 
     public enum Role {
         /**
@@ -47,29 +46,28 @@ public class ServerIdentity extends Identity {
         LWM2M_BOOTSTRAP_SERVER
     }
 
-    /**
-     * Identity for system calls.
-     */
-    public final static ServerIdentity SYSTEM = new ServerIdentity(
-            Identity.unsecure(InetSocketAddress.createUnresolved(Role.SYSTEM.toString(), 1)), Role.SYSTEM);
-
-    /**
-     * Role of the associated identity.
-     */
+    private final Identity identity;
+    private final Long id;
     private final Role role;
 
-    /**
-     * Create ServerIdentity using a Identity and the related role. Intended to be used by calling
-     * {@link #createLwm2mBootstrapServerIdentity(Identity)} or {@link #createLwm2mServerIdentity(Identity)}.
-     * 
-     * @param identity identity to be used
-     * @param role related role
-     */
-    protected ServerIdentity(Identity identity, Role role) {
-        super(identity);
+    public ServerIdentity(Identity identity, Long id) {
+        this(identity, id, Role.LWM2M_SERVER);
+    }
+
+    public ServerIdentity(Identity identity, Long id, Role role) {
+        this.identity = identity;
+        this.id = id;
         this.role = role;
     }
 
+    public Identity getIdentity() {
+        return identity;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
     /**
      * Get related role.
      * 
@@ -106,33 +104,61 @@ public class ServerIdentity extends Identity {
         return Role.SYSTEM == role;
     }
 
-    /**
-     * Create a server identity with role {@link Role#LWM2M_BOOTSTRAP_SERVER}.
-     * 
-     * @param identity identity to be used
-     * @return server identity with {@link Role#LWM2M_BOOTSTRAP_SERVER} associated.
-     */
-    public static ServerIdentity createLwm2mBootstrapServerIdentity(Identity identity) {
-        return new ServerIdentity(identity, Role.LWM2M_BOOTSTRAP_SERVER);
-    }
-
-    /**
-     * Create a server identity with role {@link Role#LWM2M_SERVER}.
-     * 
-     * @param identity identity to be used
-     * @return server identity with {@link Role#LWM2M_SERVER} associated.
-     */
-    public static ServerIdentity createLwm2mServerIdentity(Identity identity) {
-        return new ServerIdentity(identity, Role.LWM2M_SERVER);
+    public String getUri() {
+        StringBuilder uri = new StringBuilder();
+        if (identity.isSecure())
+            uri.append("coaps://");
+        else
+            uri.append("coap://");
+        uri.append(identity.getPeerAddress().getHostString());
+        uri.append(":");
+        uri.append(identity.getPeerAddress().getPort());
+        return uri.toString();
     }
 
     @Override
     public String toString() {
         if (isSystem()) {
-            return this.role.toString();
-        } else {
-            return String.format("%s[%s]", super.toString(), this.role);
+            return "System";
+        } else if (isLwm2mBootstrapServer()) {
+            return String.format("%s[%s]", getUri(), getRole());
+        } else if (isLwm2mServer()) {
+            return String.format("%s[%s %d]", getUri(), getRole(), getId());
         }
+        return null;
+    }
 
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((id == null) ? 0 : id.hashCode());
+        result = prime * result + ((identity == null) ? 0 : identity.hashCode());
+        result = prime * result + ((role == null) ? 0 : role.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        ServerIdentity other = (ServerIdentity) obj;
+        if (id == null) {
+            if (other.id != null)
+                return false;
+        } else if (!id.equals(other.id))
+            return false;
+        if (identity == null) {
+            if (other.identity != null)
+                return false;
+        } else if (!identity.equals(other.identity))
+            return false;
+        if (role != other.role)
+            return false;
+        return true;
     }
 }
diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/ServerInfo.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/ServerInfo.java
index 2a7d558a..70caeb57 100644
--- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/ServerInfo.java
+++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/ServerInfo.java
@@ -37,6 +37,7 @@ public class ServerInfo {
     private static final Logger LOG = LoggerFactory.getLogger(ServerInfo.class);
 
     public long serverId;
+    public boolean bootstrap = false;
     public URI serverUri;
     public SecurityMode secureMode;
 
diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/ServersInfoExtractor.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/ServersInfoExtractor.java
index a8615f8e..89a0b4ea 100644
--- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/ServersInfoExtractor.java
+++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/ServersInfoExtractor.java
@@ -17,7 +17,7 @@
 package org.eclipse.leshan.client.servers;
 
 import static org.eclipse.leshan.LwM2mId.*;
-import static org.eclipse.leshan.client.request.ServerIdentity.SYSTEM;
+import static org.eclipse.leshan.client.servers.ServerIdentity.SYSTEM;
 
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
@@ -37,7 +37,6 @@ import java.util.Map;
 
 import org.eclipse.leshan.LwM2mId;
 import org.eclipse.leshan.SecurityMode;
-import org.eclipse.leshan.client.request.ServerIdentity;
 import org.eclipse.leshan.client.resource.LwM2mInstanceEnabler;
 import org.eclipse.leshan.client.resource.LwM2mObjectEnabler;
 import org.eclipse.leshan.core.node.LwM2mObject;
@@ -75,6 +74,7 @@ public class ServersInfoExtractor {
                     } else {
                         // create bootstrap info
                         ServerInfo info = new ServerInfo();
+                        info.bootstrap = true;
                         LwM2mResource serverIdResource = security.getResource(SEC_SERVER_ID);
                         if (serverIdResource != null && serverIdResource.getValue() != null)
                             info.serverId = (long) serverIdResource.getValue();
@@ -99,6 +99,7 @@ public class ServersInfoExtractor {
                 } else {
                     // create device management info
                     DmServerInfo info = new DmServerInfo();
+                    info.bootstrap = false;
                     info.serverUri = new URI((String) security.getResource(SEC_SERVER_URI).getValue());
                     info.serverId = (long) security.getResource(SEC_SERVER_ID).getValue();
                     info.secureMode = getSecurityMode(security);
@@ -168,6 +169,16 @@ public class ServersInfoExtractor {
         }
     }
 
+    public static Long getServerId(LwM2mObjectEnabler serverEnabler, int instanceId) {
+        ReadResponse response = serverEnabler.read(ServerIdentity.SYSTEM,
+                new ReadRequest(SERVER, instanceId, SRV_SERVER_ID));
+        if (response.isSuccess()) {
+            return (Long) ((LwM2mResource) response.getContent()).getValue();
+        } else {
+            return null;
+        }
+    }
+
     public static SecurityMode getSecurityMode(LwM2mObjectInstance securityInstance) {
         return SecurityMode.fromCode((long) securityInstance.getResource(SEC_SECURITY_MODE).getValue());
     }
diff --git a/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/MyDevice.java b/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/MyDevice.java
index e2d15f58..5453fa05 100644
--- a/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/MyDevice.java
+++ b/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/MyDevice.java
@@ -12,8 +12,8 @@ import java.util.TimeZone;
 import java.util.Timer;
 import java.util.TimerTask;
 
-import org.eclipse.leshan.client.request.ServerIdentity;
 import org.eclipse.leshan.client.resource.BaseInstanceEnabler;
+import org.eclipse.leshan.client.servers.ServerIdentity;
 import org.eclipse.leshan.core.model.ObjectModel;
 import org.eclipse.leshan.core.model.ResourceModel.Type;
 import org.eclipse.leshan.core.node.LwM2mResource;
diff --git a/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/MyLocation.java b/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/MyLocation.java
index 4069e092..0c6fe8a6 100644
--- a/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/MyLocation.java
+++ b/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/MyLocation.java
@@ -5,8 +5,8 @@ import java.util.Date;
 import java.util.List;
 import java.util.Random;
 
-import org.eclipse.leshan.client.request.ServerIdentity;
 import org.eclipse.leshan.client.resource.BaseInstanceEnabler;
+import org.eclipse.leshan.client.servers.ServerIdentity;
 import org.eclipse.leshan.core.model.ObjectModel;
 import org.eclipse.leshan.core.response.ReadResponse;
 import org.slf4j.Logger;
diff --git a/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/RandomTemperatureSensor.java b/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/RandomTemperatureSensor.java
index 73d4f2ec..2e3aa404 100644
--- a/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/RandomTemperatureSensor.java
+++ b/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/RandomTemperatureSensor.java
@@ -9,8 +9,8 @@ import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 
-import org.eclipse.leshan.client.request.ServerIdentity;
 import org.eclipse.leshan.client.resource.BaseInstanceEnabler;
+import org.eclipse.leshan.client.servers.ServerIdentity;
 import org.eclipse.leshan.core.model.ObjectModel;
 import org.eclipse.leshan.core.response.ExecuteResponse;
 import org.eclipse.leshan.core.response.ReadResponse;


Test patch:
diff --git a/leshan-client-core/src/test/java/org/eclipse/leshan/client/util/ObjectEnablerTest.java b/leshan-client-core/src/test/java/org/eclipse/leshan/client/util/ObjectEnablerTest.java
index b515a7ff..ffd21bae 100644
--- a/leshan-client-core/src/test/java/org/eclipse/leshan/client/util/ObjectEnablerTest.java
+++ b/leshan-client-core/src/test/java/org/eclipse/leshan/client/util/ObjectEnablerTest.java
@@ -23,10 +23,10 @@ import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
 import org.eclipse.leshan.LwM2mId;
-import org.eclipse.leshan.client.request.ServerIdentity;
 import org.eclipse.leshan.client.resource.BaseInstanceEnabler;
 import org.eclipse.leshan.client.resource.LwM2mObjectEnabler;
 import org.eclipse.leshan.client.resource.ObjectsInitializer;
+import org.eclipse.leshan.client.servers.ServerIdentity;
 import org.eclipse.leshan.core.request.DeleteRequest;
 import org.junit.Test;
 
diff --git a/leshan-client-core/src/test/java/org/eclipse/leshan/client/util/ServerTest.java b/leshan-client-core/src/test/java/org/eclipse/leshan/client/util/ServerTest.java
index 51866f25..df47c0f0 100644
--- a/leshan-client-core/src/test/java/org/eclipse/leshan/client/util/ServerTest.java
+++ b/leshan-client-core/src/test/java/org/eclipse/leshan/client/util/ServerTest.java
@@ -19,7 +19,7 @@ import static org.junit.Assert.assertEquals;
 
 import java.net.InetSocketAddress;
 
-import org.eclipse.leshan.client.servers.Server;
+import org.eclipse.leshan.client.servers.ServerIdentity;
 import org.eclipse.leshan.core.request.Identity;
 import org.junit.Test;
 
@@ -27,19 +27,19 @@ public class ServerTest {
 
     @Test
     public void test_server_getUri_with_localhost_hostname() {
-        Server server = new Server(Identity.unsecure(new InetSocketAddress("localhost", 5683)), 123l);
+        ServerIdentity server = new ServerIdentity(Identity.unsecure(new InetSocketAddress("localhost", 5683)), 123l);
         assertEquals("coap://localhost:5683", server.getUri());
     }
 
     @Test
     public void test_server_getUri_with_unknown_hostname() {
-        Server server = new Server(Identity.unsecure(new InetSocketAddress("unknownhost", 5683)), 123l);
+        ServerIdentity server = new ServerIdentity(Identity.unsecure(new InetSocketAddress("unknownhost", 5683)), 123l);
         assertEquals("coap://unknownhost:5683", server.getUri());
     }
 
     @Test
     public void test_server_getUri_with_ipaddress() {
-        Server server = new Server(Identity.unsecure(new InetSocketAddress("50.0.0.1", 5683)), 123l);
+        ServerIdentity server = new ServerIdentity(Identity.unsecure(new InetSocketAddress("50.0.0.1", 5683)), 123l);
         assertEquals("coap://50.0.0.1:5683", server.getUri());
     }
 }
diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapTest.java
index 0d00dd89..605fed08 100644
--- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapTest.java
+++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapTest.java
@@ -22,10 +22,10 @@ import static org.junit.Assert.*;
 
 import org.eclipse.leshan.LwM2mId;
 import org.eclipse.leshan.SecurityMode;
-import org.eclipse.leshan.client.request.ServerIdentity;
 import org.eclipse.leshan.client.resource.LwM2mObjectEnabler;
 import org.eclipse.leshan.client.resource.ObjectsInitializer;
 import org.eclipse.leshan.client.resource.SimpleInstanceEnabler;
+import org.eclipse.leshan.client.servers.ServerIdentity;
 import org.eclipse.leshan.core.node.LwM2mObject;
 import org.eclipse.leshan.core.node.LwM2mObjectInstance;
 import org.eclipse.leshan.core.request.ReadRequest;
diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/IntegrationTestHelper.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/IntegrationTestHelper.java
index 3bb53632..a92784b8 100644
--- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/IntegrationTestHelper.java
+++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/IntegrationTestHelper.java
@@ -32,12 +32,11 @@ import org.eclipse.leshan.client.californium.LeshanClient;
 import org.eclipse.leshan.client.californium.LeshanClientBuilder;
 import org.eclipse.leshan.client.object.Device;
 import org.eclipse.leshan.client.object.Security;
-import org.eclipse.leshan.client.object.Server;
-import org.eclipse.leshan.client.request.ServerIdentity;
 import org.eclipse.leshan.client.resource.DummyInstanceEnabler;
 import org.eclipse.leshan.client.resource.LwM2mObjectEnabler;
 import org.eclipse.leshan.client.resource.ObjectsInitializer;
 import org.eclipse.leshan.client.resource.SimpleInstanceEnabler;
+import org.eclipse.leshan.client.servers.ServerIdentity;
 import org.eclipse.leshan.core.model.ObjectLoader;
 import org.eclipse.leshan.core.model.ObjectModel;
 import org.eclipse.leshan.core.model.ResourceModel;
@@ -158,7 +157,8 @@ public class IntegrationTestHelper {
         initializer.setInstancesForObject(LwM2mId.SECURITY, Security.noSec(
                 "coap://" + server.getUnsecuredAddress().getHostString() + ":" + server.getUnsecuredAddress().getPort(),
                 12345));
-        initializer.setInstancesForObject(LwM2mId.SERVER, new Server(12345, LIFETIME, BindingMode.U, false));
+        initializer.setInstancesForObject(LwM2mId.SERVER,
+                new org.eclipse.leshan.client.object.Server(12345, LIFETIME, BindingMode.U, false));
         initializer.setInstancesForObject(LwM2mId.DEVICE, new TestDevice("Eclipse Leshan", MODEL_NUMBER, "12345", "U"));
         initializer.setClassForObject(LwM2mId.ACCESS_CONTROL, DummyInstanceEnabler.class);
         initializer.setInstancesForObject(TEST_OBJECT_ID, new DummyInstanceEnabler(0),
@@ -286,6 +286,13 @@ public class IntegrationTestHelper {
         return server.getRegistrationService().getByEndpoint(currentEndpointIdentifier.get());
     }
 
+    public ServerIdentity getCurrentRegisteredServer() {
+        Map<String, ServerIdentity> registeredServers = client.getRegisteredServers();
+        if (registeredServers != null && !registeredServers.isEmpty())
+            return registeredServers.values().iterator().next();
+        return null;
+    }
+
     public void deregisterClient() {
         Registration r = getCurrentRegistration();
         if (r != null)
diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/LockStepLwM2mClient.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/LockStepLwM2mClient.java
index 437d1cc9..28afe580 100644
--- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/LockStepLwM2mClient.java
+++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/LockStepLwM2mClient.java
@@ -23,6 +23,7 @@ import org.eclipse.californium.core.network.serialization.UdpDataSerializer;
 import org.eclipse.californium.core.test.lockstep.LockstepEndpoint;
 import org.eclipse.californium.elements.RawData;
 import org.eclipse.leshan.client.californium.request.CoapRequestBuilder;
+import org.eclipse.leshan.core.request.Identity;
 import org.eclipse.leshan.core.request.UplinkRequest;
 import org.eclipse.leshan.core.response.LwM2mResponse;
 
@@ -38,7 +39,7 @@ public class LockStepLwM2mClient extends LockstepEndpoint {
 
     public void sendLwM2mRequest(UplinkRequest<? extends LwM2mResponse> lwm2mReq) {
         // create CoAP request
-        CoapRequestBuilder coapRequestBuilder = new CoapRequestBuilder(destination);
+        CoapRequestBuilder coapRequestBuilder = new CoapRequestBuilder(Identity.unsecure(destination));
         lwm2mReq.accept(coapRequestBuilder);
         Request coapReq = coapRequestBuilder.getRequest();
         byte[] token = new byte[8];
diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/ObserveTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/ObserveTest.java
index 9bbcbf6d..16d11160 100644
--- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/ObserveTest.java
+++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/ObserveTest.java
@@ -42,6 +42,7 @@ import org.eclipse.californium.elements.EndpointContext;
 import org.eclipse.californium.elements.RawData;
 import org.eclipse.leshan.ResponseCode;
 import org.eclipse.leshan.client.californium.LeshanClient;
+import org.eclipse.leshan.client.servers.ServerIdentity;
 import org.eclipse.leshan.core.model.StaticModel;
 import org.eclipse.leshan.core.node.LwM2mObject;
 import org.eclipse.leshan.core.node.LwM2mObjectInstance;
@@ -327,8 +328,9 @@ public class ObserveTest {
         byte[] payload = LwM2mNodeJsonEncoder.encode(LwM2mSingleResource.newStringResource(15, "Paris"),
                 new LwM2mPath("/3/0/15"), new StaticModel(helper.createObjectModels()), new LwM2mValueChecker());
         Response firstCoapResponse = (Response) observeResponse.getCoapResponse();
-        sendNotification(getConnector(helper.client), payload, firstCoapResponse, 666); // 666 is not a supported //
-                                                                                        // contentFormat.
+        sendNotification(getConnector(helper.client, helper.getCurrentRegisteredServer()), payload, firstCoapResponse,
+                666); // 666 is not a supported //
+        // contentFormat.
         // *** Hack End *** //
 
         // verify result
@@ -369,7 +371,8 @@ public class ObserveTest {
         byte[] payload = LwM2mNodeJsonEncoder.encodeTimestampedData(timestampedNodes, new LwM2mPath("/3/0/15"),
                 new StaticModel(helper.createObjectModels()), new LwM2mValueChecker());
         Response firstCoapResponse = (Response) observeResponse.getCoapResponse();
-        sendNotification(getConnector(helper.client), payload, firstCoapResponse, ContentFormat.JSON_CODE);
+        sendNotification(getConnector(helper.client, helper.getCurrentRegisteredServer()), payload, firstCoapResponse,
+                ContentFormat.JSON_CODE);
         // *** Hack End *** //
 
         // verify result
@@ -412,7 +415,8 @@ public class ObserveTest {
         byte[] payload = LwM2mNodeJsonEncoder.encodeTimestampedData(timestampedNodes, new LwM2mPath("/3/0"),
                 new StaticModel(helper.createObjectModels()), new LwM2mValueChecker());
         Response firstCoapResponse = (Response) observeResponse.getCoapResponse();
-        sendNotification(getConnector(helper.client), payload, firstCoapResponse, ContentFormat.JSON_CODE);
+        sendNotification(getConnector(helper.client, helper.getCurrentRegisteredServer()), payload, firstCoapResponse,
+                ContentFormat.JSON_CODE);
         // *** Hack End *** //
 
         // verify result
@@ -456,7 +460,8 @@ public class ObserveTest {
                 new StaticModel(helper.createObjectModels()), new LwM2mValueChecker());
 
         Response firstCoapResponse = (Response) observeResponse.getCoapResponse();
-        sendNotification(getConnector(helper.client), payload, firstCoapResponse, ContentFormat.JSON_CODE);
+        sendNotification(getConnector(helper.client, helper.getCurrentRegisteredServer()), payload, firstCoapResponse,
+                ContentFormat.JSON_CODE);
         // *** Hack End *** //
 
         // verify result
@@ -491,8 +496,8 @@ public class ObserveTest {
         connector.send(data);
     }
 
-    private Connector getConnector(LeshanClient client) {
-        CoapEndpoint endpoint = (CoapEndpoint) helper.client.coap().getServer().getEndpoint(helper.client.getAddress());
+    private Connector getConnector(LeshanClient client, ServerIdentity server) {
+        CoapEndpoint endpoint = (CoapEndpoint) client.coap().getServer().getEndpoint(client.getAddress(server));
         return endpoint.getConnector();
     }
 
diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecurityTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecurityTest.java
index b51d6887..c426a9d7 100644
--- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecurityTest.java
+++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecurityTest.java
@@ -124,7 +124,8 @@ public class SecurityTest {
 
         // Create new session with new credentials at client side.
         // Get connector
-        Endpoint endpoint = helper.client.coap().getServer().getEndpoint(helper.client.getAddress());
+        Endpoint endpoint = helper.client.coap().getServer()
+                .getEndpoint(helper.client.getAddress(helper.getCurrentRegisteredServer()));
         DTLSConnector connector = (DTLSConnector) ((CoapEndpoint) endpoint).getConnector();
         // Clear DTLS session to force new handshake
         connector.clearConnectionState();
diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SynchronousClientObserver.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SynchronousClientObserver.java
index ffa5abef..de5525dd 100644
--- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SynchronousClientObserver.java
+++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SynchronousClientObserver.java
@@ -22,7 +22,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.eclipse.leshan.ResponseCode;
 import org.eclipse.leshan.client.observer.LwM2mClientObserverAdapter;
-import org.eclipse.leshan.client.servers.Server;
+import org.eclipse.leshan.client.servers.ServerIdentity;
 import org.eclipse.leshan.core.request.BootstrapRequest;
 import org.eclipse.leshan.core.request.DeregisterRequest;
 import org.eclipse.leshan.core.request.RegisterRequest;
@@ -47,74 +47,74 @@ public class SynchronousClientObserver extends LwM2mClientObserverAdapter {
     private AtomicBoolean bootstrapFailed = new AtomicBoolean(false);
 
     @Override
-    public void onBootstrapSuccess(Server bsserver, BootstrapRequest request) {
+    public void onBootstrapSuccess(ServerIdentity bsserver, BootstrapRequest request) {
         bootstrapSucceed.set(true);
         bootstrapLatch.countDown();
     }
 
     @Override
-    public void onBootstrapFailure(Server bsserver, BootstrapRequest request, ResponseCode responseCode,
+    public void onBootstrapFailure(ServerIdentity bsserver, BootstrapRequest request, ResponseCode responseCode,
             String errorMessage, Exception cause) {
         bootstrapFailed.set(true);
         bootstrapLatch.countDown();
     }
 
     @Override
-    public void onBootstrapTimeout(Server bsserver, BootstrapRequest request) {
+    public void onBootstrapTimeout(ServerIdentity bsserver, BootstrapRequest request) {
         bootstrapLatch.countDown();
     }
 
     @Override
-    public void onRegistrationSuccess(Server server, RegisterRequest request, String registrationID) {
+    public void onRegistrationSuccess(ServerIdentity server, RegisterRequest request, String registrationID) {
         registerSucceed.set(true);
         registerLatch.countDown();
     }
 
     @Override
-    public void onRegistrationFailure(Server server, RegisterRequest request, ResponseCode responseCode,
+    public void onRegistrationFailure(ServerIdentity server, RegisterRequest request, ResponseCode responseCode,
             String errorMessage, Exception cause) {
         registerFailed.set(true);
         registerLatch.countDown();
     }
 
     @Override
-    public void onRegistrationTimeout(Server server, RegisterRequest request) {
+    public void onRegistrationTimeout(ServerIdentity server, RegisterRequest request) {
         registerLatch.countDown();
     }
 
     @Override
-    public void onUpdateSuccess(Server server, UpdateRequest request) {
+    public void onUpdateSuccess(ServerIdentity server, UpdateRequest request) {
         updateSucceed.set(true);
         updateLatch.countDown();
     }
 
     @Override
-    public void onUpdateFailure(Server server, UpdateRequest request, ResponseCode responseCode, String errorMessage,
+    public void onUpdateFailure(ServerIdentity server, UpdateRequest request, ResponseCode responseCode, String errorMessage,
             Exception cause) {
         updateFailed.set(true);
         updateLatch.countDown();
     }
 
     @Override
-    public void onUpdateTimeout(Server server, UpdateRequest request) {
+    public void onUpdateTimeout(ServerIdentity server, UpdateRequest request) {
         updateLatch.countDown();
     }
 
     @Override
-    public void onDeregistrationSuccess(Server server, DeregisterRequest request) {
+    public void onDeregistrationSuccess(ServerIdentity server, DeregisterRequest request) {
         deregisterSucceed.set(true);
         deregisterLatch.countDown();
     }
 
     @Override
-    public void onDeregistrationFailure(Server server, DeregisterRequest request, ResponseCode responseCode,
+    public void onDeregistrationFailure(ServerIdentity server, DeregisterRequest request, ResponseCode responseCode,
             String errorMessage, Exception cause) {
         deregisterFailed.set(true);
         deregisterLatch.countDown();
     }
 
     @Override
-    public void onDeregistrationTimeout(Server server, DeregisterRequest request) {
+    public void onDeregistrationTimeout(ServerIdentity server, DeregisterRequest request) {
         deregisterLatch.countDown();
     }