Repository: eclipse/leshan
Pull request #1027 (2021-06-10 09:53:01)

Refactoring Bootstrap Server (again)
This is a tries about making bootstrap server more flexible.
Thoughts which led to this PR are explained here : https://github.com/eclipse/leshan/issues/437

Main changes was : 
- Removing `BootstrapConfiguration` from #883 
- `BootstrapConfig` is no more deprecated... :grimacing: 
- `BootstrapSessionManager` is now responsible to handle all the logic for 1 session : This brings a lot of flexibility for someone who want to implement any kind of behavior.
- `BootstrapHandler` is now just responsible to ensure there is only 1 session at a time for a given client and send requests  provided by `BootstrapSessionManager` one by one.
- The `DefaultBootstrapSessionManager` implements default logic and delegates knowledge of requests to send to `BootstrapTaskProvider`.
- A `BootstrapConfigStoreTaskProvider` use `BootstrapConfigStore` to know which requests must be sent from `BootstrapConfig`. 

This sounds maybe a bit complicated but the idea is 
 - first use `BootstrapConfigStore`
 - if you can not do what you want with it, try to modify default  `BootstrapConfigStoreTaskProvider` or create your own `BootstrapTaskProvider`
 - if you still not be able to do what you want, try to modify `DefaultBootstrapSessionManager` or create your own `BootstrapSessionManager`
 
 This should cover almost all use cases hiding complexity for most of users. 


Patch:
diff --git a/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/LeshanBootstrapServerDemo.java b/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/LeshanBootstrapServerDemo.java
index 57c39f38..28613187 100755
--- a/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/LeshanBootstrapServerDemo.java
+++ b/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/LeshanBootstrapServerDemo.java
@@ -46,14 +46,13 @@ import org.eclipse.jetty.servlet.ServletHolder;
 import org.eclipse.leshan.core.LwM2m;
 import org.eclipse.leshan.core.model.ObjectLoader;
 import org.eclipse.leshan.core.model.ObjectModel;
-import org.eclipse.leshan.core.model.StaticModel;
 import org.eclipse.leshan.core.util.SecurityUtil;
-import org.eclipse.leshan.server.bootstrap.BootstrapConfigurationStoreAdapter;
 import org.eclipse.leshan.server.bootstrap.demo.servlet.BootstrapServlet;
 import org.eclipse.leshan.server.bootstrap.demo.servlet.ServerServlet;
 import org.eclipse.leshan.server.californium.LeshanServerBuilder;
 import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServer;
 import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServerBuilder;
+import org.eclipse.leshan.server.model.VersionedBootstrapModelProvider;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -357,9 +356,9 @@ public class LeshanBootstrapServerDemo {
         // Prepare and start bootstrap server
         LeshanBootstrapServerBuilder builder = new LeshanBootstrapServerBuilder();
         JSONFileBootstrapStore bsStore = new JSONFileBootstrapStore(configFilename);
-        builder.setConfigStore(new BootstrapConfigurationStoreAdapter(bsStore));
+        builder.setConfigStore(bsStore);
         builder.setSecurityStore(new BootstrapConfigSecurityStore(bsStore));
-        builder.setModel(new StaticModel(models));
+        builder.setObjectModelProvider(new VersionedBootstrapModelProvider(models));
 
         // Create DTLS Config
         DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder();
diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/CaliforniumLwM2mBootstrapRequestSender.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/CaliforniumLwM2mBootstrapRequestSender.java
index 4812ad91..1850932d 100644
--- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/CaliforniumLwM2mBootstrapRequestSender.java
+++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/CaliforniumLwM2mBootstrapRequestSender.java
@@ -16,7 +16,7 @@
 package org.eclipse.leshan.server.californium.bootstrap;
 
 import org.eclipse.californium.core.network.Endpoint;
-import org.eclipse.leshan.core.model.LwM2mModel;
+import org.eclipse.leshan.core.Destroyable;
 import org.eclipse.leshan.core.node.LwM2mNode;
 import org.eclipse.leshan.core.node.codec.CodecException;
 import org.eclipse.leshan.core.node.codec.LwM2mNodeDecoder;
@@ -30,7 +30,6 @@ import org.eclipse.leshan.core.request.exception.TimeoutException;
 import org.eclipse.leshan.core.response.ErrorCallback;
 import org.eclipse.leshan.core.response.LwM2mResponse;
 import org.eclipse.leshan.core.response.ResponseCallback;
-import org.eclipse.leshan.core.Destroyable;
 import org.eclipse.leshan.server.bootstrap.BootstrapSession;
 import org.eclipse.leshan.server.bootstrap.LwM2mBootstrapRequestSender;
 import org.eclipse.leshan.server.californium.request.RequestSender;
@@ -44,19 +43,16 @@ public class CaliforniumLwM2mBootstrapRequestSender implements LwM2mBootstrapReq
 
     static final Logger LOG = LoggerFactory.getLogger(CaliforniumLwM2mBootstrapRequestSender.class);
 
-    private final LwM2mModel model;
     private final RequestSender sender;
 
     /**
      * @param secureEndpoint The endpoint used to send coaps request.
      * @param nonSecureEndpoint The endpoint used to send coap request.
-     * @param model the {@link LwM2mModel} used to encode/decode {@link LwM2mNode}.
      * @param encoder The {@link LwM2mNodeEncoder} used to encode {@link LwM2mNode}.
      * @param decoder The {@link LwM2mNodeDecoder} used to encode {@link LwM2mNode}.
      */
-    public CaliforniumLwM2mBootstrapRequestSender(Endpoint secureEndpoint, Endpoint nonSecureEndpoint, LwM2mModel model,
+    public CaliforniumLwM2mBootstrapRequestSender(Endpoint secureEndpoint, Endpoint nonSecureEndpoint,
             LwM2mNodeEncoder encoder, LwM2mNodeDecoder decoder) {
-        this.model = model;
         this.sender = new RequestSender(secureEndpoint, nonSecureEndpoint, encoder, decoder);
     }
 
@@ -83,8 +79,9 @@ public class CaliforniumLwM2mBootstrapRequestSender implements LwM2mBootstrapReq
     @Override
     public <T extends LwM2mResponse> T send(BootstrapSession destination, DownlinkRequest<T> request, long timeoutInMs)
             throws InterruptedException {
-        return sender.sendLwm2mRequest(destination.getEndpoint(), destination.getIdentity(), destination.getId(), model,
-                null, request, null, timeoutInMs, false);
+
+        return sender.sendLwm2mRequest(destination.getEndpoint(), destination.getIdentity(), destination.getId(),
+                destination.getModel(), null, request, null, timeoutInMs, false);
     }
 
     /**
@@ -116,8 +113,9 @@ public class CaliforniumLwM2mBootstrapRequestSender implements LwM2mBootstrapReq
     @Override
     public <T extends LwM2mResponse> void send(BootstrapSession destination, DownlinkRequest<T> request,
             long timeoutInMs, ResponseCallback<T> responseCallback, ErrorCallback errorCallback) {
-        sender.sendLwm2mRequest(destination.getEndpoint(), destination.getIdentity(), destination.getId(), model, null,
-                request, null, timeoutInMs, responseCallback, errorCallback, false);
+
+        sender.sendLwm2mRequest(destination.getEndpoint(), destination.getIdentity(), destination.getId(),
+                destination.getModel(), null, request, null, timeoutInMs, responseCallback, errorCallback, false);
     }
 
     /**
diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServer.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServer.java
index 3a5ba27c..ce0e3673 100644
--- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServer.java
+++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServer.java
@@ -23,20 +23,17 @@ import org.eclipse.californium.core.network.CoapEndpoint;
 import org.eclipse.californium.core.network.Endpoint;
 import org.eclipse.californium.core.network.config.NetworkConfig;
 import org.eclipse.californium.core.server.resources.Resource;
-import org.eclipse.leshan.core.model.LwM2mModel;
-import org.eclipse.leshan.core.node.codec.LwM2mNodeDecoder;
-import org.eclipse.leshan.core.node.codec.LwM2mNodeEncoder;
-import org.eclipse.leshan.core.util.Validate;
 import org.eclipse.leshan.core.Destroyable;
 import org.eclipse.leshan.core.Startable;
 import org.eclipse.leshan.core.Stoppable;
-import org.eclipse.leshan.server.bootstrap.BootstrapConfigurationStore;
+import org.eclipse.leshan.core.node.codec.LwM2mNodeDecoder;
+import org.eclipse.leshan.core.node.codec.LwM2mNodeEncoder;
+import org.eclipse.leshan.core.util.Validate;
 import org.eclipse.leshan.server.bootstrap.BootstrapHandler;
 import org.eclipse.leshan.server.bootstrap.BootstrapHandlerFactory;
 import org.eclipse.leshan.server.bootstrap.BootstrapSessionManager;
 import org.eclipse.leshan.server.bootstrap.LwM2mBootstrapRequestSender;
 import org.eclipse.leshan.server.californium.RootResource;
-import org.eclipse.leshan.server.security.BootstrapSecurityStore;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -53,10 +50,6 @@ public class LeshanBootstrapServer {
     private final CoapEndpoint unsecuredEndpoint;
     private final CoapEndpoint securedEndpoint;
 
-    // LWM2M attributes
-    private final BootstrapConfigurationStore bsStore;
-    private final BootstrapSecurityStore bsSecurityStore;
-
     private LwM2mBootstrapRequestSender requestSender;
 
     /**
@@ -66,28 +59,20 @@ public class LeshanBootstrapServer {
      * 
      * @param unsecuredEndpoint CoAP endpoint used for <code>coap://</code> communication.
      * @param securedEndpoint CoAP endpoint used for <code>coaps://</code> communication.
-     * @param bsStore the store containing bootstrap configuration to apply during a bootstrap session.
-     * @param bsSecurityStore the store containing security information needed to authenticate a client.
      * @param bsSessionManager manages life cycle of a bootstrap process
      * @param bsHandlerFactory responsible to create the {@link BootstrapHandler}
-     * @param model the {@link LwM2mModel} used mainly to decode an encode LWM2M payload.
      * @param coapConfig the CoAP {@link NetworkConfig}.
      * @param encoder encode used to encode request payload.
      * @param decoder decoder used to decode response payload.
      */
     public LeshanBootstrapServer(CoapEndpoint unsecuredEndpoint, CoapEndpoint securedEndpoint,
-            BootstrapConfigurationStore bsStore, BootstrapSecurityStore bsSecurityStore,
-            BootstrapSessionManager bsSessionManager, BootstrapHandlerFactory bsHandlerFactory, LwM2mModel model,
+            BootstrapSessionManager bsSessionManager, BootstrapHandlerFactory bsHandlerFactory,
             NetworkConfig coapConfig, LwM2mNodeEncoder encoder, LwM2mNodeDecoder decoder) {
 
-        Validate.notNull(bsStore, "bootstrap store must not be null");
         Validate.notNull(bsSessionManager, "session manager must not be null");
         Validate.notNull(bsHandlerFactory, "BootstrapHandler factory must not be null");
-        Validate.notNull(model, "model must not be null");
         Validate.notNull(coapConfig, "coapConfig must not be null");
 
-        this.bsStore = bsStore;
-        this.bsSecurityStore = bsSecurityStore;
         this.coapApi = new CoapAPI();
 
         // init CoAP server
@@ -102,11 +87,10 @@ public class LeshanBootstrapServer {
             coapServer.addEndpoint(securedEndpoint);
 
         // create request sender
-        requestSender = createRequestSender(securedEndpoint, unsecuredEndpoint, model, encoder, decoder);
+        requestSender = createRequestSender(securedEndpoint, unsecuredEndpoint, encoder, decoder);
 
         // create bootstrap resource
-        CoapResource bsResource = createBootstrapResource(
-                bsHandlerFactory.create(bsStore, requestSender, bsSessionManager));
+        CoapResource bsResource = createBootstrapResource(bsHandlerFactory.create(requestSender, bsSessionManager));
         coapServer.add(bsResource);
     }
 
@@ -120,33 +104,14 @@ public class LeshanBootstrapServer {
     }
 
     protected LwM2mBootstrapRequestSender createRequestSender(Endpoint securedEndpoint, Endpoint unsecuredEndpoint,
-            LwM2mModel model, LwM2mNodeEncoder encoder, LwM2mNodeDecoder decoder) {
-        return new CaliforniumLwM2mBootstrapRequestSender(securedEndpoint, unsecuredEndpoint, model, encoder, decoder);
+            LwM2mNodeEncoder encoder, LwM2mNodeDecoder decoder) {
+        return new CaliforniumLwM2mBootstrapRequestSender(securedEndpoint, unsecuredEndpoint, encoder, decoder);
     }
 
     protected CoapResource createBootstrapResource(BootstrapHandler handler) {
         return new BootstrapResource(handler);
     }
 
-    /**
-     * Security store used for DTLS authentication on the bootstrap resource.
-     * 
-     * @return the {@link BootstrapSecurityStore} containing data used to authenticate devices.
-     */
-    public BootstrapSecurityStore getBootstrapSecurityStore() {
-        return bsSecurityStore;
-    }
-
-    /**
-     * Access to the bootstrap configuration store. It's used for sending configuration to the devices initiating a
-     * bootstrap.
-     * 
-     * @return the {@link BootstrapConfigurationStore} containing configuration to apply to each devices.
-     */
-    public BootstrapConfigurationStore getBoostrapStore() {
-        return bsStore;
-    }
-
     /**
      * Starts the server and binds it to the specified port.
      */
diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServerBuilder.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServerBuilder.java
index b95c3fd9..e937a296 100644
--- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServerBuilder.java
+++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServerBuilder.java
@@ -35,25 +35,25 @@ import org.eclipse.californium.scandium.dtls.x509.BridgeCertificateVerifier;
 import org.eclipse.leshan.core.LwM2m;
 import org.eclipse.leshan.core.californium.DefaultEndpointFactory;
 import org.eclipse.leshan.core.californium.EndpointFactory;
-import org.eclipse.leshan.core.model.LwM2mModel;
-import org.eclipse.leshan.core.model.ObjectLoader;
-import org.eclipse.leshan.core.model.StaticModel;
 import org.eclipse.leshan.core.node.LwM2mNode;
 import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeDecoder;
 import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeEncoder;
 import org.eclipse.leshan.core.node.codec.LwM2mNodeDecoder;
 import org.eclipse.leshan.core.node.codec.LwM2mNodeEncoder;
 import org.eclipse.leshan.server.bootstrap.BootstrapConfig;
-import org.eclipse.leshan.server.bootstrap.BootstrapConfigurationStore;
-import org.eclipse.leshan.server.bootstrap.BootstrapConfigurationStoreAdapter;
+import org.eclipse.leshan.server.bootstrap.BootstrapConfigStore;
+import org.eclipse.leshan.server.bootstrap.BootstrapConfigStoreTaskProvider;
 import org.eclipse.leshan.server.bootstrap.BootstrapHandler;
 import org.eclipse.leshan.server.bootstrap.BootstrapHandlerFactory;
 import org.eclipse.leshan.server.bootstrap.BootstrapSessionManager;
 import org.eclipse.leshan.server.bootstrap.DefaultBootstrapHandler;
 import org.eclipse.leshan.server.bootstrap.DefaultBootstrapSessionManager;
-import org.eclipse.leshan.server.bootstrap.InMemoryBootstrapConfigurationStore;
+import org.eclipse.leshan.server.bootstrap.InMemoryBootstrapConfigStore;
 import org.eclipse.leshan.server.bootstrap.LwM2mBootstrapRequestSender;
+import org.eclipse.leshan.server.model.LwM2mBootstrapModelProvider;
+import org.eclipse.leshan.server.model.StandardBootstrapModelProvider;
 import org.eclipse.leshan.server.security.BootstrapSecurityStore;
+import org.eclipse.leshan.server.security.SecurityChecker;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -69,12 +69,12 @@ public class LeshanBootstrapServerBuilder {
 
     private InetSocketAddress localAddress;
     private InetSocketAddress localAddressSecure;
-    private BootstrapConfigurationStore configStore;
+    private BootstrapConfigStore configStore;
     private BootstrapSecurityStore securityStore;
     private BootstrapSessionManager sessionManager;
     private BootstrapHandlerFactory bootstrapHandlerFactory;
 
-    private LwM2mModel model;
+    private LwM2mBootstrapModelProvider modelProvider;
     private NetworkConfig coapConfig;
     private Builder dtlsConfigBuilder;
 
@@ -220,18 +220,17 @@ public class LeshanBootstrapServerBuilder {
     }
 
     /**
-     * Set the {@link BootstrapConfigurationStore} containing bootstrap configuration to apply to each devices.
+     * Set the {@link BootstrapConfigStore} containing bootstrap configuration to apply to each devices.
      * <p>
-     * By default an {@link InMemoryBootstrapConfigurationStore} is used.
+     * By default an {@link InMemoryBootstrapConfigStore} is used.
      * <p>
      * See {@link BootstrapConfig} to see what is could be done during a bootstrap session.
      * 
      * @param configStore the bootstrap configuration store.
      * @return the builder for fluent Bootstrap Server creation.
      * 
-     * @see BootstrapConfigurationStoreAdapter
      */
-    public LeshanBootstrapServerBuilder setConfigStore(BootstrapConfigurationStore configStore) {
+    public LeshanBootstrapServerBuilder setConfigStore(BootstrapConfigStore configStore) {
         this.configStore = configStore;
         return this;
     }
@@ -280,18 +279,14 @@ public class LeshanBootstrapServerBuilder {
     }
 
     /**
-     * Advanced setter used to customize default {@link LwM2mModel}. This model is mainly used for data encoding of
-     * Bootstrap write request.
      * <p>
-     * By default, LWM2M object models defined in LWM2M v1.1.x are used.
-     * <p>
-     * WARNING: Only 1 version by object is supported for now.
+     * Set your {@link LwM2mBootstrapModelProvider} implementation.
+     * </p>
+     * By default the {@link StandardBootstrapModelProvider}.
      * 
-     * @param model the LWM2M Model.
-     * @return the builder for fluent Bootstrap Server creation.
      */
-    public LeshanBootstrapServerBuilder setModel(LwM2mModel model) {
-        this.model = model;
+    public LeshanBootstrapServerBuilder setObjectModelProvider(LwM2mBootstrapModelProvider objectModelProvider) {
+        this.modelProvider = objectModelProvider;
         return this;
     }
 
@@ -402,21 +397,29 @@ public class LeshanBootstrapServerBuilder {
     public LeshanBootstrapServer build() {
         if (localAddress == null)
             localAddress = new InetSocketAddress(LwM2m.DEFAULT_COAP_PORT);
-        if (configStore == null)
-            configStore = new InMemoryBootstrapConfigurationStore();
-
-        if (sessionManager == null)
-            sessionManager = new DefaultBootstrapSessionManager(securityStore);
         if (bootstrapHandlerFactory == null)
             bootstrapHandlerFactory = new BootstrapHandlerFactory() {
                 @Override
-                public BootstrapHandler create(BootstrapConfigurationStore store, LwM2mBootstrapRequestSender sender,
+                public BootstrapHandler create(LwM2mBootstrapRequestSender sender,
                         BootstrapSessionManager sessionManager) {
-                    return new DefaultBootstrapHandler(store, sender, sessionManager);
+                    return new DefaultBootstrapHandler(sender, sessionManager);
                 }
             };
-        if (model == null)
-            model = new StaticModel(ObjectLoader.loadDefault());
+        if (configStore == null) {
+            configStore = new InMemoryBootstrapConfigStore();
+        } else if (sessionManager != null) {
+            LOG.warn("configStore is set but you also provide a custom SessionManager so this store will not be used");
+        }
+        if (modelProvider == null) {
+            modelProvider = new StandardBootstrapModelProvider();
+        } else if (sessionManager != null) {
+            LOG.warn(
+                    "modelProvider is set but you also provide a custom SessionManager so this provider will not be used");
+        }
+        if (sessionManager == null) {
+            sessionManager = new DefaultBootstrapSessionManager(securityStore, new SecurityChecker(),
+                    new BootstrapConfigStoreTaskProvider(configStore), modelProvider);
+        }
         if (coapConfig == null) {
             coapConfig = createDefaultNetworkConfig();
         }
@@ -566,8 +569,8 @@ public class LeshanBootstrapServerBuilder {
                     "All CoAP enpoints are deactivated, at least one endpoint should be activated");
         }
 
-        return createBootstrapServer(unsecuredEndpoint, securedEndpoint, configStore, securityStore, sessionManager,
-                bootstrapHandlerFactory, model, coapConfig, encoder, decoder);
+        return createBootstrapServer(unsecuredEndpoint, securedEndpoint, sessionManager, bootstrapHandlerFactory,
+                coapConfig, encoder, decoder);
     }
 
     /**
@@ -586,21 +589,17 @@ public class LeshanBootstrapServerBuilder {
      * 
      * @param unsecuredEndpoint CoAP endpoint used for <code>coap://</code> communication.
      * @param securedEndpoint CoAP endpoint used for <code>coaps://</code> communication.
-     * @param bsStore the bootstrap configuration store.
-     * @param bsSecurityStore the security store used to authenticate devices.
      * @param bsSessionManager the manager responsible to handle bootstrap session.
      * @param bsHandlerFactory the factory used to create {@link BootstrapHandler}.
-     * @param model the LWM2M model used mainly used for data encoding.
      * @param coapConfig the CoAP configuration.
      * @param decoder decoder used to decode response payload.
      * @param encoder encode used to encode request payload.
      * @return the LWM2M Bootstrap server.
      */
     protected LeshanBootstrapServer createBootstrapServer(CoapEndpoint unsecuredEndpoint, CoapEndpoint securedEndpoint,
-            BootstrapConfigurationStore bsStore, BootstrapSecurityStore bsSecurityStore,
-            BootstrapSessionManager bsSessionManager, BootstrapHandlerFactory bsHandlerFactory, LwM2mModel model,
+            BootstrapSessionManager bsSessionManager, BootstrapHandlerFactory bsHandlerFactory,
             NetworkConfig coapConfig, LwM2mNodeEncoder encoder, LwM2mNodeDecoder decoder) {
-        return new LeshanBootstrapServer(unsecuredEndpoint, securedEndpoint, bsStore, bsSecurityStore, bsSessionManager,
-                bsHandlerFactory, model, coapConfig, encoder, decoder);
+        return new LeshanBootstrapServer(unsecuredEndpoint, securedEndpoint, bsSessionManager, bsHandlerFactory,
+                coapConfig, encoder, decoder);
     }
 }
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapConfig.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapConfig.java
index 2eeec029..81d35792 100644
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapConfig.java
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapConfig.java
@@ -15,7 +15,6 @@
  *******************************************************************************/
 package org.eclipse.leshan.server.bootstrap;
 
-import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.EnumSet;
@@ -42,9 +41,7 @@ import org.eclipse.leshan.core.request.BindingMode;
  * @see BootstrapConfigStore
  * @see DefaultBootstrapHandler
  */
-public class BootstrapConfig implements Serializable {
-
-    private static final long serialVersionUID = 1L;
+public class BootstrapConfig {
 
     /**
      * List of LWM2M path to delete.
@@ -67,8 +64,7 @@ public class BootstrapConfig implements Serializable {
     public Map<Integer, ACLConfig> acls = new HashMap<>();
 
     /** Server Configuration (object 1) as defined in LWM2M 1.0.x TS. */
-    public static class ServerConfig implements Serializable {
-        private static final long serialVersionUID = 1L;
+    public static class ServerConfig {
 
         /** Used as link to associate server Object Instance. */
         public int shortId;
@@ -123,8 +119,7 @@ public class BootstrapConfig implements Serializable {
      * One Object Instance SHOULD address a LwM2M Bootstrap-Server. These LwM2M Object Resources MUST only be changed by
      * a LwM2M Bootstrap-Server or Bootstrap from Smartcard and MUST NOT be accessible by any other LwM2M Server.
      */
-    public static class ServerSecurity implements Serializable {
-        private static final long serialVersionUID = 1L;
+    public static class ServerSecurity {
 
         /**
          * Uniquely identifies the LwM2M Server or LwM2M Bootstrap-Server. The format of the CoAP URI is defined in
@@ -252,8 +247,7 @@ public class BootstrapConfig implements Serializable {
      * <p>
      * Access Control Object is used to check whether the LwM2M Server has access right for performing an operation.
      */
-    public static class ACLConfig implements Serializable {
-        private static final long serialVersionUID = 1L;
+    public static class ACLConfig {
 
         /** The Object ID of the Object Instance for which ACL are applied. */
         public int objectId;
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapConfigStore.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapConfigStore.java
index c22cd2b9..6d64beaf 100644
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapConfigStore.java
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapConfigStore.java
@@ -20,10 +20,7 @@ import org.eclipse.leshan.core.request.Identity;
 /**
  * A store containing the bootstrap information to be sent to the devices.
  * 
- * @deprecated use {@link BootstrapConfigurationStore} instead or see {@link BootstrapConfigurationStoreAdapter} or
- *             {@link BootstrapUtil#toRequests(BootstrapConfig, org.eclipse.leshan.core.request.ContentFormat)}
  */
-@Deprecated
 public interface BootstrapConfigStore {
 
     /**
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapConfigStoreTaskProvider.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapConfigStoreTaskProvider.java
new file mode 100644
index 00000000..608bef4d
--- /dev/null
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapConfigStoreTaskProvider.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * Copyright (c) 2021 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.server.bootstrap;
+
+import java.util.HashMap;
+import java.util.List;
+
+import org.eclipse.leshan.core.response.LwM2mResponse;
+
+/**
+ * An implementation of {@link BootstrapTaskProvider} which use a {@link BootstrapConfigStore} to know which requests to
+ * send during a {@link BootstrapSession}.
+ */
+public class BootstrapConfigStoreTaskProvider implements BootstrapTaskProvider {
+
+    private BootstrapConfigStore store;
+
+    public BootstrapConfigStoreTaskProvider(BootstrapConfigStore store) {
+        this.store = store;
+    }
+
+    @Override
+    public Tasks getTasks(BootstrapSession session, List<LwM2mResponse> previousResponse) {
+
+        BootstrapConfig config = store.get(session.getEndpoint(), session.getIdentity(), session);
+        if (config == null)
+            return null;
+
+        Tasks tasks = new Tasks();
+        // create requests from config
+        tasks.requestsToSend = BootstrapUtil.toRequests(config, session.getContentFormat());
+
+        // We add model for Security(0), Server(0) and ACL(2) which are the only one supported by BootstrapConfig
+        // We use default 1.0 model as currently BootstrapConfig support only this model version (which should be
+        // compatible with models of LWM2M v1.1 but without new resources)
+        tasks.supportedObjects = new HashMap<>();
+        tasks.supportedObjects.put(0, "1.0");
+        tasks.supportedObjects.put(1, "1.0");
+        tasks.supportedObjects.put(2, "1.0");
+
+        return tasks;
+    }
+}
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapConfiguration.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapConfiguration.java
deleted file mode 100644
index 9764e9a6..00000000
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapConfiguration.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2020 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.server.bootstrap;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-import org.eclipse.leshan.core.request.BootstrapDownlinkRequest;
-import org.eclipse.leshan.core.response.LwM2mResponse;
-
-public class BootstrapConfiguration {
-
-    private List<BootstrapDownlinkRequest<? extends LwM2mResponse>> requests;
-
-    public BootstrapConfiguration() {
-        this.requests = Collections.emptyList();
-    }
-
-    @SafeVarargs
-    public BootstrapConfiguration(BootstrapDownlinkRequest<? extends LwM2mResponse>... requests) {
-        this.requests = Arrays.asList(requests);
-    }
-
-    public BootstrapConfiguration(List<BootstrapDownlinkRequest<? extends LwM2mResponse>> requests) {
-        this.requests = requests;
-    }
-
-    public List<BootstrapDownlinkRequest<? extends LwM2mResponse>> getRequests() {
-        return requests;
-    }
-}
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapConfigurationStore.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapConfigurationStore.java
deleted file mode 100644
index 2093fc7b..00000000
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapConfigurationStore.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2020 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.server.bootstrap;
-
-import org.eclipse.leshan.core.request.Identity;
-
-/**
- * A store containing the bootstrap information to be sent to the devices.
- */
-public interface BootstrapConfigurationStore {
-
-    /**
-     * Get the bootstrap configuration to apply to the device identified by the given parameters.
-     * 
-     * @param endpoint the endpoint of the device.
-     * @param deviceIdentity the {@link Identity} the device.
-     * @param session the current {@link BootstrapSession}.
-     * @return the {@link BootstrapConfig} to apply.
-     */
-    BootstrapConfiguration get(String endpoint, Identity deviceIdentity, BootstrapSession session);
-}
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapConfigurationStoreAdapter.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapConfigurationStoreAdapter.java
deleted file mode 100644
index 563fa81a..00000000
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapConfigurationStoreAdapter.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2020 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.server.bootstrap;
-
-import org.eclipse.leshan.core.request.Identity;
-
-/**
- * It allows to adapt the deprecated {@link BootstrapConfigStore} to the new API {@link BootstrapConfigurationStore}.
- * You can use it to adapt but it is clearly recommanded to rather directly implements
- * {@link BootstrapConfigurationStore} and maybe used {@link BootstrapUtil} if you still want to handle
- * {@link BootstrapConfig}.
- */
-@SuppressWarnings("deprecation")
-public class BootstrapConfigurationStoreAdapter implements BootstrapConfigurationStore {
-
-    private BootstrapConfigStore internalStore;
-
-    public BootstrapConfigurationStoreAdapter(BootstrapConfigStore store) {
-        this.internalStore = store;
-    }
-
-    @Override
-    public BootstrapConfiguration get(String endpoint, Identity deviceIdentity, BootstrapSession session) {
-        BootstrapConfig bootstrapConfig = internalStore.get(endpoint, deviceIdentity, session);
-        if (bootstrapConfig == null)
-            return null;
-
-        return new BootstrapConfiguration(BootstrapUtil.toRequests(bootstrapConfig, session.getContentFormat()));
-    }
-}
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapHandler.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapHandler.java
index f5c63f1d..eb3775ec 100644
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapHandler.java
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapHandler.java
@@ -22,8 +22,9 @@ import org.eclipse.leshan.core.response.SendableResponse;
 
 /**
  * Handle the bootstrap logic at Server side. Check if the client is allowed to bootstrap, with the wanted security
- * scheme. Then send delete and write request to bootstrap the client, then close the bootstrap session by sending a
- * bootstrap finished request.
+ * scheme. Then send requests to the client, then close the bootstrap session by sending a bootstrap finished request.
+ * 
+ * @see DefaultBootstrapHandler
  */
 public interface BootstrapHandler {
 
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapHandlerFactory.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapHandlerFactory.java
index 4cf9aace..da38072e 100644
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapHandlerFactory.java
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapHandlerFactory.java
@@ -25,11 +25,9 @@ public interface BootstrapHandlerFactory {
     /**
      * Creates {@link BootstrapHandler}.
      * 
-     * @param store the store containing bootstrap configuration.
      * @param sender the class responsible to send LWM2M request during a bootstapSession.
      * @param sessionManager the manager responsible to handle bootstrap session.
      * @return the new {@link BootstrapHandler}.
      */
-    BootstrapHandler create(BootstrapConfigurationStore store, LwM2mBootstrapRequestSender sender,
-            BootstrapSessionManager sessionManager);
+    BootstrapHandler create(LwM2mBootstrapRequestSender sender, BootstrapSessionManager sessionManager);
 }
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapSession.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapSession.java
index 54d4fbdf..7fae9308 100644
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapSession.java
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapSession.java
@@ -15,6 +15,7 @@
  *******************************************************************************/
 package org.eclipse.leshan.server.bootstrap;
 
+import org.eclipse.leshan.core.model.LwM2mModel;
 import org.eclipse.leshan.core.request.BootstrapRequest;
 import org.eclipse.leshan.core.request.ContentFormat;
 import org.eclipse.leshan.core.request.Identity;
@@ -72,4 +73,9 @@ public interface BootstrapSession {
      * @return True if this session was cancellled
      */
     boolean isCancelled();
+
+    /**
+     * @return objects model supported by the client for this session.
+     */
+    LwM2mModel getModel();
 }
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapSessionManager.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapSessionManager.java
index 219a914f..adf4d3f7 100644
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapSessionManager.java
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapSessionManager.java
@@ -19,39 +19,76 @@ import org.eclipse.leshan.core.request.BootstrapDownlinkRequest;
 import org.eclipse.leshan.core.request.BootstrapRequest;
 import org.eclipse.leshan.core.request.Identity;
 import org.eclipse.leshan.core.response.LwM2mResponse;
+import org.eclipse.leshan.core.util.Validate;
 
 /**
  * Manages life cycle of a bootstrap process.
  * <p>
- * This class is responsible to accept or refuse to start new {@link BootstrapSession}. The session also contain the
- * ContentFormat which will be used to send Bootstrap Write Request.
+ * This class is responsible to accept or refuse to start new {@link BootstrapSession}, then to provide request to send.
+ * It also decide when session should continue, finished or failed.
  * 
  * @see DefaultBootstrapSessionManager
  * @see BootstrapSession
  */
 public interface BootstrapSessionManager {
 
-    public enum BootstrapPolicy {
-        /**
-         * Continue bootstrap session ignoring failure.
-         */
-        CONTINUE,
-        /**
-         * Try to resend the failed request.
-         */
-        RETRY,
+    /**
+     * Define if the session should continue, finished or failed.
+     * <p>
+     * If the session continue, it also define which request must be send next.
+     */
+    public class BootstrapPolicy {
+
+        private final boolean failed;
+        private final BootstrapDownlinkRequest<? extends LwM2mResponse> nextRequest;
+
+        protected BootstrapPolicy(boolean stop, BootstrapDownlinkRequest<? extends LwM2mResponse> nextRequest) {
+            this.failed = stop;
+            this.nextRequest = nextRequest;
+        }
+
         /**
-         * Retry the bootstrap session from the beginning generally.
+         * @return true if the session should continue with another request to send
          */
-        RETRYALL,
+        public boolean shouldContinue() {
+            return !failed && nextRequest != null;
+        }
+
         /**
-         * Stop to try to write bootstrap config and directly send a "Bootstrap Finished" request.
+         * @return true if the session should stop with success
          */
-        SEND_FINISHED,
+        public boolean shouldFinish() {
+            return !failed && nextRequest == null;
+        }
+
         /**
-         * Stop the bootstrap session. The session is considered as failure.
+         * @return true if session should stop with failure
          */
-        STOP
+        public boolean shouldfail() {
+            return failed;
+        }
+
+        public BootstrapDownlinkRequest<? extends LwM2mResponse> nextRequest() {
+            return nextRequest;
+        }
+
+        public static BootstrapPolicy continueWith(BootstrapDownlinkRequest<? extends LwM2mResponse> nextRequest) {
+            Validate.notNull(nextRequest);
+            return new BootstrapPolicy(false, nextRequest);
+        }
+
+        public static BootstrapPolicy finished() {
+            return new BootstrapPolicy(false, null);
+        }
+
+        public static BootstrapPolicy failed() {
+            return new BootstrapPolicy(true, null);
+        }
+
+        @Override
+        public String toString() {
+            return String.format("BootstrapPolicy [failed=%s, nextRequest=%s]", failed, nextRequest);
+        }
     }
 
     /**
@@ -65,14 +102,33 @@ public interface BootstrapSessionManager {
      */
     public BootstrapSession begin(BootstrapRequest request, Identity clientIdentity);
 
+    /**
+     * Generally called after {@link #begin(BootstrapRequest, Identity)} to know if there is something to do on this
+     * device.
+     * 
+     * @param bsSession the bootstrap session concerned.
+     * @return true if there is a bootstrap requests to send for this client.
+     */
+    public boolean hasConfigFor(BootstrapSession bsSession);
+
+    /**
+     * Generally called after {@link #hasConfigFor(BootstrapSession)} to know the first request to send.
+     * 
+     * @param bsSession the bootstrap session concerned.
+     * @return the first request to send.
+     */
+    public BootstrapDownlinkRequest<? extends LwM2mResponse> getFirstRequest(BootstrapSession bsSession);
+
     /**
      * Called when we receive a successful response to a request.
      * 
      * @param bsSession the bootstrap session concerned.
-     * @param request The request for which we get a successfull response.
+     * @param request The request for which we get a successful response.
+     * @param response The response received.
+     * @return a {@link BootstrapPolicy} given the way to continue the bootstrap session.
      */
-    public void onResponseSuccess(BootstrapSession bsSession,
-            BootstrapDownlinkRequest<? extends LwM2mResponse> request);
+    public BootstrapPolicy onResponseSuccess(BootstrapSession bsSession,
+            BootstrapDownlinkRequest<? extends LwM2mResponse> request, LwM2mResponse response);
 
     /**
      * Called when we receive a error response to a request.
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapTaskProvider.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapTaskProvider.java
new file mode 100644
index 00000000..fd3b6c9a
--- /dev/null
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapTaskProvider.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * Copyright (c) 2021 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.server.bootstrap;
+
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.leshan.core.model.LwM2mModel;
+import org.eclipse.leshan.core.request.BootstrapDiscoverRequest;
+import org.eclipse.leshan.core.request.BootstrapDownlinkRequest;
+import org.eclipse.leshan.core.request.BootstrapWriteRequest;
+import org.eclipse.leshan.core.response.LwM2mResponse;
+
+/**
+ * A class responsible to return tasks to do during a {@link BootstrapSession}
+ * <p>
+ * This class is used by {@link DefaultBootstrapSessionManager}.
+ * <p>
+ * {@link #getTasks(BootstrapSession, List)} must return requests to send to the client. During 1 session,
+ * {@link #getTasks(BootstrapSession, List)} can be called several time. Responses received for first batch of requests
+ * can be used to determine next request to send. E.g. first batch of Requests could be a
+ * {@link BootstrapDiscoverRequest} or a BootstrapReadRequest and response can be used to determine which
+ * {@link BootstrapWriteRequest} to Send.
+ * 
+ * @see BootstrapConfigStoreTaskProvider
+ */
+public interface BootstrapTaskProvider {
+
+    /**
+     * a batch of requests to send.
+     *
+     */
+    public class Tasks {
+        /**
+         * the list of request to send
+         */
+        public List<BootstrapDownlinkRequest<? extends LwM2mResponse>> requestsToSend;
+
+        /**
+         * Object (with version) supported by the client. {@link LwM2mModel} needed to encode/decode payload of request
+         * will be created from this data
+         */
+        public Map<Integer, String> supportedObjects;
+
+        /**
+         * if true {@link BootstrapTaskProvider#getTasks(BootstrapSession, List)} will not be called again for this
+         * session
+         */
+        public boolean last = true;
+    }
+
+    /**
+     * @param previousResponses a list of {@link LwM2mResponse} received from previous executed {@link Tasks}. It can be
+     *        <code>null</code> if this is the first call for this session.
+     * @return next tasks to do (next requests to send), returning <code>null</code> means there is nothing to do with
+     *         this client.
+     */
+    Tasks getTasks(BootstrapSession session, List<LwM2mResponse> previousResponses);
+}
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/DefaultBootstrapHandler.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/DefaultBootstrapHandler.java
index ed46b20f..22aef263 100644
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/DefaultBootstrapHandler.java
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/DefaultBootstrapHandler.java
@@ -17,15 +17,12 @@ package org.eclipse.leshan.server.bootstrap;
 
 import static org.eclipse.leshan.server.bootstrap.BootstrapFailureCause.*;
 
-import java.util.ArrayList;
-import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
 
 import org.eclipse.leshan.core.request.BootstrapDownlinkRequest;
 import org.eclipse.leshan.core.request.BootstrapFinishRequest;
 import org.eclipse.leshan.core.request.BootstrapRequest;
 import org.eclipse.leshan.core.request.Identity;
-import org.eclipse.leshan.core.response.BootstrapFinishResponse;
 import org.eclipse.leshan.core.response.BootstrapResponse;
 import org.eclipse.leshan.core.response.ErrorCallback;
 import org.eclipse.leshan.core.response.LwM2mResponse;
@@ -37,6 +34,13 @@ import org.slf4j.LoggerFactory;
 
 /**
  * A default implementation for {@link BootstrapHandler}.
+ * <p>
+ * It ensure there isn't 2 bootstrap session at the same time for a given client. If this happens the old one was stop
+ * and ongoing request are cancelled.
+ * <p>
+ * It also ensure that we send only one request at a time for a given client.
+ * <p>
+ * All the logic for a given session is delegate to a the {@link BootstrapSessionManager}.
  */
 public class DefaultBootstrapHandler implements BootstrapHandler {
 
@@ -46,34 +50,18 @@ public class DefaultBootstrapHandler implements BootstrapHandler {
     // send a Confirmable message to the time when an acknowledgement is no longer expected.
     public static final long DEFAULT_TIMEOUT = 2 * 60 * 1000l; // 2min in ms
 
-    protected final BootstrapConfigurationStore store;
-
     protected final LwM2mBootstrapRequestSender sender;
     protected final long requestTimeout;
 
     protected final ConcurrentHashMap<String, BootstrapSession> onGoingSession = new ConcurrentHashMap<>();
     protected final BootstrapSessionManager sessionManager;
 
-    @Deprecated
-    public DefaultBootstrapHandler(BootstrapConfigStore store, LwM2mBootstrapRequestSender sender,
-            BootstrapSessionManager sessionManager) {
-        this(new BootstrapConfigurationStoreAdapter(store), sender, sessionManager, DEFAULT_TIMEOUT);
-    }
-
-    @Deprecated
-    public DefaultBootstrapHandler(BootstrapConfigStore store, LwM2mBootstrapRequestSender sender,
-            BootstrapSessionManager sessionManager, long requestTimeout) {
-        this(new BootstrapConfigurationStoreAdapter(store), sender, sessionManager, requestTimeout);
+    public DefaultBootstrapHandler(LwM2mBootstrapRequestSender sender, BootstrapSessionManager sessionManager) {
+        this(sender, sessionManager, DEFAULT_TIMEOUT);
     }
 
-    public DefaultBootstrapHandler(BootstrapConfigurationStore store, LwM2mBootstrapRequestSender sender,
-            BootstrapSessionManager sessionManager) {
-        this(store, sender, sessionManager, DEFAULT_TIMEOUT);
-    }
-
-    public DefaultBootstrapHandler(BootstrapConfigurationStore store, LwM2mBootstrapRequestSender sender,
-            BootstrapSessionManager sessionManager, long requestTimeout) {
-        this.store = store;
+    public DefaultBootstrapHandler(LwM2mBootstrapRequestSender sender, BootstrapSessionManager sessionManager,
+            long requestTimeout) {
         this.sender = sender;
         this.sessionManager = sessionManager;
         this.requestTimeout = requestTimeout;
@@ -103,9 +91,8 @@ public class DefaultBootstrapHandler implements BootstrapHandler {
         }
 
         try {
-            // Get the desired bootstrap config for the endpoint
-            final BootstrapConfiguration cfg = store.get(endpoint, sender, session);
-            if (cfg == null) {
+            // check if there is a configuration to apply for this device
+            if (!sessionManager.hasConfigFor(session)) {
                 LOG.debug("No bootstrap config for {}", session);
                 stopSession(session, NO_BOOTSTRAP_CONFIG);
                 return new SendableResponse<>(BootstrapResponse.badRequest("no bootstrap config"));
@@ -115,7 +102,7 @@ public class DefaultBootstrapHandler implements BootstrapHandler {
             Runnable onSent = new Runnable() {
                 @Override
                 public void run() {
-                    startBootstrap(session, cfg);
+                    startBootstrap(session);
                 }
             };
             return new SendableResponse<>(BootstrapResponse.success(), onSent);
@@ -127,8 +114,8 @@ public class DefaultBootstrapHandler implements BootstrapHandler {
         }
     }
 
-    protected void startBootstrap(BootstrapSession session, BootstrapConfiguration cfg) {
-        sendRequest(session, cfg, new ArrayList<>(cfg.getRequests()));
+    protected void startBootstrap(BootstrapSession session) {
+        sendRequest(session, sessionManager.getFirstRequest(session));
     }
 
     protected void stopSession(BootstrapSession session, BootstrapFailureCause cause) {
@@ -146,116 +133,49 @@ public class DefaultBootstrapHandler implements BootstrapHandler {
     }
 
     @SuppressWarnings({ "unchecked", "rawtypes" })
-    protected void sendRequest(final BootstrapSession session, final BootstrapConfiguration cfg,
-            final List<BootstrapDownlinkRequest<? extends LwM2mResponse>> requestToSend) {
-        if (!requestToSend.isEmpty()) {
-            // get next request
-            final BootstrapDownlinkRequest request = requestToSend.get(0);
-
-            send(session, request, new SafeResponseCallback(session) {
-                @Override
-                public void safeOnResponse(LwM2mResponse response) {
-                    if (response.isSuccess()) {
-                        LOG.trace("{} receives {} for {}", session, response, request);
-                        sessionManager.onResponseSuccess(session, request);
-                        afterRequest(session, cfg, requestToSend, BootstrapPolicy.CONTINUE);
-                    } else {
-                        LOG.debug("{} receives {} for {}", session, response, request);
-                        BootstrapPolicy policy = sessionManager.onResponseError(session, request, response);
-                        afterRequest(session, cfg, requestToSend, policy);
-                    }
-                }
-            }, new SafeErrorCallback(session) {
-                @Override
-                public void safeOnError(Exception e) {
-                    LOG.debug("Error for {} while sending {} ", session, request, e);
-                    BootstrapPolicy policy = sessionManager.onRequestFailure(session, request, e);
-                    afterRequest(session, cfg, requestToSend, policy);
-                }
-            });
-        } else {
-            // we are done, send bootstrap finished.
-            bootstrapFinished(session, cfg);
-        }
-    }
+    protected void sendRequest(final BootstrapSession session,
+            final BootstrapDownlinkRequest<? extends LwM2mResponse> requestToSend) {
 
-    protected void afterRequest(BootstrapSession session, BootstrapConfiguration cfg,
-            final List<BootstrapDownlinkRequest<? extends LwM2mResponse>> requestToSend, BootstrapPolicy policy) {
-        if (session.isCancelled()) {
-            stopSession(session, CANCELLED);
-            return;
-        }
-        switch (policy) {
-        case CONTINUE:
-            requestToSend.remove(0);
-            sendRequest(session, cfg, requestToSend);
-            break;
-        case RETRY:
-            sendRequest(session, cfg, requestToSend);
-            break;
-        case RETRYALL:
-            startBootstrap(session, cfg);
-            break;
-        case SEND_FINISHED:
-            bootstrapFinished(session, cfg);
-            break;
-        case STOP:
-            stopSession(session, REQUEST_FAILED);
-            break;
-        default:
-            throw new IllegalStateException("unknown policy :" + policy);
-        }
-    }
-
-    protected void bootstrapFinished(final BootstrapSession session, final BootstrapConfiguration cfg) {
-
-        final BootstrapFinishRequest finishBootstrapRequest = new BootstrapFinishRequest();
-        send(session, finishBootstrapRequest, new SafeResponseCallback<BootstrapFinishResponse>(session) {
+        send(session, requestToSend, new SafeResponseCallback(session) {
             @Override
-            public void safeOnResponse(BootstrapFinishResponse response) {
+            public void safeOnResponse(LwM2mResponse response) {
                 if (response.isSuccess()) {
-                    LOG.trace("{} receives {} for {}", session, response, finishBootstrapRequest);
-                    sessionManager.onResponseSuccess(session, finishBootstrapRequest);
-                    afterBootstrapFinished(session, cfg, BootstrapPolicy.CONTINUE);
+                    LOG.trace("{} receives {} for {}", session, response, requestToSend);
+                    BootstrapPolicy policy = sessionManager.onResponseSuccess(session, requestToSend, response);
+                    afterRequest(session, policy, requestToSend);
                 } else {
-                    LOG.debug("{} receives {} for {}", session, response, finishBootstrapRequest);
-                    BootstrapPolicy policy = sessionManager.onResponseError(session, finishBootstrapRequest, response);
-                    afterBootstrapFinished(session, cfg, policy);
+                    LOG.debug("{} receives {} for {}", session, response, requestToSend);
+                    BootstrapPolicy policy = sessionManager.onResponseError(session, requestToSend, response);
+                    afterRequest(session, policy, requestToSend);
                 }
             }
         }, new SafeErrorCallback(session) {
             @Override
             public void safeOnError(Exception e) {
-                LOG.debug("Error for {} while sending {} ", session, finishBootstrapRequest, e);
-                BootstrapPolicy policy = sessionManager.onRequestFailure(session, finishBootstrapRequest, e);
-                afterBootstrapFinished(session, cfg, policy);
+                LOG.debug("Error for {} while sending {} ", session, requestToSend, e);
+                BootstrapPolicy policy = sessionManager.onRequestFailure(session, requestToSend, e);
+                afterRequest(session, policy, requestToSend);
             }
         });
     }
 
-    protected void afterBootstrapFinished(BootstrapSession session, BootstrapConfiguration cfg,
-            BootstrapPolicy policy) {
+    protected void afterRequest(BootstrapSession session, BootstrapPolicy policy,
+            BootstrapDownlinkRequest<? extends LwM2mResponse> requestSent) {
         if (session.isCancelled()) {
             stopSession(session, CANCELLED);
             return;
         }
-        switch (policy) {
-        case CONTINUE:
+        if (policy.shouldContinue()) {
+            sendRequest(session, policy.nextRequest());
+        } else if (policy.shouldfail()) {
+            if (requestSent instanceof BootstrapFinishRequest) {
+                stopSession(session, FINISH_FAILED);
+            } else {
+                stopSession(session, REQUEST_FAILED);
+            }
+        } else if (policy.shouldFinish()) {
             stopSession(session, null);
-            break;
-        case RETRY:
-            bootstrapFinished(session, cfg);
-            break;
-        case RETRYALL:
-            startBootstrap(session, cfg);
-            break;
-        case SEND_FINISHED:
-            bootstrapFinished(session, cfg);
-            break;
-        case STOP:
-            stopSession(session, FINISH_FAILED);
-            break;
-        default:
+        } else {
             throw new IllegalStateException("unknown policy :" + policy);
         }
     }
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/DefaultBootstrapSession.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/DefaultBootstrapSession.java
index 7a2104bc..a5ab39c4 100644
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/DefaultBootstrapSession.java
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/DefaultBootstrapSession.java
@@ -15,9 +15,14 @@
  *******************************************************************************/
 package org.eclipse.leshan.server.bootstrap;
 
+import java.util.List;
+
+import org.eclipse.leshan.core.model.LwM2mModel;
+import org.eclipse.leshan.core.request.BootstrapDownlinkRequest;
 import org.eclipse.leshan.core.request.BootstrapRequest;
 import org.eclipse.leshan.core.request.ContentFormat;
 import org.eclipse.leshan.core.request.Identity;
+import org.eclipse.leshan.core.response.LwM2mResponse;
 import org.eclipse.leshan.core.util.RandomStringUtils;
 import org.eclipse.leshan.core.util.Validate;
 
@@ -34,6 +39,10 @@ public class DefaultBootstrapSession implements BootstrapSession {
     private final long creationTime;
     private final BootstrapRequest request;
 
+    private volatile LwM2mModel model;
+    private volatile List<BootstrapDownlinkRequest<? extends LwM2mResponse>> requests;
+    private volatile List<LwM2mResponse> responses;
+    private volatile boolean moreTasks = false;
     private volatile boolean cancelled = false;
 
     /**
@@ -132,6 +141,39 @@ public class DefaultBootstrapSession implements BootstrapSession {
         return request;
     }
 
+    public List<BootstrapDownlinkRequest<? extends LwM2mResponse>> getRequests() {
+        return requests;
+    }
+
+    public void setRequests(List<BootstrapDownlinkRequest<? extends LwM2mResponse>> requests) {
+        this.requests = requests;
+    }
+
+    public List<LwM2mResponse> getResponses() {
+        return responses;
+    }
+
+    public void setResponses(List<LwM2mResponse> responses) {
+        this.responses = responses;
+    }
+
+    @Override
+    public LwM2mModel getModel() {
+        return model;
+    }
+
+    public void setModel(LwM2mModel model) {
+        this.model = model;
+    }
+
+    public void setMoreTasks(boolean moreTasks) {
+        this.moreTasks = moreTasks;
+    }
+
+    public boolean hasMoreTasks() {
+        return moreTasks;
+    }
+
     @Override
     public void cancel() {
         cancelled = true;
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/DefaultBootstrapSessionManager.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/DefaultBootstrapSessionManager.java
index 9b157706..44461047 100644
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/DefaultBootstrapSessionManager.java
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/DefaultBootstrapSessionManager.java
@@ -15,13 +15,19 @@
  *******************************************************************************/
 package org.eclipse.leshan.server.bootstrap;
 
+import java.util.ArrayList;
 import java.util.Iterator;
+import java.util.List;
 
 import org.eclipse.leshan.core.request.BootstrapDownlinkRequest;
 import org.eclipse.leshan.core.request.BootstrapFinishRequest;
 import org.eclipse.leshan.core.request.BootstrapRequest;
 import org.eclipse.leshan.core.request.Identity;
 import org.eclipse.leshan.core.response.LwM2mResponse;
+import org.eclipse.leshan.core.util.Validate;
+import org.eclipse.leshan.server.bootstrap.BootstrapTaskProvider.Tasks;
+import org.eclipse.leshan.server.model.LwM2mBootstrapModelProvider;
+import org.eclipse.leshan.server.model.StandardBootstrapModelProvider;
 import org.eclipse.leshan.server.security.BootstrapSecurityStore;
 import org.eclipse.leshan.server.security.SecurityChecker;
 import org.eclipse.leshan.server.security.SecurityInfo;
@@ -29,7 +35,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * Implementation of a session manager.
+ * Default implementation of a {@link BootstrapSessionManager}.
  * <p>
  * Starting a session only checks credentials from BootstrapSecurityStore.
  * <p>
@@ -41,6 +47,8 @@ public class DefaultBootstrapSessionManager implements BootstrapSessionManager {
 
     private BootstrapSecurityStore bsSecurityStore;
     private SecurityChecker securityChecker;
+    private BootstrapTaskProvider tasksProvider;
+    private LwM2mBootstrapModelProvider modelProvider;
 
     /**
      * Create a {@link DefaultBootstrapSessionManager} using a default {@link SecurityChecker} to accept or refuse new
@@ -48,8 +56,9 @@ public class DefaultBootstrapSessionManager implements BootstrapSessionManager {
      * 
      * @param bsSecurityStore the {@link BootstrapSecurityStore} used by default {@link SecurityChecker}.
      */
-    public DefaultBootstrapSessionManager(BootstrapSecurityStore bsSecurityStore) {
-        this(bsSecurityStore, new SecurityChecker());
+    public DefaultBootstrapSessionManager(BootstrapSecurityStore bsSecurityStore, BootstrapConfigStore configStore) {
+        this(bsSecurityStore, new SecurityChecker(), new BootstrapConfigStoreTaskProvider(configStore),
+                new StandardBootstrapModelProvider());
     }
 
     /**
@@ -58,15 +67,20 @@ public class DefaultBootstrapSessionManager implements BootstrapSessionManager {
      * @param bsSecurityStore the {@link BootstrapSecurityStore} used by {@link SecurityChecker}.
      * @param securityChecker used to accept or refuse new {@link BootstrapSession}.
      */
-    public DefaultBootstrapSessionManager(BootstrapSecurityStore bsSecurityStore, SecurityChecker securityChecker) {
+    public DefaultBootstrapSessionManager(BootstrapSecurityStore bsSecurityStore, SecurityChecker securityChecker,
+            BootstrapTaskProvider tasksProvider, LwM2mBootstrapModelProvider modelProvider) {
+        Validate.notNull(tasksProvider);
+        Validate.notNull(modelProvider);
         this.bsSecurityStore = bsSecurityStore;
         this.securityChecker = securityChecker;
+        this.tasksProvider = tasksProvider;
+        this.modelProvider = modelProvider;
     }
 
     @Override
     public BootstrapSession begin(BootstrapRequest request, Identity clientIdentity) {
         boolean authorized;
-        if (bsSecurityStore != null) {
+        if (bsSecurityStore != null && securityChecker != null) {
             Iterator<SecurityInfo> securityInfos = bsSecurityStore.getAllByEndpoint(request.getEndpointName());
             authorized = securityChecker.checkSecurityInfos(request.getEndpointName(), clientIdentity, securityInfos);
         } else {
@@ -74,25 +88,81 @@ public class DefaultBootstrapSessionManager implements BootstrapSessionManager {
         }
         DefaultBootstrapSession session = new DefaultBootstrapSession(request, clientIdentity, authorized);
         LOG.trace("Bootstrap session started : {}", session);
+
         return session;
     }
 
     @Override
-    public void end(BootstrapSession bsSession) {
-        LOG.trace("Bootstrap session finished : {}", bsSession);
+    public boolean hasConfigFor(BootstrapSession session) {
+        Tasks firstTasks = tasksProvider.getTasks(session, null);
+        if (firstTasks == null)
+            return false;
+
+        initTasks(session, firstTasks);
+        return true;
+    }
+
+    protected void initTasks(BootstrapSession bssession, Tasks tasks) {
+        DefaultBootstrapSession session = (DefaultBootstrapSession) bssession;
+        // set models
+        if (tasks.supportedObjects != null)
+            session.setModel(modelProvider.getObjectModel(session, tasks.supportedObjects));
+
+        // set Requests to Send
+        session.setRequests(tasks.requestsToSend);
+
+        // prepare list where we will store Responses
+        session.setResponses(new ArrayList<LwM2mResponse>(tasks.requestsToSend.size()));
+
+        // is last Tasks ?
+        session.setMoreTasks(!tasks.last);
     }
 
     @Override
-    public void failed(BootstrapSession bsSession, BootstrapFailureCause cause) {
-        LOG.trace("Bootstrap session failed by {}: {}", cause, bsSession);
+    public BootstrapDownlinkRequest<? extends LwM2mResponse> getFirstRequest(BootstrapSession bsSession) {
+        return nextRequest(bsSession);
+    }
+
+    protected BootstrapDownlinkRequest<? extends LwM2mResponse> nextRequest(BootstrapSession bsSession) {
+        DefaultBootstrapSession session = (DefaultBootstrapSession) bsSession;
+        List<BootstrapDownlinkRequest<? extends LwM2mResponse>> requestsToSend = session.getRequests();
+
+        if (!requestsToSend.isEmpty()) {
+            // get next requests
+            return requestsToSend.remove(0);
+        } else {
+            if (session.hasMoreTasks()) {
+                Tasks nextTasks = tasksProvider.getTasks(session, session.getResponses());
+                if (nextTasks == null) {
+                    session.setMoreTasks(false);
+                    return new BootstrapFinishRequest();
+                }
+
+                initTasks(session, nextTasks);
+                return nextRequest(bsSession);
+            } else {
+                return new BootstrapFinishRequest();
+            }
+        }
     }
 
     @Override
-    public void onResponseSuccess(BootstrapSession bsSession,
-            BootstrapDownlinkRequest<? extends LwM2mResponse> request) {
+    public BootstrapPolicy onResponseSuccess(BootstrapSession bsSession,
+            BootstrapDownlinkRequest<? extends LwM2mResponse> request, LwM2mResponse response) {
         if (LOG.isTraceEnabled())
             LOG.trace("{} {} receives success response for {} : {}", request.getClass().getSimpleName(),
                     request.getPath(), bsSession, request);
+
+        if (!(request instanceof BootstrapFinishRequest)) {
+            // store response
+            DefaultBootstrapSession session = (DefaultBootstrapSession) bsSession;
+            session.getResponses().add(response);
+            // on success for NOT bootstrap finish request we send next request
+            return BootstrapPolicy.continueWith(nextRequest(bsSession));
+        } else {
+            // on success for bootstrap finish request we stop the session
+            return BootstrapPolicy.finished();
+        }
     }
 
     @Override
@@ -102,10 +172,17 @@ public class DefaultBootstrapSessionManager implements BootstrapSessionManager {
             LOG.trace("{} {} receives error response {} for {} : {}", request.getClass().getSimpleName(),
                     request.getPath(), response, bsSession, request);
 
-        if (request instanceof BootstrapFinishRequest) {
-            return BootstrapPolicy.STOP;
+        if (!(request instanceof BootstrapFinishRequest)) {
+            // store response
+            DefaultBootstrapSession session = (DefaultBootstrapSession) bsSession;
+            session.getResponses().add(response);
+
+            // on response error for NOT bootstrap finish request we continue any sending next request
+            return BootstrapPolicy.continueWith(nextRequest(bsSession));
+        } else {
+            // on response error for bootstrap finish request we stop the session
+            return BootstrapPolicy.failed();
         }
-        return BootstrapPolicy.CONTINUE;
     }
 
     @Override
@@ -115,6 +192,17 @@ public class DefaultBootstrapSessionManager implements BootstrapSessionManager {
             LOG.trace("{} {} failed because of {} for {} : {}", request.getClass().getSimpleName(), request.getPath(),
                     cause, bsSession, request);
 
-        return BootstrapPolicy.STOP;
+        return BootstrapPolicy.failed();
     }
+
+    @Override
+    public void end(BootstrapSession bsSession) {
+        LOG.trace("Bootstrap session finished : {}", bsSession);
+    }
+
+    @Override
+    public void failed(BootstrapSession bsSession, BootstrapFailureCause cause) {
+        LOG.trace("Bootstrap session failed by {}: {}", cause, bsSession);
+    }
+
 }
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/EditableBootstrapConfigStore.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/EditableBootstrapConfigStore.java
index a5ff04c7..4a7f1973 100644
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/EditableBootstrapConfigStore.java
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/EditableBootstrapConfigStore.java
@@ -19,13 +19,7 @@ import java.util.Map;
 
 /**
  * An editable {@link BootstrapConfigStore}.
- * 
- * @deprecated use {@link EditableBootstrapConfigurationStore} instead or see {@link BootstrapConfigurationStoreAdapter}
- *             or
- *             {@link BootstrapUtil#toRequests(BootstrapConfig, org.eclipse.leshan.core.request.ContentFormat)}
- * 
  */
-@Deprecated
 public interface EditableBootstrapConfigStore extends BootstrapConfigStore {
 
     /**
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/EditableBootstrapConfigurationStore.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/EditableBootstrapConfigurationStore.java
deleted file mode 100644
index 0d3e6082..00000000
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/EditableBootstrapConfigurationStore.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2020 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.server.bootstrap;
-
-/**
- * An editable {@link BootstrapConfigurationStore}.
- */
-public interface EditableBootstrapConfigurationStore extends BootstrapConfigurationStore {
-
-    /**
-     * Add a new bootstrap configuration for a client end-point.
-     * 
-     * @param endpoint The client endpoint name to which we want to set the given bootstrap configuration.
-     * @param config The configuration to apply for the given client.
-     * @throws InvalidConfigurationException if {@link BootstrapConfiguration} is not valid.
-     */
-    void add(String endpoint, BootstrapConfiguration config) throws InvalidConfigurationException;
-
-    /**
-     * Removes the bootstrap Configuration for a given end-point.
-     * 
-     * @param endpoint The client endpoint to which we want to remove the configuration.
-     * @return the removed {@link BootstrapConfiguration} or <code>null</code> if no bootstrap configuration for this
-     *         client.
-     */
-    BootstrapConfiguration remove(String endpoint);
-}
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/InMemoryBootstrapConfigStore.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/InMemoryBootstrapConfigStore.java
index c89b808b..f488ea31 100644
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/InMemoryBootstrapConfigStore.java
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/InMemoryBootstrapConfigStore.java
@@ -26,11 +26,7 @@ import org.eclipse.leshan.server.bootstrap.BootstrapConfig.ServerSecurity;
 /**
  * Simple bootstrap store implementation storing bootstrap configuration information in memory.
  * 
- * * @deprecated use {@link InMemoryBootstrapConfigurationStore} instead or see *
- * {@link BootstrapConfigurationStoreAdapter} or
- * {@link BootstrapUtil#toRequests(BootstrapConfig, org.eclipse.leshan.core.request.ContentFormat)}
  */
-@Deprecated
 public class InMemoryBootstrapConfigStore implements EditableBootstrapConfigStore {
 
     protected final ConfigurationChecker configChecker = new ConfigurationChecker();
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/InMemoryBootstrapConfigurationStore.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/InMemoryBootstrapConfigurationStore.java
deleted file mode 100644
index 2275af64..00000000
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/InMemoryBootstrapConfigurationStore.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2020 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.server.bootstrap;
-
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.eclipse.leshan.core.request.Identity;
-
-/**
- * Simple bootstrap store implementation storing bootstrap configuration information in memory.
- */
-public class InMemoryBootstrapConfigurationStore implements EditableBootstrapConfigurationStore {
-    protected final ConfigurationChecker configChecker = new ConfigurationChecker();
-
-    protected final Map<String /* endpoint */, BootstrapConfiguration> bootstrapByEndpoint = new ConcurrentHashMap<>();
-
-    @Override
-    public BootstrapConfiguration get(String endpoint, Identity deviceIdentity, BootstrapSession session) {
-        return bootstrapByEndpoint.get(endpoint);
-    }
-
-    @Override
-    public synchronized void add(String endpoint, BootstrapConfiguration config) throws InvalidConfigurationException {
-        bootstrapByEndpoint.put(endpoint, config);
-    }
-
-    @Override
-    public synchronized BootstrapConfiguration remove(String enpoint) {
-        BootstrapConfiguration bootstrapConfig = bootstrapByEndpoint.remove(enpoint);
-        return bootstrapConfig;
-    }
-}
\ No newline at end of file
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/model/LwM2mBootstrapModelProvider.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/model/LwM2mBootstrapModelProvider.java
new file mode 100644
index 00000000..cbe10503
--- /dev/null
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/model/LwM2mBootstrapModelProvider.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (c) 2015 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.server.model;
+
+import java.util.Map;
+
+import org.eclipse.leshan.core.model.LwM2mModel;
+import org.eclipse.leshan.core.node.codec.LwM2mNodeDecoder;
+import org.eclipse.leshan.core.node.codec.LwM2mNodeEncoder;
+import org.eclipse.leshan.server.bootstrap.BootstrapSession;
+
+/**
+ * A <code>LwM2mBootstrapModelProvider</code> implementation is in charge of returning the description of the LWM2M
+ * objects for a given session.
+ * <p>
+ * The description of each object is mainly used by the {@link LwM2mNodeEncoder}/{@link LwM2mNodeDecoder} to
+ * encode/decode the requests/responses payload.
+ * </p>
+ * <p>
+ * A typical use case to implement a custom provider is the need to support several version of the specification.
+ * </p>
+ */
+public interface LwM2mBootstrapModelProvider {
+
+    /**
+     * Returns the description of the objects supported by the client for a given session.
+     * 
+     * @param session the current Bootstrap Session.
+     * @param supportedObjects objects supported by the client for this session. (ObjectId -&gt; Version of the model)
+     * @return the list of object descriptions
+     */
+    LwM2mModel getObjectModel(BootstrapSession session, Map<Integer, String> supportedObjects);
+
+}
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/model/StandardBootstrapModelProvider.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/model/StandardBootstrapModelProvider.java
new file mode 100644
index 00000000..016cbf73
--- /dev/null
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/model/StandardBootstrapModelProvider.java
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * Copyright (c) 2015 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.server.model;
+
+import org.eclipse.leshan.core.model.ObjectLoader;
+
+/**
+ * A versioned model provider which uses the default model embedded in Leshan.
+ * 
+ * @see VersionedModelProvider
+ */
+public class StandardBootstrapModelProvider extends VersionedBootstrapModelProvider {
+
+    public StandardBootstrapModelProvider() {
+        super(ObjectLoader.loadAllDefault());
+    }
+}
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/model/VersionedBootstrapModelProvider.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/model/VersionedBootstrapModelProvider.java
new file mode 100644
index 00000000..65102ccc
--- /dev/null
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/model/VersionedBootstrapModelProvider.java
@@ -0,0 +1,87 @@
+/*******************************************************************************
+ * Copyright (c) 2019 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.server.model;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.eclipse.leshan.core.model.LwM2mModel;
+import org.eclipse.leshan.core.model.LwM2mModelRepository;
+import org.eclipse.leshan.core.model.ObjectModel;
+import org.eclipse.leshan.core.model.ResourceModel;
+import org.eclipse.leshan.server.bootstrap.BootstrapSession;
+
+/**
+ * A {@link LwM2mBootstrapModelProvider} which supports object versioning.
+ */
+public class VersionedBootstrapModelProvider implements LwM2mBootstrapModelProvider {
+
+    private LwM2mModelRepository repository;
+
+    public VersionedBootstrapModelProvider(Collection<ObjectModel> objectModels) {
+        this.repository = new LwM2mModelRepository(objectModels);
+    }
+
+    public VersionedBootstrapModelProvider(LwM2mModelRepository repository) {
+        this.repository = repository;
+    }
+
+    @Override
+    public LwM2mModel getObjectModel(BootstrapSession session, Map<Integer, String> supportedObjects) {
+        return new DynamicModel(supportedObjects);
+    }
+
+    private class DynamicModel implements LwM2mModel {
+
+        private final Map<Integer, String> supportedObjects;
+
+        public DynamicModel(Map<Integer, String> supportedObjects) {
+            this.supportedObjects = supportedObjects;
+        }
+
+        @Override
+        public ResourceModel getResourceModel(int objectId, int resourceId) {
+            ObjectModel objectModel = getObjectModel(objectId);
+            if (objectModel != null)
+                return objectModel.resources.get(resourceId);
+            else
+                return null;
+        }
+
+        @Override
+        public ObjectModel getObjectModel(int objectId) {
+            String version = supportedObjects.get(objectId);
+            if (version != null) {
+                return repository.getObjectModel(objectId, version);
+            }
+            return null;
+        }
+
+        @Override
+        public Collection<ObjectModel> getObjectModels() {
+            Collection<ObjectModel> result = new ArrayList<>(supportedObjects.size());
+            for (Entry<Integer, String> supportedObject : supportedObjects.entrySet()) {
+                ObjectModel objectModel = repository.getObjectModel(supportedObject.getKey(),
+                        supportedObject.getValue());
+                if (objectModel != null)
+                    result.add(objectModel);
+            }
+            return result;
+        }
+    }
+}
\ No newline at end of file


Test patch:
diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/BootstrapIntegrationTestHelper.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/BootstrapIntegrationTestHelper.java
index 0dfef92e..4798c3cb 100644
--- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/BootstrapIntegrationTestHelper.java
+++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/BootstrapIntegrationTestHelper.java
@@ -57,27 +57,24 @@ import org.eclipse.leshan.core.request.BootstrapRequest;
 import org.eclipse.leshan.core.request.ContentFormat;
 import org.eclipse.leshan.core.request.Identity;
 import org.eclipse.leshan.core.response.BootstrapDiscoverResponse;
+import org.eclipse.leshan.core.response.LwM2mResponse;
 import org.eclipse.leshan.core.util.Hex;
 import org.eclipse.leshan.server.bootstrap.BootstrapConfig;
 import org.eclipse.leshan.server.bootstrap.BootstrapConfig.ACLConfig;
 import org.eclipse.leshan.server.bootstrap.BootstrapConfig.ServerConfig;
 import org.eclipse.leshan.server.bootstrap.BootstrapConfig.ServerSecurity;
-import org.eclipse.leshan.server.bootstrap.BootstrapConfiguration;
-import org.eclipse.leshan.server.bootstrap.BootstrapConfigurationStore;
-import org.eclipse.leshan.server.bootstrap.BootstrapFailureCause;
-import org.eclipse.leshan.server.bootstrap.BootstrapHandler;
-import org.eclipse.leshan.server.bootstrap.BootstrapHandlerFactory;
+import org.eclipse.leshan.server.bootstrap.BootstrapConfigStore;
+import org.eclipse.leshan.server.bootstrap.BootstrapConfigStoreTaskProvider;
 import org.eclipse.leshan.server.bootstrap.BootstrapSession;
-import org.eclipse.leshan.server.bootstrap.BootstrapSessionManager;
-import org.eclipse.leshan.server.bootstrap.BootstrapUtil;
-import org.eclipse.leshan.server.bootstrap.DefaultBootstrapHandler;
+import org.eclipse.leshan.server.bootstrap.BootstrapTaskProvider;
 import org.eclipse.leshan.server.bootstrap.DefaultBootstrapSession;
 import org.eclipse.leshan.server.bootstrap.DefaultBootstrapSessionManager;
-import org.eclipse.leshan.server.bootstrap.LwM2mBootstrapRequestSender;
 import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServer;
 import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServerBuilder;
+import org.eclipse.leshan.server.model.StandardBootstrapModelProvider;
 import org.eclipse.leshan.server.security.BootstrapSecurityStore;
 import org.eclipse.leshan.server.security.EditableSecurityStore;
+import org.eclipse.leshan.server.security.SecurityChecker;
 import org.eclipse.leshan.server.security.SecurityInfo;
 
 /**
@@ -92,6 +89,30 @@ public class BootstrapIntegrationTestHelper extends SecureIntegrationTestHelper
     public volatile DefaultBootstrapSession lastBootstrapSession;
     public volatile BootstrapDiscoverResponse lastDiscoverAnswer;
 
+    private class TestBootstrapSessionManager extends DefaultBootstrapSessionManager {
+
+        public TestBootstrapSessionManager(BootstrapSecurityStore bsSecurityStore,
+                BootstrapTaskProvider tasksProvider) {
+            super(bsSecurityStore, new SecurityChecker(), tasksProvider, new StandardBootstrapModelProvider());
+        }
+
+        public TestBootstrapSessionManager(BootstrapSecurityStore bsSecurityStore, BootstrapConfigStore configStore) {
+            super(bsSecurityStore, new SecurityChecker(), new BootstrapConfigStoreTaskProvider(configStore),
+                    new StandardBootstrapModelProvider());
+        }
+
+        @Override
+        public BootstrapSession begin(BootstrapRequest request, Identity clientIdentity) {
+            assertThat(request.getCoapRequest(), instanceOf(Request.class));
+            return super.begin(request, clientIdentity);
+        }
+
+        @Override
+        public void end(BootstrapSession bsSession) {
+            lastBootstrapSession = (DefaultBootstrapSession) bsSession;
+        }
+    }
+
     public BootstrapIntegrationTestHelper() {
         super();
 
@@ -124,76 +145,56 @@ public class BootstrapIntegrationTestHelper extends SecureIntegrationTestHelper
     }
 
     private LeshanBootstrapServerBuilder createBootstrapBuilder(BootstrapSecurityStore securityStore,
-            BootstrapConfigurationStore bootstrapStore) {
-        if (bootstrapStore == null) {
-            bootstrapStore = unsecuredBootstrapStore();
-        }
-
-        if (securityStore == null) {
-            securityStore = dummyBsSecurityStore();
-        }
-
+            BootstrapConfigStore bootstrapStore) {
         LeshanBootstrapServerBuilder builder = new LeshanBootstrapServerBuilder();
-        builder.setConfigStore(bootstrapStore);
-        builder.setSecurityStore(securityStore);
         builder.setLocalAddress(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
         builder.setLocalSecureAddress(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
         builder.setPrivateKey(bootstrapServerPrivateKey);
         builder.setPublicKey(bootstrapServerPublicKey);
-        builder.setSessionManager(new DefaultBootstrapSessionManager(securityStore) {
-
-            @Override
-            public BootstrapSession begin(BootstrapRequest request, Identity clientIdentity) {
-                assertThat(request.getCoapRequest(), instanceOf(Request.class));
-                return super.begin(request, clientIdentity);
-            }
-
-            @Override
-            public void end(BootstrapSession bsSession) {
-                lastBootstrapSession = (DefaultBootstrapSession) bsSession;
-            }
-        });
-
         return builder;
     }
 
-    public void createBootstrapServer(BootstrapSecurityStore securityStore,
-            BootstrapConfigurationStore bootstrapStore) {
-        bootstrapServer = createBootstrapBuilder(securityStore, bootstrapStore).build();
+    public void createBootstrapServer(BootstrapSecurityStore securityStore, BootstrapConfigStore bootstrapStore) {
+        LeshanBootstrapServerBuilder builder = createBootstrapBuilder(securityStore, bootstrapStore);
+        if (bootstrapStore == null) {
+            bootstrapStore = unsecuredBootstrapStore();
+        }
+
+        if (securityStore == null) {
+            securityStore = dummyBsSecurityStore();
+        }
+        builder.setSecurityStore(securityStore);
+        builder.setSessionManager(new TestBootstrapSessionManager(securityStore, bootstrapStore));
+        bootstrapServer = builder.build();
     }
 
-    public void createBootstrapServer(BootstrapSecurityStore securityStore, BootstrapConfigurationStore bootstrapStore,
+    public void createBootstrapServer(BootstrapSecurityStore securityStore, BootstrapConfigStore bootstrapStore,
             final BootstrapDiscoverRequest request) {
         LeshanBootstrapServerBuilder builder = createBootstrapBuilder(securityStore, bootstrapStore);
-
+        if (bootstrapStore == null) {
+            bootstrapStore = unsecuredBootstrapStore();
+        }
+        if (securityStore == null) {
+            securityStore = dummyBsSecurityStore();
+        }
         // start bootstrap session by a bootstrap discover request
-        builder.setBootstrapHandlerFactory(new BootstrapHandlerFactory() {
-
+        BootstrapConfigStoreTaskProvider taskProvider = new BootstrapConfigStoreTaskProvider(bootstrapStore) {
             @Override
-            public BootstrapHandler create(BootstrapConfigurationStore store, LwM2mBootstrapRequestSender sender,
-                    BootstrapSessionManager sessionManager) {
-                return new DefaultBootstrapHandler(store, sender, sessionManager) {
-
-                    @Override
-                    protected void startBootstrap(final BootstrapSession session, final BootstrapConfiguration cfg) {
-                        send(session, request, new SafeResponseCallback<BootstrapDiscoverResponse>(session) {
-
-                            @Override
-                            public void safeOnResponse(BootstrapDiscoverResponse response) {
-                                lastDiscoverAnswer = response;
-                                sendRequest(session, cfg, new ArrayList<>(cfg.getRequests()));
-                            }
-                        }, new SafeErrorCallback(session) {
-                            @Override
-                            public void safeOnError(Exception e) {
-                                stopSession(session, BootstrapFailureCause.INTERNAL_SERVER_ERROR);
-                            }
-                        });
-                    }
-                };
+            public Tasks getTasks(BootstrapSession session, List<LwM2mResponse> previousResponses) {
+                if (previousResponses == null) {
+                    Tasks tasks = new Tasks();
+                    tasks.requestsToSend = new ArrayList<>();
+                    tasks.requestsToSend.add(request);
+                    tasks.last = false;
+                    return tasks;
+                } else {
+                    lastDiscoverAnswer = (BootstrapDiscoverResponse) previousResponses.get(0);
+                    return super.getTasks(session, previousResponses);
+                }
             }
-        });
-
+        };
+        builder.setSecurityStore(securityStore);
+        builder.setSessionManager(new TestBootstrapSessionManager(securityStore, taskProvider));
         bootstrapServer = builder.build();
     }
 
@@ -346,11 +347,11 @@ public class BootstrapIntegrationTestHelper extends SecureIntegrationTestHelper
         };
     }
 
-    public BootstrapConfigurationStore unsecuredBootstrapStore() {
-        return new BootstrapConfigurationStore() {
+    public BootstrapConfigStore unsecuredBootstrapStore() {
+        return new BootstrapConfigStore() {
 
             @Override
-            public BootstrapConfiguration get(String endpoint, Identity deviceIdentity, BootstrapSession session) {
+            public BootstrapConfig get(String endpoint, Identity deviceIdentity, BootstrapSession session) {
 
                 BootstrapConfig bsConfig = new BootstrapConfig();
 
@@ -375,12 +376,12 @@ public class BootstrapIntegrationTestHelper extends SecureIntegrationTestHelper
                 dmConfig.shortId = 2222;
                 bsConfig.servers.put(0, dmConfig);
 
-                return new BootstrapConfiguration(BootstrapUtil.toRequests(bsConfig, session.getContentFormat()));
+                return bsConfig;
             }
         };
     }
 
-    public BootstrapConfigurationStore deleteSecurityStore(Integer... objectToDelete) {
+    public BootstrapConfigStore deleteSecurityStore(Integer... objectToDelete) {
         String[] pathToDelete = new String[objectToDelete.length];
         for (int i = 0; i < pathToDelete.length; i++) {
             pathToDelete[i] = "/" + objectToDelete[i];
@@ -389,24 +390,24 @@ public class BootstrapIntegrationTestHelper extends SecureIntegrationTestHelper
         return deleteSecurityStore(pathToDelete);
     }
 
-    public BootstrapConfigurationStore deleteSecurityStore(final String... pathToDelete) {
-        return new BootstrapConfigurationStore() {
+    public BootstrapConfigStore deleteSecurityStore(final String... pathToDelete) {
+        return new BootstrapConfigStore() {
 
             @Override
-            public BootstrapConfiguration get(String endpoint, Identity deviceIdentity, BootstrapSession session) {
+            public BootstrapConfig get(String endpoint, Identity deviceIdentity, BootstrapSession session) {
 
                 BootstrapConfig bsConfig = new BootstrapConfig();
                 bsConfig.toDelete = Arrays.asList(pathToDelete);
-                return new BootstrapConfiguration(BootstrapUtil.toRequests(bsConfig, session.getContentFormat()));
+                return bsConfig;
             }
         };
     }
 
-    public BootstrapConfigurationStore unsecuredWithAclBootstrapStore() {
-        return new BootstrapConfigurationStore() {
+    public BootstrapConfigStore unsecuredWithAclBootstrapStore() {
+        return new BootstrapConfigStore() {
 
             @Override
-            public BootstrapConfiguration get(String endpoint, Identity deviceIdentity, BootstrapSession session) {
+            public BootstrapConfig get(String endpoint, Identity deviceIdentity, BootstrapSession session) {
 
                 BootstrapConfig bsConfig = new BootstrapConfig();
 
@@ -447,16 +448,16 @@ public class BootstrapIntegrationTestHelper extends SecureIntegrationTestHelper
                 aclConfig.AccessControlOwner = 2222;
                 bsConfig.acls.put(1, aclConfig);
 
-                return new BootstrapConfiguration(BootstrapUtil.toRequests(bsConfig));
+                return bsConfig;
             }
         };
     }
 
-    public BootstrapConfigurationStore pskBootstrapStore() {
-        return new BootstrapConfigurationStore() {
+    public BootstrapConfigStore pskBootstrapStore() {
+        return new BootstrapConfigStore() {
 
             @Override
-            public BootstrapConfiguration get(String endpoint, Identity deviceIdentity, BootstrapSession session) {
+            public BootstrapConfig get(String endpoint, Identity deviceIdentity, BootstrapSession session) {
 
                 BootstrapConfig bsConfig = new BootstrapConfig();
 
@@ -483,16 +484,16 @@ public class BootstrapIntegrationTestHelper extends SecureIntegrationTestHelper
                 dmConfig.shortId = 2222;
                 bsConfig.servers.put(0, dmConfig);
 
-                return new BootstrapConfiguration(BootstrapUtil.toRequests(bsConfig, session.getContentFormat()));
+                return bsConfig;
             }
         };
     }
 
-    public BootstrapConfigurationStore rpkBootstrapStore() {
-        return new BootstrapConfigurationStore() {
+    public BootstrapConfigStore rpkBootstrapStore() {
+        return new BootstrapConfigStore() {
 
             @Override
-            public BootstrapConfiguration get(String endpoint, Identity deviceIdentity, BootstrapSession session) {
+            public BootstrapConfig get(String endpoint, Identity deviceIdentity, BootstrapSession session) {
 
                 BootstrapConfig bsConfig = new BootstrapConfig();
 
@@ -520,7 +521,7 @@ public class BootstrapIntegrationTestHelper extends SecureIntegrationTestHelper
                 dmConfig.shortId = 2222;
                 bsConfig.servers.put(0, dmConfig);
 
-                return new BootstrapConfiguration(BootstrapUtil.toRequests(bsConfig, session.getContentFormat()));
+                return bsConfig;
             }
         };
     }
diff --git a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServerBuilderTest.java b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServerBuilderTest.java
index 8287aadc..c3fe6e83 100644
--- a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServerBuilderTest.java
+++ b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServerBuilderTest.java
@@ -20,8 +20,8 @@ import static org.junit.Assert.*;
 import java.util.Iterator;
 
 import org.eclipse.leshan.core.request.Identity;
-import org.eclipse.leshan.server.bootstrap.BootstrapConfiguration;
-import org.eclipse.leshan.server.bootstrap.BootstrapConfigurationStore;
+import org.eclipse.leshan.server.bootstrap.BootstrapConfig;
+import org.eclipse.leshan.server.bootstrap.BootstrapConfigStore;
 import org.eclipse.leshan.server.bootstrap.BootstrapSession;
 import org.eclipse.leshan.server.security.BootstrapSecurityStore;
 import org.eclipse.leshan.server.security.SecurityInfo;
@@ -36,10 +36,10 @@ public class LeshanBootstrapServerBuilderTest {
     @Before
     public void start() {
         builder = new LeshanBootstrapServerBuilder();
-        builder.setConfigStore(new BootstrapConfigurationStore() {
+        builder.setConfigStore(new BootstrapConfigStore() {
 
             @Override
-            public BootstrapConfiguration get(String endpoint, Identity deviceIdentity, BootstrapSession session) {
+            public BootstrapConfig get(String endpoint, Identity deviceIdentity, BootstrapSession session) {
                 return null;
             }
         });
@@ -70,7 +70,6 @@ public class LeshanBootstrapServerBuilderTest {
 
         assertNotNull(server.getSecuredAddress());
         assertNotNull(server.getUnsecuredAddress());
-        assertNotNull(server.getBootstrapSecurityStore());
     }
 
     @Test
diff --git a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServerTest.java b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServerTest.java
index 365856e6..b57dc438 100644
--- a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServerTest.java
+++ b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServerTest.java
@@ -19,13 +19,12 @@ import static org.junit.Assert.assertEquals;
 
 import java.net.InetSocketAddress;
 
-import org.eclipse.leshan.core.request.BootstrapDeleteRequest;
 import org.eclipse.leshan.core.request.BootstrapRequest;
 import org.eclipse.leshan.core.request.Identity;
 import org.eclipse.leshan.core.response.BootstrapResponse;
 import org.eclipse.leshan.core.response.SendableResponse;
-import org.eclipse.leshan.server.bootstrap.BootstrapConfiguration;
-import org.eclipse.leshan.server.bootstrap.BootstrapConfigurationStore;
+import org.eclipse.leshan.server.bootstrap.BootstrapConfig;
+import org.eclipse.leshan.server.bootstrap.BootstrapConfigStore;
 import org.eclipse.leshan.server.bootstrap.BootstrapHandler;
 import org.eclipse.leshan.server.bootstrap.BootstrapHandlerFactory;
 import org.eclipse.leshan.server.bootstrap.BootstrapSession;
@@ -44,17 +43,18 @@ public class LeshanBootstrapServerTest {
         builder.setBootstrapHandlerFactory(new BootstrapHandlerFactory() {
 
             @Override
-            public BootstrapHandler create(BootstrapConfigurationStore store, LwM2mBootstrapRequestSender sender,
-                    BootstrapSessionManager sessionManager) {
-                bsHandler = new DefaultBootstrapHandler(store, sender, sessionManager);
+            public BootstrapHandler create(LwM2mBootstrapRequestSender sender, BootstrapSessionManager sessionManager) {
+                bsHandler = new DefaultBootstrapHandler(sender, sessionManager);
                 return bsHandler;
             }
         });
-        builder.setConfigStore(new BootstrapConfigurationStore() {
+        builder.setConfigStore(new BootstrapConfigStore() {
 
             @Override
-            public BootstrapConfiguration get(String endpoint, Identity deviceIdentity, BootstrapSession session) {
-                return new BootstrapConfiguration(new BootstrapDeleteRequest());
+            public BootstrapConfig get(String endpoint, Identity deviceIdentity, BootstrapSession session) {
+                BootstrapConfig config = new BootstrapConfig();
+                config.toDelete.add("/");
+                return config;
             }
         });
         return builder.build();
diff --git a/leshan-server-core/src/test/java/org/eclipse/leshan/server/SerializationTests.java b/leshan-server-core/src/test/java/org/eclipse/leshan/server/SerializationTests.java
index 31183f09..67db8ab7 100644
--- a/leshan-server-core/src/test/java/org/eclipse/leshan/server/SerializationTests.java
+++ b/leshan-server-core/src/test/java/org/eclipse/leshan/server/SerializationTests.java
@@ -19,7 +19,6 @@ import static org.junit.Assert.fail;
 
 import java.util.Map;
 
-import org.eclipse.leshan.server.bootstrap.BootstrapConfig;
 import org.eclipse.leshan.server.security.SecurityInfo;
 import org.junit.Test;
 
@@ -30,11 +29,6 @@ public class SerializationTests {
         assertIsSerializable(SecurityInfo.class);
     }
 
-    @Test
-    public void ensure_BootstrapConfig_is_serializable() {
-        assertIsSerializable(BootstrapConfig.class);
-    }
-
     private static void assertIsSerializable(Class<?> clazz, String... excludes) {
         Map<Object, String> results = SerializationUtil.isSerializable(clazz, excludes);
 
diff --git a/leshan-server-core/src/test/java/org/eclipse/leshan/server/bootstrap/BootstrapHandlerTest.java b/leshan-server-core/src/test/java/org/eclipse/leshan/server/bootstrap/BootstrapHandlerTest.java
index 7d5c6422..9a136d20 100644
--- a/leshan-server-core/src/test/java/org/eclipse/leshan/server/bootstrap/BootstrapHandlerTest.java
+++ b/leshan-server-core/src/test/java/org/eclipse/leshan/server/bootstrap/BootstrapHandlerTest.java
@@ -25,7 +25,6 @@ import java.util.Map;
 
 import org.eclipse.leshan.core.ResponseCode;
 import org.eclipse.leshan.core.request.BootstrapDeleteRequest;
-import org.eclipse.leshan.core.request.BootstrapDownlinkRequest;
 import org.eclipse.leshan.core.request.BootstrapFinishRequest;
 import org.eclipse.leshan.core.request.BootstrapRequest;
 import org.eclipse.leshan.core.request.BootstrapWriteRequest;
@@ -48,9 +47,9 @@ public class BootstrapHandlerTest {
     @Test
     public void error_if_not_authorized() {
         // prepare bootstrapHandler with a session manager which does not authorized any session
-        BootstrapSessionManager bsSessionManager = new MockBootstrapSessionManager(false);
-        BootstrapHandler bsHandler = new DefaultBootstrapHandler((BootstrapConfigurationStore) null, null,
-                bsSessionManager);
+        BootstrapSessionManager bsSessionManager = new MockBootstrapSessionManager(false,
+                new InMemoryBootstrapConfigStore());
+        BootstrapHandler bsHandler = new DefaultBootstrapHandler(null, bsSessionManager);
 
         // Try to bootstrap
         BootstrapResponse response = bsHandler
@@ -66,11 +65,12 @@ public class BootstrapHandlerTest {
         // prepare a bootstrap handler with a session manager which authorize all session
         // and a sender which "obtains" always successful response.
         // and a config store with and an empty config for the expected endpoint
-        MockBootstrapSessionManager bsSessionManager = new MockBootstrapSessionManager(true);
         LwM2mBootstrapRequestSender requestSender = new MockRequestSender(Mode.ALWAYS_SUCCESS);
-        EditableBootstrapConfigurationStore bsStore = new InMemoryBootstrapConfigurationStore();
-        bsStore.add("endpoint", new BootstrapConfiguration());
-        BootstrapHandler bsHandler = new DefaultBootstrapHandler(bsStore, requestSender, bsSessionManager);
+        EditableBootstrapConfigStore bsStore = new InMemoryBootstrapConfigStore();
+        bsStore.add("endpoint", new BootstrapConfig());
+        MockBootstrapSessionManager bsSessionManager = new MockBootstrapSessionManager(true, bsStore);
+
+        BootstrapHandler bsHandler = new DefaultBootstrapHandler(requestSender, bsSessionManager);
 
         // Try to bootstrap
         SendableResponse<BootstrapResponse> sendableResponse = bsHandler
@@ -87,11 +87,11 @@ public class BootstrapHandlerTest {
         // prepare a bootstrap handler with a session manager which authorize all session
         // and a sender which always failed to send request.
         // and a config store with and an empty config for the expected endpoint
-        MockBootstrapSessionManager bsSessionManager = new MockBootstrapSessionManager(true);
         LwM2mBootstrapRequestSender requestSender = new MockRequestSender(Mode.ALWAYS_FAILURE);
-        EditableBootstrapConfigurationStore bsStore = new InMemoryBootstrapConfigurationStore();
-        bsStore.add("endpoint", new BootstrapConfiguration());
-        BootstrapHandler bsHandler = new DefaultBootstrapHandler(bsStore, requestSender, bsSessionManager);
+        EditableBootstrapConfigStore bsStore = new InMemoryBootstrapConfigStore();
+        bsStore.add("endpoint", new BootstrapConfig());
+        MockBootstrapSessionManager bsSessionManager = new MockBootstrapSessionManager(true, bsStore);
+        BootstrapHandler bsHandler = new DefaultBootstrapHandler(requestSender, bsSessionManager);
 
         // Try to bootstrap
         SendableResponse<BootstrapResponse> sendableResponse = bsHandler
@@ -110,11 +110,11 @@ public class BootstrapHandlerTest {
         // prepare a bootstrap handler with a session manager which authorize all session
         // and a sender which never get response.
         // and a config store with and an empty config for the expected endpoint
-        MockBootstrapSessionManager bsSessionManager = new MockBootstrapSessionManager(true);
         MockRequestSender requestSender = new MockRequestSender(Mode.NO_RESPONSE);
-        EditableBootstrapConfigurationStore bsStore = new InMemoryBootstrapConfigurationStore();
-        bsStore.add("endpoint", new BootstrapConfiguration());
-        BootstrapHandler bsHandler = new DefaultBootstrapHandler(bsStore, requestSender, bsSessionManager,
+        EditableBootstrapConfigStore bsStore = new InMemoryBootstrapConfigStore();
+        bsStore.add("endpoint", new BootstrapConfig());
+        MockBootstrapSessionManager bsSessionManager = new MockBootstrapSessionManager(true, bsStore);
+        BootstrapHandler bsHandler = new DefaultBootstrapHandler(requestSender, bsSessionManager,
                 DefaultBootstrapHandler.DEFAULT_TIMEOUT);
 
         // First bootstrap : which will not end (because of sender)
@@ -211,7 +211,7 @@ public class BootstrapHandlerTest {
         }
     }
 
-    private static class MockBootstrapSessionManager implements BootstrapSessionManager {
+    private static class MockBootstrapSessionManager extends DefaultBootstrapSessionManager {
 
         private boolean authorized;
         private BootstrapSession lastSession;
@@ -219,7 +219,8 @@ public class BootstrapHandlerTest {
         private List<BootstrapSession> endedSession = new ArrayList<BootstrapSession>();
         private Map<BootstrapSession, BootstrapFailureCause> failureCauses = new HashMap<>();
 
-        public MockBootstrapSessionManager(boolean authorized) {
+        public MockBootstrapSessionManager(boolean authorized, BootstrapConfigStore store) {
+            super(null, store);
             this.authorized = authorized;
         }
 
@@ -247,36 +248,17 @@ public class BootstrapHandlerTest {
 
         @Override
         public void end(BootstrapSession bsSession) {
+            super.end(bsSession);
             endedSession.add(bsSession);
         }
 
         @Override
         public void failed(BootstrapSession bsSession, BootstrapFailureCause cause) {
+            super.end(bsSession);
             lastFailureCause = cause;
             failureCauses.put(bsSession, cause);
         }
 
-        @Override
-        public void onResponseSuccess(BootstrapSession bsSession,
-                BootstrapDownlinkRequest<? extends LwM2mResponse> request) {
-
-        }
-
-        @Override
-        public BootstrapPolicy onResponseError(BootstrapSession bsSession,
-                BootstrapDownlinkRequest<? extends LwM2mResponse> request, LwM2mResponse response) {
-            if (request instanceof BootstrapFinishRequest) {
-                return BootstrapPolicy.STOP;
-            }
-            return BootstrapPolicy.CONTINUE;
-        }
-
-        @Override
-        public BootstrapPolicy onRequestFailure(BootstrapSession bsSession,
-                BootstrapDownlinkRequest<? extends LwM2mResponse> request, Throwable cause) {
-            return BootstrapPolicy.STOP;
-        }
-
         public void reset() {
             lastSession = null;
             lastFailureCause = null;