diff --git a/core/src/main/kotlin/net/corda/core/serialization/CordaClassResolver.kt b/core/src/main/kotlin/net/corda/core/serialization/CordaClassResolver.kt
index 4b6350ba03..a1743230f5 100644
--- a/core/src/main/kotlin/net/corda/core/serialization/CordaClassResolver.kt
+++ b/core/src/main/kotlin/net/corda/core/serialization/CordaClassResolver.kt
@@ -92,6 +92,24 @@ class CordaClassResolver(val whitelist: ClassWhitelist) : DefaultClassResolver()
         return type.interfaces.any { it.isAnnotationPresent(CordaSerializable::class.java) || hasAnnotationOnInterface(it) }
                 || (type.superclass != null && hasAnnotationOnInterface(type.superclass))
     }
+
+    // Need to clear out class names from attachments.
+    override fun reset() {
+        super.reset()
+        // Kryo creates a cache of class name to Class<*> which does not work so well with multiple class loaders.
+        // TODO: come up with a more efficient way.  e.g. segregate the name space by class loader.
+        if(nameToClass != null) {
+            val classesToRemove: MutableList<String> = ArrayList(nameToClass.size)
+            for (entry in nameToClass.entries()) {
+                if (entry.value.classLoader is AttachmentsClassLoader) {
+                    classesToRemove += entry.key
+                }
+            }
+            for (className in classesToRemove) {
+                nameToClass.remove(className)
+            }
+        }
+    }
 }
 
 interface ClassWhitelist {
diff --git a/core/src/main/kotlin/net/corda/core/serialization/Kryo.kt b/core/src/main/kotlin/net/corda/core/serialization/Kryo.kt
index c99356d3fa..ef93e233d9 100644
--- a/core/src/main/kotlin/net/corda/core/serialization/Kryo.kt
+++ b/core/src/main/kotlin/net/corda/core/serialization/Kryo.kt
@@ -527,11 +527,18 @@ object ReferencesAwareJavaSerializer : JavaSerializer() {
 
 val ATTACHMENT_STORAGE = "ATTACHMENT_STORAGE"
 
-var Kryo.attachmentStorage: AttachmentStorage?
+val Kryo.attachmentStorage: AttachmentStorage?
     get() = this.context.get(ATTACHMENT_STORAGE, null) as AttachmentStorage?
-    set(value) {
-        this.context.put(ATTACHMENT_STORAGE, value)
+
+fun <T> Kryo.withAttachmentStorage(attachmentStorage: AttachmentStorage?, block: () -> T): T {
+    val priorAttachmentStorage = this.attachmentStorage
+    this.context.put(ATTACHMENT_STORAGE, attachmentStorage)
+    try {
+        return block()
+    } finally {
+        this.context.put(ATTACHMENT_STORAGE, priorAttachmentStorage)
     }
+}
 
 object OrderedSerializer : Serializer<HashMap<Any, Any>>() {
     override fun write(kryo: Kryo, output: Output, obj: HashMap<Any, Any>) {
diff --git a/core/src/test/kotlin/net/corda/core/node/AttachmentClassLoaderTests.kt b/core/src/test/kotlin/net/corda/core/node/AttachmentClassLoaderTests.kt
index 8583abb27a..c2d3cd4545 100644
--- a/core/src/test/kotlin/net/corda/core/node/AttachmentClassLoaderTests.kt
+++ b/core/src/test/kotlin/net/corda/core/node/AttachmentClassLoaderTests.kt
@@ -83,16 +83,11 @@ class AttachmentClassLoaderTests {
 
     @Before
     fun setup() {
+        // Do not release these back to the pool, since we do some unorthodox modifications to them below.
         kryo = p2PKryo().borrow()
         kryo2 = p2PKryo().borrow()
     }
 
-    @After
-    fun teardown() {
-        p2PKryo().release(kryo)
-        p2PKryo().release(kryo2)
-    }
-
     @Test
     fun `dynamically load AnotherDummyContract from isolated contracts jar`() {
         val child = ClassLoaderForTests()
@@ -258,8 +253,19 @@ class AttachmentClassLoaderTests {
         val state2 = bytes.deserialize(kryo)
         assertEquals(cl, state2.contract.javaClass.classLoader)
         assertNotNull(state2)
+
+        // We should be able to load same class from a different class loader and have them be distinct.
+        val cl2 = AttachmentsClassLoader(arrayOf(att0, att1, att2).map { storage.openAttachment(it)!! }, FilteringClassLoader)
+
+        kryo.classLoader = cl2
+        kryo.addToWhitelist(Class.forName("net.corda.contracts.isolated.AnotherDummyContract", true, cl2))
+
+        val state3 = bytes.deserialize(kryo)
+        assertEquals(cl2, state3.contract.javaClass.classLoader)
+        assertNotNull(state3)
     }
 
+
     @Test
     fun `test serialization of WireTransaction with statically loaded contract`() {
         val tx = ATTACHMENT_TEST_PROGRAM_ID.generateInitial(MEGA_CORP.ref(0), 42, DUMMY_NOTARY)
@@ -283,24 +289,25 @@ class AttachmentClassLoaderTests {
         kryo.addToWhitelist(Class.forName("net.corda.contracts.isolated.AnotherDummyContract\$Commands\$Create", true, child))
 
         // todo - think about better way to push attachmentStorage down to serializer
-        kryo.attachmentStorage = storage
+        val bytes = kryo.withAttachmentStorage(storage) {
 
-        val attachmentRef = importJar(storage)
+            val attachmentRef = importJar(storage)
 
-        tx.addAttachment(storage.openAttachment(attachmentRef)!!.id)
+            tx.addAttachment(storage.openAttachment(attachmentRef)!!.id)
 
-        val wireTransaction = tx.toWireTransaction()
-
-        val bytes = wireTransaction.serialize(kryo)
+            val wireTransaction = tx.toWireTransaction()
 
+            wireTransaction.serialize(kryo)
+        }
         // use empty attachmentStorage
-        kryo2.attachmentStorage = storage
+        kryo2.withAttachmentStorage(storage) {
 
-        val copiedWireTransaction = bytes.deserialize(kryo2)
+            val copiedWireTransaction = bytes.deserialize(kryo2)
 
-        assertEquals(1, copiedWireTransaction.outputs.size)
-        val contract2 = copiedWireTransaction.outputs[0].data.contract as DummyContractBackdoor
-        assertEquals(42, contract2.inspectState(copiedWireTransaction.outputs[0].data))
+            assertEquals(1, copiedWireTransaction.outputs.size)
+            val contract2 = copiedWireTransaction.outputs[0].data.contract as DummyContractBackdoor
+            assertEquals(42, contract2.inspectState(copiedWireTransaction.outputs[0].data))
+        }
     }
 
     @Test
@@ -312,22 +319,22 @@ class AttachmentClassLoaderTests {
         val storage = MockAttachmentStorage()
 
         // todo - think about better way to push attachmentStorage down to serializer
-        kryo.attachmentStorage = storage
-
         val attachmentRef = importJar(storage)
+        val bytes = kryo.withAttachmentStorage(storage) {
 
-        tx.addAttachment(storage.openAttachment(attachmentRef)!!.id)
+            tx.addAttachment(storage.openAttachment(attachmentRef)!!.id)
 
-        val wireTransaction = tx.toWireTransaction()
+            val wireTransaction = tx.toWireTransaction()
 
-        val bytes = wireTransaction.serialize(kryo)
-
-        // use empty attachmentStorage
-        kryo2.attachmentStorage = MockAttachmentStorage()
-
-        val e = assertFailsWith(MissingAttachmentsException::class) {
-            bytes.deserialize(kryo2)
+            wireTransaction.serialize(kryo)
+        }
+        // use empty attachmentStorage
+        kryo2.withAttachmentStorage(MockAttachmentStorage()) {
+
+            val e = assertFailsWith(MissingAttachmentsException::class) {
+                bytes.deserialize(kryo2)
+            }
+            assertEquals(attachmentRef, e.ids.single())
         }
-        assertEquals(attachmentRef, e.ids.single())
     }
 }
diff --git a/core/src/test/kotlin/net/corda/core/serialization/KryoTests.kt b/core/src/test/kotlin/net/corda/core/serialization/KryoTests.kt
index 992020f94e..d27c0cbb95 100644
--- a/core/src/test/kotlin/net/corda/core/serialization/KryoTests.kt
+++ b/core/src/test/kotlin/net/corda/core/serialization/KryoTests.kt
@@ -10,6 +10,7 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider
 import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider
 import org.junit.After
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Test
 import org.slf4j.LoggerFactory
 import java.io.InputStream
@@ -25,14 +26,10 @@ class KryoTests {
 
     @Before
     fun setup() {
+        // We deliberately do not return this, since we do some unorthodox registering below and do not want to pollute the pool.
         kryo = p2PKryo().borrow()
     }
 
-    @After
-    fun teardown() {
-        p2PKryo().release(kryo)
-    }
-
     @Test
     fun ok() {
         val birthday = Instant.parse("1984-04-17T00:30:00.00Z")
diff --git a/node/src/main/kotlin/net/corda/node/serialization/DefaultWhitelist.kt b/node/src/main/kotlin/net/corda/node/serialization/DefaultWhitelist.kt
index fd4e1807b5..17be5f1aef 100644
--- a/node/src/main/kotlin/net/corda/node/serialization/DefaultWhitelist.kt
+++ b/node/src/main/kotlin/net/corda/node/serialization/DefaultWhitelist.kt
@@ -6,6 +6,7 @@ import net.corda.core.node.CordaPluginRegistry
 import net.corda.core.serialization.SerializationCustomization
 import org.apache.activemq.artemis.api.core.SimpleString
 import rx.Notification
+import rx.exceptions.OnErrorNotImplementedException
 import java.math.BigDecimal
 import java.time.LocalDate
 import java.time.Period
@@ -49,6 +50,7 @@ class DefaultWhitelist : CordaPluginRegistry() {
             addToWhitelist(LocalDate::class.java)
             addToWhitelist(Period::class.java)
             addToWhitelist(BitSet::class.java)
+            addToWhitelist(OnErrorNotImplementedException::class.java)
         }
         return true
     }