diff --git a/constants.properties b/constants.properties
index 309968d183..a7f40449c3 100644
--- a/constants.properties
+++ b/constants.properties
@@ -98,7 +98,7 @@ snappyVersion=0.5
 jcabiManifestsVersion=1.1
 picocliVersion=3.9.6
 commonsLangVersion=3.9
-commonsIoVersion=2.6
+commonsIoVersion=2.17.0
 controlsfxVersion=8.40.15
 # FontAwesomeFX for Java8
 fontawesomefxCommonsJava8Version=8.15
diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt
index 56f12e0dde..95d195bab8 100644
--- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt
+++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt
@@ -3,7 +3,13 @@
 
 package net.corda.nodeapi.internal.config
 
-import com.typesafe.config.*
+import com.typesafe.config.Config
+import com.typesafe.config.ConfigException
+import com.typesafe.config.ConfigFactory
+import com.typesafe.config.ConfigUtil
+import com.typesafe.config.ConfigValue
+import com.typesafe.config.ConfigValueFactory
+import com.typesafe.config.ConfigValueType
 import net.corda.core.identity.CordaX500Name
 import net.corda.core.internal.isStatic
 import net.corda.core.internal.noneOrSingle
@@ -11,9 +17,7 @@ import net.corda.core.internal.uncheckedCast
 import net.corda.core.utilities.NetworkHostAndPort
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
-import java.lang.reflect.Field
 import java.lang.reflect.InvocationTargetException
-import java.lang.reflect.ParameterizedType
 import java.net.Proxy
 import java.net.URL
 import java.nio.file.Path
@@ -22,7 +26,9 @@ import java.time.Duration
 import java.time.Instant
 import java.time.LocalDate
 import java.time.temporal.Temporal
-import java.util.*
+import java.time.temporal.TemporalAmount
+import java.util.Properties
+import java.util.UUID
 import javax.security.auth.x500.X500Principal
 import kotlin.reflect.KClass
 import kotlin.reflect.KProperty
@@ -181,7 +187,7 @@ private fun Config.getSingleValue(
             X500Principal::class -> X500Principal(getString(path))
             CordaX500Name::class -> {
                 when (getValue(path).valueType()) {
-                    ConfigValueType.OBJECT -> getConfig(path).parseAs(onUnknownKeys)
+                    ConfigValueType.OBJECT -> getConfig(path).parseAs(onUnknownKeys) as CordaX500Name
                     else -> CordaX500Name.parse(getString(path))
                 }
             }
@@ -291,98 +297,39 @@ private fun <T : Enum<T>> enumBridge(clazz: Class<T>, name: String): T {
  */
 fun Any.toConfig(): Config = ConfigValueFactory.fromMap(toConfigMap()).toConfig()
 
-fun Any?.toConfigValue(): ConfigValue = if (this is ConfigValue) {
-    this
-} else if (this != null) {
-    ConfigValueFactory.fromAnyRef(convertValue(this))
-} else {
-    ConfigValueFactory.fromAnyRef(null)
-}
+fun Any?.toConfigValue(): ConfigValue = ConfigValueFactory.fromAnyRef(sanitiseForFromAnyRef(this))
 
-@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
 // Reflect over the fields of the receiver and generate a value Map that can use to create Config object.
-private fun Any.toConfigMap(): Map<String, Any> {
-    val values = HashMap<String, Any>()
+private fun Any.toConfigMap(): Map<String, Any?> {
+    val values = LinkedHashMap<String, Any?>()
     for (field in javaClass.declaredFields) {
         if (field.isStatic || field.isSynthetic) continue
         field.isAccessible = true
         val value = field.get(this) ?: continue
-        val configValue = if (value is String || value is Boolean || value is Number) {
-            // These types are supported by Config as use as is
-            value
-        } else if (value is Temporal || value is NetworkHostAndPort || value is CordaX500Name ||
-                value is Path || value is URL || value is UUID || value is X500Principal) {
-            // These types make sense to be represented as Strings and the exact inverse parsing function for use in parseAs
-            value.toString()
-        } else if (value is Enum<*>) {
-            // Expicitly use the Enum's name in case the toString is overridden, which would make parsing problematic.
-            value.name
-        } else if (value is Properties) {
-            // For Properties we treat keys with . as nested configs
-            ConfigFactory.parseMap(uncheckedCast(value)).root()
-        } else if (value is Iterable<*>) {
-            value.toConfigIterable(field)
-        } else {
-            // Else this is a custom object recursed over
-            value.toConfigMap()
-        }
-        values[field.name] = configValue
+        values[field.name] = sanitiseForFromAnyRef(value)
     }
     return values
 }
 
-private fun convertValue(value: Any): Any {
-
-    return if (value is String || value is Boolean || value is Number) {
+/**
+ * @see ConfigValueFactory.fromAnyRef
+ */
+private fun sanitiseForFromAnyRef(value: Any?): Any? {
+    return when (value) {
         // These types are supported by Config as use as is
-        value
-    } else if (value is Temporal || value is NetworkHostAndPort || value is CordaX500Name ||
-            value is Path || value is URL || value is UUID || value is X500Principal) {
-        // These types make sense to be represented as Strings and the exact inverse parsing function for use in parseAs
-        value.toString()
-    } else if (value is Enum<*>) {
-        // Expicitly use the Enum's name in case the toString is overridden, which would make parsing problematic.
-        value.name
-    } else if (value is Properties) {
+        is String, is Boolean, is Number, is ConfigValue, is Duration, null -> value
+        is Enum<*> -> value.name
+        // These types make sense to be represented as Strings
+        is Temporal, is TemporalAmount, is NetworkHostAndPort, is CordaX500Name, is Path, is URL, is UUID, is X500Principal -> value.toString()
         // For Properties we treat keys with . as nested configs
-        ConfigFactory.parseMap(uncheckedCast(value)).root()
-    } else if (value is Iterable<*>) {
-        value.toConfigIterable()
-    } else {
+        is Properties -> ConfigFactory.parseMap(uncheckedCast(value)).root()
+        is Map<*, *> -> ConfigFactory.parseMap(value.map { it.key.toString() to sanitiseForFromAnyRef(it.value) }.toMap()).root()
+        is Iterable<*> -> value.map(::sanitiseForFromAnyRef)
         // Else this is a custom object recursed over
-        value.toConfigMap()
+        else -> value.toConfigMap()
     }
 }
 
-// For Iterables figure out the type parameter and apply the same logic as above on the individual elements.
-private fun Iterable<*>.toConfigIterable(field: Field): Iterable<Any?> {
-    val elementType = (field.genericType as ParameterizedType).actualTypeArguments[0] as Class<*>
-    return when (elementType) {
-    // For the types already supported by Config we can use the Iterable as is
-        String::class.java -> this
-        Integer::class.java -> this
-        java.lang.Long::class.java -> this
-        java.lang.Double::class.java -> this
-        java.lang.Boolean::class.java -> this
-        LocalDate::class.java -> map(Any?::toString)
-        Instant::class.java -> map(Any?::toString)
-        NetworkHostAndPort::class.java -> map(Any?::toString)
-        Path::class.java -> map(Any?::toString)
-        URL::class.java -> map(Any?::toString)
-        X500Principal::class.java -> map(Any?::toString)
-        UUID::class.java -> map(Any?::toString)
-        CordaX500Name::class.java -> map(Any?::toString)
-        Properties::class.java -> map { ConfigFactory.parseMap(uncheckedCast(it)).root() }
-        else -> if (elementType.isEnum) {
-            map { (it as Enum<*>).name }
-        } else {
-            map { it?.toConfigMap() }
-        }
-    }
-}
-
-private fun Iterable<*>.toConfigIterable(): Iterable<Any?> = map { element -> element?.let(::convertValue) }
-
 // The typesafe .getBoolean function is case sensitive, this is a case insensitive version
 fun Config.getBooleanCaseInsensitive(path: String): Boolean {
     try {
diff --git a/tools/blobinspector/src/test/kotlin/net/corda/blobinspector/BlobInspectorTest.kt b/tools/blobinspector/src/test/kotlin/net/corda/blobinspector/BlobInspectorTest.kt
index 210a5e9e11..fd00341ea5 100644
--- a/tools/blobinspector/src/test/kotlin/net/corda/blobinspector/BlobInspectorTest.kt
+++ b/tools/blobinspector/src/test/kotlin/net/corda/blobinspector/BlobInspectorTest.kt
@@ -55,7 +55,12 @@ class BlobInspectorTest {
     private fun run(resourceName: String): String {
         blobInspector.source = javaClass.getResource(resourceName)
         val writer = StringWriter()
-        blobInspector.run(PrintStream(WriterOutputStream(writer, UTF_8)))
+        val oStream = WriterOutputStream.builder()
+                .setCharset(UTF_8)
+                .setWriter(writer)
+                .get()
+        blobInspector.run(PrintStream(oStream))
+
         val output = writer.toString()
         println(output)
         return output