diff --git a/common/src/main/java/org/red5/server/net/rtmp/status/RuntimeStatusObject.java b/common/src/main/java/org/red5/server/net/rtmp/status/RuntimeStatusObject.java
index d8e64130ab1506423d9863993264280512a389bf..17d3ec3ef32274a035f4b173049e1f711e0cd76c 100644
--- a/common/src/main/java/org/red5/server/net/rtmp/status/RuntimeStatusObject.java
+++ b/common/src/main/java/org/red5/server/net/rtmp/status/RuntimeStatusObject.java
@@ -7,20 +7,14 @@
 
 package org.red5.server.net.rtmp.status;
 
-import java.io.IOException;
-import java.io.ObjectInput;
-import java.io.ObjectOutput;
+import org.red5.io.amf3.IDataInput;
+import org.red5.io.amf3.IDataOutput;
 
 /**
  * Runtime status object
  */
 public class RuntimeStatusObject extends StatusObject {
 
-    /**
-     * Serializable
-     */
-    private static final long serialVersionUID = 6990998992583246039L;
-
     /**
      * Status event details
      */
@@ -109,14 +103,14 @@ public class RuntimeStatusObject extends StatusObject {
     }
 
     @Override
-    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+    public void readExternal(IDataInput in) {
         super.readExternal(in);
         clientid = in.readInt();
         details = (String) in.readObject();
     }
 
     @Override
-    public void writeExternal(ObjectOutput out) throws IOException {
+    public void writeExternal(IDataOutput out) {
         super.writeExternal(out);
         out.writeInt(clientid);
         out.writeObject(details);
diff --git a/common/src/main/java/org/red5/server/net/rtmp/status/Status.java b/common/src/main/java/org/red5/server/net/rtmp/status/Status.java
index 575290f0e8a49046319f55811a46b363ecc3c017..e240db8d11c3b3f94b8c61fe80350d47ef01ddef 100644
--- a/common/src/main/java/org/red5/server/net/rtmp/status/Status.java
+++ b/common/src/main/java/org/red5/server/net/rtmp/status/Status.java
@@ -7,22 +7,16 @@
 
 package org.red5.server.net.rtmp.status;
 
-import java.io.Externalizable;
-import java.io.IOException;
-import java.io.ObjectInput;
-import java.io.ObjectOutput;
-
 import org.red5.annotations.Anonymous;
-import org.red5.io.object.ICustomSerializable;
-import org.red5.io.object.Output;
+import org.red5.io.amf3.IDataInput;
+import org.red5.io.amf3.IDataOutput;
+import org.red5.io.amf3.IExternalizable;
 
 /**
  * Represents status object that are transferred between server and client
  */
 @Anonymous
-public class Status implements StatusCodes, ICustomSerializable, Externalizable {
-
-    private static final long serialVersionUID = -5501563718489586136L;
+public class Status implements StatusCodes, IExternalizable {
 
     /**
      * Error constant
@@ -206,36 +200,22 @@ public class Status implements StatusCodes, ICustomSerializable, Externalizable
         return "Status: code: " + getCode() + " desc: " + getDescription() + " level: " + getLevel();
     }
 
-    public void serialize(Output output) {
-        output.putString("level");
-        output.writeString(getLevel());
-        output.putString("code");
-        output.writeString(getCode());
-        output.putString("description");
-        output.writeString(getDescription());
-        output.putString("details");
-        if (getDetails() != null) {
-            output.writeString(getDetails());
-        } else {
-            output.writeNull();
-        }
-        output.putString("clientid");
-        output.writeNumber(getClientid());
-    }
-
-    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+    @Override
+    public void readExternal(IDataInput in) {
         clientid = (Number) in.readDouble();
-        code = (String) in.readObject();
-        description = (String) in.readObject();
-        details = (String) in.readObject();
-        level = (String) in.readObject();
+        code = (String) in.readUTF();
+        description = (String) in.readUTF();
+        details = (String) in.readUTF();
+        level = (String) in.readUTF();
     }
 
-    public void writeExternal(ObjectOutput out) throws IOException {
+    @Override
+    public void writeExternal(IDataOutput out) {
         out.writeDouble(clientid.doubleValue());
-        out.writeObject(code);
-        out.writeObject(description);
-        out.writeObject(details);
-        out.writeObject(level);
+        out.writeUTF(code);
+        out.writeUTF(description);
+        out.writeUTF(details);
+        out.writeUTF(level);
     }
+
 }
diff --git a/common/src/main/java/org/red5/server/net/rtmp/status/StatusObject.java b/common/src/main/java/org/red5/server/net/rtmp/status/StatusObject.java
index bab8755c54dda13f3313f64d88530a6132ed232b..be75d993be4336795f9bfec9c0b13574203e2f58 100644
--- a/common/src/main/java/org/red5/server/net/rtmp/status/StatusObject.java
+++ b/common/src/main/java/org/red5/server/net/rtmp/status/StatusObject.java
@@ -7,18 +7,13 @@
 
 package org.red5.server.net.rtmp.status;
 
-import java.io.Externalizable;
-import java.io.IOException;
-import java.io.ObjectInput;
-import java.io.ObjectOutput;
-import java.io.Serializable;
 import java.util.HashMap;
 import java.util.Map;
 
 import org.red5.annotations.Anonymous;
-import org.red5.io.object.ICustomSerializable;
-import org.red5.io.object.Output;
-import org.red5.io.object.Serializer;
+import org.red5.io.amf3.IDataInput;
+import org.red5.io.amf3.IDataOutput;
+import org.red5.io.amf3.IExternalizable;
 
 /**
  * Status object that is sent to client with every status event
@@ -27,9 +22,7 @@ import org.red5.io.object.Serializer;
  * @author Luke Hubbard, Codegent Ltd (luke@codegent.com)
  */
 @Anonymous
-public class StatusObject implements Serializable, ICustomSerializable, Externalizable {
-
-    private static final long serialVersionUID = 8817297676191096283L;
+public class StatusObject implements IExternalizable {
 
     public static final String ERROR = "error";
 
@@ -139,10 +132,14 @@ public class StatusObject implements Serializable, ICustomSerializable, External
         return application;
     }
 
-    /** {@inheritDoc} */
-    @Override
-    public String toString() {
-        return String.format("Status code: %s level: %s description: %s", code, level, description);
+    public void setAdditional(String name, Object value) {
+        if ("code,level,description,application".indexOf(name) != -1) {
+            throw new RuntimeException("the name \"" + name + "\" is reserved");
+        }
+        if (additional == null) {
+            additional = new HashMap<>();
+        }
+        additional.put(name, value);
     }
 
     /**
@@ -154,54 +151,37 @@ public class StatusObject implements Serializable, ICustomSerializable, External
         return new Status(getCode(), getLevel(), getDescription());
     }
 
-    public void setAdditional(String name, Object value) {
-        if ("code,level,description,application".indexOf(name) != -1) {
-            throw new RuntimeException("the name \"" + name + "\" is reserved");
-        }
-        if (additional == null) {
-            additional = new HashMap<String, Object>();
-        }
-        additional.put(name, value);
-    }
-
-    public void serialize(Output output) {
-        output.putString("level");
-        output.writeString(getLevel());
-        output.putString("code");
-        output.writeString(getCode());
-        output.putString("description");
-        output.writeString(getDescription());
-        if (application != null) {
-            output.putString("application");
-            Serializer.serialize(output, application);
-        }
-        if (additional != null) {
-            // Add additional parameters
-            for (Map.Entry<String, Object> entry : additional.entrySet()) {
-                output.putString(entry.getKey());
-                Serializer.serialize(output, entry.getValue());
-            }
-        }
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return String.format("Status code: %s level: %s description: %s", code, level, description);
     }
 
     @SuppressWarnings("unchecked")
-    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
-        code = (String) in.readObject();
-        description = (String) in.readObject();
-        level = (String) in.readObject();
-        additional = (Map<String, Object>) in.readObject();
+    @Override
+    public void readExternal(IDataInput in) {
+        code = (String) in.readUTF();
+        description = (String) in.readUTF();
+        level = (String) in.readUTF();
         application = in.readObject();
+        additional = (Map<String, Object>) in.readObject();
     }
 
-    public void writeExternal(ObjectOutput out) throws IOException {
-        out.writeObject(code);
-        out.writeObject(description);
-        out.writeObject(level);
+    @Override
+    public void writeExternal(IDataOutput out) {
+        out.writeUTF(code);
+        out.writeUTF(description);
+        out.writeUTF(level);
         if (application != null) {
-            out.writeObject(additional);
+            out.writeObject(application);
+        } else {
+            out.writeObject(null);
         }
         if (additional != null) {
-            out.writeObject(application);
+            out.writeObject(additional);
+        } else {
+            out.writeObject(null);
         }
     }
+
 }
diff --git a/common/src/main/java/org/red5/server/net/rtmp/status/StatusObjectService.java b/common/src/main/java/org/red5/server/net/rtmp/status/StatusObjectService.java
index a75e0af22f8b791604a984b7e8e043968d3aaf24..7d17d94fb3cc138a69dbef761985f1cfe4d3d318 100644
--- a/common/src/main/java/org/red5/server/net/rtmp/status/StatusObjectService.java
+++ b/common/src/main/java/org/red5/server/net/rtmp/status/StatusObjectService.java
@@ -46,17 +46,15 @@ public class StatusObjectService implements StatusCodes, InitializingBean {
      * Initialization
      */
     public void afterPropertiesSet() throws Exception {
-        log.trace("Loading status objects");
-        loadStatusObjects();
-        log.trace("Caching status objects");
-        cacheStatusObjects();
+        log.trace("Loading and caching status objects");
+        loadAndCacheStatusObjects();
         log.debug("Status service ready");
     }
 
     /**
-     * Creates all status objects and adds them to status objects map
+     * Creates all status objects and adds them to status objects map; then caches them.
      */
-    public void loadStatusObjects() {
+    public void loadAndCacheStatusObjects() {
         statusObjects = new HashMap<>();
 
         statusObjects.put(NC_CALL_FAILED, new StatusObject(NC_CALL_FAILED, StatusObject.ERROR, ""));
@@ -103,23 +101,15 @@ public class StatusObjectService implements StatusCodes, InitializingBean {
         statusObjects.put(NS_PLAY_FILE_STRUCTURE_INVALID, new StatusObject(NS_PLAY_FILE_STRUCTURE_INVALID, StatusObject.ERROR, ""));
         statusObjects.put(NS_PLAY_NO_SUPPORTED_TRACK_FOUND, new StatusObject(NS_PLAY_NO_SUPPORTED_TRACK_FOUND, StatusObject.ERROR, ""));
 
-    }
-
-    /**
-     * Cache status objects
-     */
-    public void cacheStatusObjects() {
-
-        cachedStatusObjects = new HashMap<String, byte[]>();
-
+        cachedStatusObjects = new HashMap<>();
         String statusCode;
         IoBuffer out = IoBuffer.allocate(256);
         out.setAutoExpand(true);
-
         for (String s : statusObjects.keySet()) {
             statusCode = s;
             StatusObject statusObject = statusObjects.get(statusCode);
-            if (statusObject instanceof RuntimeStatusObject) {
+            if (RuntimeStatusObject.class.isAssignableFrom(statusObject.getClass())) {
+                log.debug("Skip caching runtime status object: {}", statusCode);
                 continue;
             }
             serializeStatusObject(out, statusObject);
@@ -127,7 +117,7 @@ public class StatusObjectService implements StatusCodes, InitializingBean {
             if (log.isTraceEnabled()) {
                 log.trace(HexDump.formatHexDump(out.getHexDump()));
             }
-            byte[] cachedBytes = new byte[out.limit()];
+            byte[] cachedBytes = new byte[out.remaining()];
             out.get(cachedBytes);
             out.clear();
             cachedStatusObjects.put(statusCode, cachedBytes);
diff --git a/common/src/main/java/org/red5/server/service/Call.java b/common/src/main/java/org/red5/server/service/Call.java
index 234ea9f21bea7257d74c3f7895279dc112efb7ba..dedd5a1c96abc7634234b024919fd6f11ac69827 100644
--- a/common/src/main/java/org/red5/server/service/Call.java
+++ b/common/src/main/java/org/red5/server/service/Call.java
@@ -7,12 +7,12 @@
 
 package org.red5.server.service;
 
-import java.io.Externalizable;
-import java.io.IOException;
-import java.io.ObjectInput;
-import java.io.ObjectOutput;
-
+import org.red5.io.amf3.IDataInput;
+import org.red5.io.amf3.IDataOutput;
+import org.red5.io.amf3.IExternalizable;
 import org.red5.server.api.service.IServiceCall;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Basic service call (remote call) implementation
@@ -20,9 +20,9 @@ import org.red5.server.api.service.IServiceCall;
  * @author The Red5 Project
  * @author Luke Hubbard, Codegent Ltd (luke@codegent.com)
  */
-public class Call implements IServiceCall, Externalizable {
+public class Call implements IServiceCall, IExternalizable {
 
-    private static final long serialVersionUID = -3699712251588013875L;
+    private static final Logger log = LoggerFactory.getLogger(Call.class);
 
     /**
      * Pending status constant
@@ -279,25 +279,37 @@ public class Call implements IServiceCall, Externalizable {
         return sb.toString();
     }
 
-    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+    @Override
+    public void readExternal(IDataInput in) {
         // keep a time of receipt
         readTime = System.currentTimeMillis();
         // read-in properties
-        serviceName = (String) in.readObject();
-        serviceMethodName = (String) in.readObject();
-        arguments = (Object[]) in.readObject();
         status = in.readByte();
+        serviceName = (String) in.readUTF();
+        serviceMethodName = (String) in.readUTF();
+        arguments = (Object[]) in.readObject();
+        if (log.isDebugEnabled()) {
+            for (int i = 0; i < arguments.length; i++) {
+                log.debug("ReadExt - Arg: {} type: {} => {}", i, (arguments[i] != null ? arguments[i].getClass().getName() : null), arguments[i]);
+            }
+        }
         exception = (Exception) in.readObject();
     }
 
-    public void writeExternal(ObjectOutput out) throws IOException {
+    @Override
+    public void writeExternal(IDataOutput out) {
         // keep a time of receipt
         writeTime = System.currentTimeMillis();
         // write-out properties
-        out.writeObject(serviceName);
-        out.writeObject(serviceMethodName);
-        out.writeObject(arguments);
         out.writeByte(status);
+        out.writeUTF(serviceName);
+        out.writeUTF(serviceMethodName);
+        if (log.isDebugEnabled()) {
+            for (int i = 0; i < arguments.length; i++) {
+                log.debug("WriteExt - Arg: {} type: {} => {}", i, (arguments[i] != null ? arguments[i].getClass().getName() : null), arguments[i]);
+            }
+        }
+        out.writeObject(arguments);
         out.writeObject(exception);
     }
 }
diff --git a/common/src/main/java/org/red5/server/service/PendingCall.java b/common/src/main/java/org/red5/server/service/PendingCall.java
index d0bb5fd63c954c0d508f7fd9a534329976ce78b4..c83078fe409642e3fbe7903d49e71b61a81dae39 100644
--- a/common/src/main/java/org/red5/server/service/PendingCall.java
+++ b/common/src/main/java/org/red5/server/service/PendingCall.java
@@ -7,12 +7,11 @@
 
 package org.red5.server.service;
 
-import java.io.IOException;
-import java.io.ObjectInput;
-import java.io.ObjectOutput;
 import java.util.HashSet;
 import java.util.Set;
 
+import org.red5.io.amf3.IDataInput;
+import org.red5.io.amf3.IDataOutput;
 import org.red5.server.api.service.IPendingServiceCall;
 import org.red5.server.api.service.IPendingServiceCallback;
 
@@ -21,8 +20,6 @@ import org.red5.server.api.service.IPendingServiceCallback;
  */
 public class PendingCall extends Call implements IPendingServiceCall {
 
-    private static final long serialVersionUID = 3219267601240355335L;
-
     /**
      * Result object
      */
@@ -105,13 +102,13 @@ public class PendingCall extends Call implements IPendingServiceCall {
     }
 
     @Override
-    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+    public void readExternal(IDataInput in) {
         super.readExternal(in);
         result = in.readObject();
     }
 
     @Override
-    public void writeExternal(ObjectOutput out) throws IOException {
+    public void writeExternal(IDataOutput out) {
         super.writeExternal(out);
         out.writeObject(result);
     }
diff --git a/common/src/main/java/org/red5/server/service/ReflectionUtils.java b/common/src/main/java/org/red5/server/service/ReflectionUtils.java
index d65c2eeb5fad6427b04bf50b2eff3107be071b9b..8733586939a19bb665d41e1ceccb62114f250902 100644
--- a/common/src/main/java/org/red5/server/service/ReflectionUtils.java
+++ b/common/src/main/java/org/red5/server/service/ReflectionUtils.java
@@ -45,22 +45,21 @@ public class ReflectionUtils {
      * @param args arguments
      * @return Method/params pairs or null if not found
      */
-    public static Object[] findMethod(Object service, String methodName, List<?> args) {
+    public static Object[] findMethod(Object service, String methodName, List<?> listArgs) {
         if (isDebug) {
-            log.debug("Find method: {} in service: {} args: {}", methodName, service, args);
+            log.debug("Find method: {} in service: {} args: {}", methodName, service, listArgs);
         }
         // return value(s)
         Object[] methodResult = NULL_RETURN;
         // get all the name matched methods once, then filter out the ones that contain a $
-        final Set<Method> methods = Arrays.stream(service.getClass().getMethods()).filter(m -> (m.getName().equals(methodName) && !m.getName().contains("$"))).filter(m -> m.getParameterCount() == 1 || m.getParameterCount() == args.size()).collect(Collectors.toUnmodifiableSet());
-        if (methods.isEmpty()) {
-            // no methods found
-        } else {
+        final Set<Method> methods = Arrays.stream(service.getClass().getMethods()).filter(m -> (m.getName().equals(methodName) && !m.getName().contains("$"))).filter(m -> m.getParameterCount() == 1 || m.getParameterCount() == listArgs.size()).collect(Collectors.toUnmodifiableSet());
+        if (!methods.isEmpty()) {
             if (isDebug) {
                 log.debug("Named method(s) {}: {} found in {}", methods.size(), methodName, service);
             }
+            Object[] args = listArgs.toArray();
             // convert the args to their class types
-            final Class<?>[] callParams = ConversionUtils.convertParams(args.toArray());
+            final Class<?>[] callParams = ConversionUtils.convertParams(args);
             // search for method with matching parameters
             for (Method method : methods) {
                 // track method parameters count
@@ -69,32 +68,36 @@ public class ReflectionUtils {
                     log.trace("Method {} count - parameters: {} args: {}", methodName, paramCount, callParams.length);
                 }
                 // if there are no args nor parameters
-                if ((args == null || args.isEmpty()) && paramCount == 0) {
+                if ((listArgs == null || listArgs.isEmpty()) && paramCount == 0) {
+                    if (isTrace) {
+                        log.trace("Method {} matched - zero-length", methodName);
+                    }
                     // fastest way to handle zero parameter methods
-                    methodResult = new Object[] { method, args };
+                    methodResult = new Object[] { method, listArgs };
                     break;
                 }
                 // get the methods parameter types
                 Class<?>[] paramTypes = method.getParameterTypes();
                 // search for method with List as the first and only parameter
-                if (paramCount == 1 && paramTypes[0].isAssignableFrom(List.class)) {
-                    methodResult = new Object[] { method, List.of(args) };
+                if (paramTypes[0].isAssignableFrom(List.class)) {
+                    if (isTrace) {
+                        log.trace("Method {} matched - parameter 0 is a list", methodName);
+                    }
+                    methodResult = new Object[] { method, listArgs };
                     break;
                 }
                 // search for method matching parameters without a forced connection parameter
-                if (callParams.length == paramCount) {
-                    // search for method with exact parameters
-                    boolean valid = true;
-                    for (int j = 0; j < callParams.length; j++) {
-                        // call params content can be null, param types cannot
-                        if (!paramTypes[j].equals(callParams[j]) && !paramTypes[j].isAssignableFrom(callParams[j]) && callParams[j] != null) {
-                            valid = false;
-                            break;
+                if (paramCount == callParams.length) {
+                    // attempt to convert the args to match the method
+                    try {
+                        Object[] convertedArgs = ConversionUtils.convertParams(args, paramTypes);
+                        if (isTrace) {
+                            log.trace("Method {} matched - parameters: {}", methodName, paramTypes);
                         }
-                    }
-                    if (valid) {
-                        methodResult = new Object[] { method, args };
+                        methodResult = new Object[] { method, convertedArgs };
                         break;
+                    } catch (Exception e) {
+                        log.warn("Method {} not found in {} with parameters {}", methodName, service, Arrays.asList(paramTypes), e);
                     }
                 }
             }
@@ -118,7 +121,7 @@ public class ReflectionUtils {
         // get the arguments
         final Object[] args = call.getArguments();
         // convert the args to their class types
-        final Class<?>[] callParams = ConversionUtils.convertParams(args);
+        Class<?>[] callParams = ConversionUtils.convertParams(args);
         // XXX(paul) someday this will be deprecated as its an extremely legacy feature to have a method with a
         // connection as the first parameter
         // build an array with the incoming args and the current connection as the first element
@@ -128,10 +131,7 @@ public class ReflectionUtils {
             argsWithConnection[0] = conn;
             for (int i = 0; i < args.length; i++) {
                 if (isDebug) {
-                    log.debug("{} => {}", i, args[i]);
-                    if (isTrace && args[i] != null) {
-                        log.trace("Arg type: {}", args[i].getClass().getName());
-                    }
+                    log.debug("Arg: {} type: {} => {}", i, (args[i] != null ? args[i].getClass().getName() : null), args[i]);
                 }
                 argsWithConnection[i + 1] = args[i];
             }
@@ -159,6 +159,9 @@ public class ReflectionUtils {
                 }
                 // if there are no args nor parameters
                 if ((args == null || args.length == 0) && paramCount == 0) {
+                    if (isTrace) {
+                        log.trace("Method {} matched - zero-length", methodName);
+                    }
                     // fastest way to handle zero parameter methods
                     methodResult = new Object[] { method, args };
                     break;
@@ -167,59 +170,43 @@ public class ReflectionUtils {
                 Class<?>[] paramTypes = method.getParameterTypes();
                 // search for method with Object[] as the first and only parameter
                 if (paramCount == 1 && paramTypes[0].isArray()) {
+                    if (isTrace) {
+                        log.trace("Method {} matched - parameter 0 is an array", methodName);
+                    }
                     methodResult = new Object[] { method, args };
                     break;
                 }
-                // search for method with List as the first and only parameter
-                if (paramCount == 1 && paramTypes[0].isAssignableFrom(List.class)) {
-                    methodResult = new Object[] { method, List.of(args) };
-                    break;
-                }
-                // search for method with Set as the first and only parameter
-                if (paramCount == 1 && paramTypes[0].isAssignableFrom(Set.class)) {
-                    methodResult = new Object[] { method, Set.of(args) };
-                    break;
-                }
                 // search for method matching parameters without a forced connection parameter
-                if (callParams.length == paramCount) {
-                    // search for method with exact parameters
-                    boolean valid = true;
-                    for (int j = 0; j < callParams.length; j++) {
-                        // call params content can be null, param types cannot
-                        if (!paramTypes[j].equals(callParams[j]) && !paramTypes[j].isAssignableFrom(callParams[j]) && args[j] != null) {
-                            valid = false;
-                            break;
+                if (paramCount == callParams.length && !paramTypes[0].isAssignableFrom(IConnection.class)) {
+                    // attempt to convert the args to match the method
+                    try {
+                        Object[] convertedArgs = ConversionUtils.convertParams(args, paramTypes);
+                        if (isTrace) {
+                            log.trace("Method {} matched - parameters: {}", methodName, paramTypes);
                         }
-                    }
-                    if (valid) {
-                        methodResult = new Object[] { method, args };
+                        methodResult = new Object[] { method, convertedArgs };
                         break;
+                    } catch (Exception e) {
+                        log.warn("Method {} not found in {} with parameters {}", methodName, service, Arrays.asList(paramTypes), e);
                     }
                 }
-                // search for method with Object[] as the first and only parameter
-                if (paramTypes[0].isArray()) {
-                    methodResult = new Object[] { method, args };
-                    break;
-                }
                 // lastly try with connection at position 0 in parameters
-                if (conn != null) {
-                    // search for method with exact parameters
-                    boolean valid = true;
-                    for (int j = 0; j < argsWithConnection.length; j++) {
-                        // call params content can be null, param types cannot
-                        if (!paramTypes[j].equals(argsWithConnection[j].getClass()) && !paramTypes[j].isAssignableFrom(argsWithConnection[j].getClass()) && argsWithConnection[j] != null) {
-                            valid = false;
-                            break;
+                if (conn != null && paramCount == (callParams.length + 1) && paramTypes[0].isAssignableFrom(IConnection.class)) {
+                    // attempt to convert the args to match the method
+                    try {
+                        Object[] convertedArgs = ConversionUtils.convertParams(argsWithConnection, paramTypes);
+                        if (isTrace) {
+                            log.trace("Method {} matched - parameters: {}", methodName, paramTypes);
                         }
-                    }
-                    if (valid) {
-                        methodResult = new Object[] { method, args };
+                        methodResult = new Object[] { method, convertedArgs };
                         break;
+                    } catch (Exception e) {
+                        log.warn("Method {} not found in {} with parameters {}", methodName, service, Arrays.asList(paramTypes), e);
                     }
                 }
             }
             if (methodResult[0] == null) {
-                log.warn("Method {} not found in {} with parameters {}", methodName, service, Arrays.asList(args));
+                log.warn("Method {} not found in {} with parameters {}", methodName, service, Arrays.asList(callParams));
                 call.setStatus(Call.STATUS_METHOD_NOT_FOUND);
                 call.setException(new MethodNotFoundException(methodName, args));
             }
diff --git a/io/src/main/java/org/red5/io/amf/Input.java b/io/src/main/java/org/red5/io/amf/Input.java
index e0b9bbe963a5900d72a2dc4fbf377d4dc4221178..9bc832642bb89d37aae670dcbccba83105e4ef13 100644
--- a/io/src/main/java/org/red5/io/amf/Input.java
+++ b/io/src/main/java/org/red5/io/amf/Input.java
@@ -92,7 +92,7 @@ public class Input extends BaseInput implements org.red5.io.object.Input {
             do {
                 // get the data type
                 currentDataType = buf.get();
-                log.trace("Data type: {}", currentDataType);
+                log.trace("Data type {}: {}", currentDataType, DataTypes.toStringValue(currentDataType));
                 switch (currentDataType) {
                     case AMF.TYPE_NULL:
                     case AMF.TYPE_UNDEFINED:
diff --git a/io/src/main/java/org/red5/io/amf/Output.java b/io/src/main/java/org/red5/io/amf/Output.java
index 02844cee114238896dfb0c97b137806449c696a6..7850abe2616e5cc371d3791d6377be1b800b89de 100644
--- a/io/src/main/java/org/red5/io/amf/Output.java
+++ b/io/src/main/java/org/red5/io/amf/Output.java
@@ -12,6 +12,7 @@ import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.nio.ByteBuffer;
 import java.nio.file.Paths;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Date;
 import java.util.HashMap;
@@ -25,7 +26,6 @@ import org.apache.mina.core.buffer.IoBuffer;
 import org.red5.annotations.Anonymous;
 import org.red5.io.amf3.ByteArray;
 import org.red5.io.object.BaseOutput;
-import org.red5.io.object.ICustomSerializable;
 import org.red5.io.object.RecordSet;
 import org.red5.io.object.Serializer;
 import org.red5.io.utils.XMLUtils;
@@ -110,13 +110,14 @@ public class Output extends BaseOutput implements org.red5.io.object.Output {
         if (hasReference(obj)) {
             writeReference(obj);
             return true;
-        } else
-            return false;
+        }
+        return false;
     }
 
     /** {@inheritDoc} */
     @Override
     public void writeArray(Collection<?> array) {
+        log.debug("writeArray - (collection source) array: {}", array);
         if (!checkWriteReference(array)) {
             storeReference(array);
             buf.put(AMF.TYPE_ARRAY);
@@ -130,7 +131,7 @@ public class Output extends BaseOutput implements org.red5.io.object.Output {
     /** {@inheritDoc} */
     @Override
     public void writeArray(Object[] array) {
-        log.debug("writeArray - array: {}", array);
+        log.debug("writeArray - (array source) array: {}", Arrays.asList(array));
         if (array != null) {
             if (!checkWriteReference(array)) {
                 storeReference(array);
@@ -148,12 +149,14 @@ public class Output extends BaseOutput implements org.red5.io.object.Output {
     /** {@inheritDoc} */
     @Override
     public void writeArray(Object array) {
+        log.debug("writeArray - (object source) array: {}", array);
         if (array != null) {
             if (!checkWriteReference(array)) {
                 storeReference(array);
                 buf.put(AMF.TYPE_ARRAY);
-                buf.putInt(Array.getLength(array));
-                for (int i = 0; i < Array.getLength(array); i++) {
+                final int length = Array.getLength(array);
+                buf.putInt(length);
+                for (int i = 0; i < length; i++) {
                     Serializer.serialize(this, Array.get(array, i));
                 }
             }
@@ -307,11 +310,11 @@ public class Output extends BaseOutput implements org.red5.io.object.Output {
             } else {
                 buf.put(AMF.TYPE_OBJECT);
             }
-            if (object instanceof ICustomSerializable) {
-                ((ICustomSerializable) object).serialize(this);
-                buf.put(AMF.END_OF_OBJECT_SEQUENCE);
-                return;
-            }
+            // if (object instanceof ICustomSerializable) {
+            //     ((ICustomSerializable) object).serialize(this);
+            //     buf.put(AMF.END_OF_OBJECT_SEQUENCE);
+            //     return;
+            // }
             // Iterate thru entries and write out property names with separators
             for (Object key : attrs) {
                 String fieldName = key.toString();
@@ -405,6 +408,7 @@ public class Output extends BaseOutput implements org.red5.io.object.Output {
             buf.put(AMF.TYPE_OBJECT);
             boolean isBeanMap = (map instanceof BeanMap);
             for (Map.Entry<Object, Object> entry : map.entrySet()) {
+                log.debug("Key: {} item: {}", entry.getKey(), entry.getValue());
                 if (isBeanMap && "class".equals(entry.getKey())) {
                     continue;
                 }
diff --git a/io/src/main/java/org/red5/io/amf3/DataInput.java b/io/src/main/java/org/red5/io/amf3/DataInput.java
index 1a3eaa366642369c64920a2cf5ca6321f9fcceb4..3f1933c32b01aa58cc05e1c691584d82a83a52a0 100644
--- a/io/src/main/java/org/red5/io/amf3/DataInput.java
+++ b/io/src/main/java/org/red5/io/amf3/DataInput.java
@@ -140,13 +140,13 @@ public class DataInput implements IDataInput {
     /** {@inheritDoc} */
     @Override
     public int readUnsignedShort() {
-        return buffer.getShort() & 0xffff; //buffer.getUnsignedShort();
+        return buffer.getShort() & 0xffff;
     }
 
     /** {@inheritDoc} */
     @Override
     public String readUTF() {
-        int length = buffer.getShort() & 0xffff; //buffer.getUnsignedShort();
+        int length = buffer.getShort() & 0xffff;
         return readUTFBytes(length);
     }
 
diff --git a/io/src/main/java/org/red5/io/amf3/Input.java b/io/src/main/java/org/red5/io/amf3/Input.java
index 5db3e6b3d417290851848a7b0b627c3badfdbb77..2e1bd6918c598e178ae171613c35d63a882db308 100644
--- a/io/src/main/java/org/red5/io/amf3/Input.java
+++ b/io/src/main/java/org/red5/io/amf3/Input.java
@@ -51,7 +51,7 @@ import org.w3c.dom.Document;
  * @author Luke Hubbard, Codegent Ltd (luke@codegent.com)
  * @author Joachim Bauch (jojo@struktur.de)
  */
-public class Input extends org.red5.io.amf.Input implements org.red5.io.object.Input {
+public class Input extends org.red5.io.amf.Input {
 
     /**
      * Holds informations about already deserialized classes.
@@ -513,6 +513,7 @@ public class Input extends org.red5.io.amf.Input implements org.red5.io.object.I
                 collection = (Class) target;
             }
             if (collection.isArray()) {
+                log.debug("Using an array for key: {} count: {}", key, count);
                 nested = ArrayUtils.getGenericType(collection.getComponentType());
                 result = Array.newInstance(nested, count);
                 storeReference(result);
@@ -526,6 +527,7 @@ public class Input extends org.red5.io.amf.Input implements org.red5.io.object.I
                 } else if (Set.class.isAssignableFrom(collection)) {
                     resultCollection = new HashSet(count);
                 } else {
+                    log.debug("Using an ArrayList for key: {} count: {}", key, count);
                     resultCollection = new ArrayList(count);
                 }
                 result = resultCollection;
diff --git a/io/src/main/java/org/red5/io/amf3/Output.java b/io/src/main/java/org/red5/io/amf3/Output.java
index c9f8ae25c873a09823061891147c5eeebd431944..8d10c0bed89e932124350c49f4284c8a18c7eb76 100644
--- a/io/src/main/java/org/red5/io/amf3/Output.java
+++ b/io/src/main/java/org/red5/io/amf3/Output.java
@@ -44,7 +44,7 @@ import org.w3c.dom.Document;
  * @author Joachim Bauch (jojo@struktur.de)
  * @author Harald Radi (harald.radi@nme.at)
  */
-public class Output extends org.red5.io.amf.Output implements org.red5.io.object.Output {
+public class Output extends org.red5.io.amf.Output {
 
     protected static Logger log = LoggerFactory.getLogger(Output.class);
 
@@ -271,7 +271,6 @@ public class Output extends org.red5.io.amf.Output implements org.red5.io.object
     private void writePrimitiveByteArray(byte[] bytes) {
         writeAMF3();
         this.buf.put(AMF3.TYPE_BYTEARRAY);
-
         if (hasReference(bytes)) {
             putInteger(getReferenceId(bytes) << 1);
             return;
@@ -279,7 +278,6 @@ public class Output extends org.red5.io.amf.Output implements org.red5.io.object
         storeReference(bytes);
         int length = bytes.length;
         putInteger(length << 1 | 0x1);
-
         this.buf.put(bytes);
     }
 
@@ -436,7 +434,8 @@ public class Output extends org.red5.io.amf.Output implements org.red5.io.object
         }
         log.debug("Storing object reference");
         storeReference(object);
-        if (object instanceof IExternalizable) {
+        Class<?> objectClass = object.getClass();
+        if (IExternalizable.class.isAssignableFrom(objectClass)) {
             log.debug("Object is IExternalizable");
             // the object knows how to serialize itself
             int type = 1 << 1 | 1;
@@ -446,7 +445,7 @@ public class Output extends org.red5.io.amf.Output implements org.red5.io.object
                 type |= AMF3.TYPE_OBJECT_EXTERNALIZABLE << 2;
             }
             putInteger(type);
-            putString(Serializer.getClassName(object.getClass()));
+            putString(Serializer.getClassName(objectClass));
             amf3_mode += 1;
             ((IExternalizable) object).writeExternal(new DataOutput(this));
             amf3_mode -= 1;
@@ -467,10 +466,9 @@ public class Output extends org.red5.io.amf.Output implements org.red5.io.object
             return;
         }
         // write out either start of object marker for class name or "empty" start of object marker
-        Class<?> objectClass = object.getClass();
         if (!objectClass.isAnnotationPresent(Anonymous.class)) {
             log.debug("Object is annotated as Anonymous");
-            putString(Serializer.getClassName(object.getClass()));
+            putString(Serializer.getClassName(objectClass));
         } else {
             putString("");
         }
diff --git a/io/src/main/java/org/red5/io/object/Deserializer.java b/io/src/main/java/org/red5/io/object/Deserializer.java
index 1481a045358b163acf217ee6c8dfd8252114b2d0..a9a870057ecdc110b236afe2ecb5272e92dfb9b9 100644
--- a/io/src/main/java/org/red5/io/object/Deserializer.java
+++ b/io/src/main/java/org/red5/io/object/Deserializer.java
@@ -69,9 +69,7 @@ public class Deserializer {
         }
         byte type = in.readDataType();
         if (log.isTraceEnabled()) {
-            log.trace("Type: {} target: {}", type, (target != null ? target.toString() : "Target not specified"));
-        } else if (log.isDebugEnabled()) {
-            log.debug("Datatype: {}", DataTypes.toStringValue(type));
+            log.trace("Type {}: {} target: {}", type, DataTypes.toStringValue(type), (target != null ? target.toString() : "Target not specified"));
         }
         Object result = null;
         switch (type) {
diff --git a/io/src/main/java/org/red5/io/object/Serializer.java b/io/src/main/java/org/red5/io/object/Serializer.java
index 4ffd346ac553d9b6d2739c922373151447741531..4843fadb474bc00fd16d701ec1f657a8f6d909c9 100644
--- a/io/src/main/java/org/red5/io/object/Serializer.java
+++ b/io/src/main/java/org/red5/io/object/Serializer.java
@@ -48,11 +48,11 @@ public class Serializer {
      *
      * @param out
      *            Output writer
-     * @param any
+     * @param obj
      *            Object to serialize
      */
-    public static void serialize(Output out, Object any) {
-        Serializer.serialize(out, null, null, null, any);
+    public static void serialize(Output out, Object obj) {
+        Serializer.serialize(out, null, null, null, obj);
     }
 
     /**
@@ -64,60 +64,85 @@ public class Serializer {
      *            The field to serialize
      * @param getter
      *            The getter method if not a field
-     * @param object
+     * @param parent
      *            Parent object
-     * @param value
+     * @param obj
      *            Object to serialize
      */
     @SuppressWarnings("unchecked")
-    public static void serialize(Output out, Field field, Method getter, Object object, Object value) {
-        log.trace("serialize");
-        if (value instanceof IExternalizable) {
+    public static void serialize(Output out, Field field, Method getter, Object parent, Object obj) {
+        log.trace("serialize: {}", obj);
+        if (obj == null) {
+            out.writeNull();
+        } else if (IExternalizable.class.isAssignableFrom(obj.getClass())) {
             // make sure all IExternalizable objects are serialized as objects
-            out.writeObject(value);
-        } else if (value instanceof ByteArray) {
+            log.trace("write externalizable: {}", obj);
+            out.writeObject(obj);
+        } else if (obj instanceof ByteArray) {
             // write ByteArray objects directly
-            out.writeByteArray((ByteArray) value);
-        } else if (value instanceof Vector) {
-            log.trace("Serialize Vector");
-            // scan the vector to determine the generic type
-            Vector<?> vector = (Vector<?>) value;
-            int ints = 0;
-            int longs = 0;
-            int dubs = 0;
-            int nans = 0;
-            for (Object o : vector) {
-                if (o instanceof Integer) {
-                    ints++;
-                } else if (o instanceof Long) {
-                    longs++;
-                } else if (o instanceof Number || o instanceof Double) {
-                    dubs++;
+            out.writeByteArray((ByteArray) obj);
+        } else if (out.isCustom(obj)) {
+            log.trace("write custom: {}", obj);
+            // Write custom data
+            out.writeCustom(obj);
+        } else {
+            if (writeBasic(out, obj)) {
+                log.trace("Wrote as basic");
+            } else if (obj.getClass().isArray() && obj.getClass().getComponentType().isPrimitive()) {
+                log.trace("write array: {}", obj);
+                out.writeArray(obj);
+            } else if (obj instanceof Object[]) {
+                log.trace("write object array: {}", obj);
+                out.writeArray((Object[]) obj);
+            } else if (obj instanceof Collection) {
+                log.trace("write collection");
+                out.writeArray((Collection<Object>) obj);
+            } else if (obj instanceof List<?>) {
+                log.trace("write list");
+                writeList(out, (List<?>) obj);
+            } else if (obj instanceof Document) {
+                writeDocument(out, (Document) obj);
+            } else if (obj instanceof Vector) {
+                log.trace("write vector");
+                // scan the vector to determine the generic type
+                Vector<?> vector = (Vector<?>) obj;
+                int ints = 0;
+                int longs = 0;
+                int dubs = 0;
+                int nans = 0;
+                for (Object o : vector) {
+                    if (o instanceof Integer) {
+                        ints++;
+                    } else if (o instanceof Long) {
+                        longs++;
+                    } else if (o instanceof Number || o instanceof Double) {
+                        dubs++;
+                    } else {
+                        nans++;
+                    }
+                }
+                // look at the type counts
+                if (nans > 0) {
+                    // if we have non-number types, use object
+                    ((org.red5.io.amf3.Output) out).enforceAMF3();
+                    out.writeVectorObject((Vector<Object>) obj);
+                } else if (dubs == 0 && longs == 0) {
+                    // no doubles or longs
+                    out.writeVectorInt((Vector<Integer>) obj);
+                } else if (dubs == 0 && ints == 0) {
+                    // no doubles or ints
+                    out.writeVectorUInt((Vector<Long>) obj);
                 } else {
-                    nans++;
+                    // handle any other types of numbers
+                    ((org.red5.io.amf3.Output) out).enforceAMF3();
+                    out.writeVectorNumber((Vector<Double>) obj);
                 }
-            }
-            // look at the type counts
-            if (nans > 0) {
-                // if we have non-number types, use object
-                ((org.red5.io.amf3.Output) out).enforceAMF3();
-                out.writeVectorObject((Vector<Object>) value);
-            } else if (dubs == 0 && longs == 0) {
-                // no doubles or longs
-                out.writeVectorInt((Vector<Integer>) value);
-            } else if (dubs == 0 && ints == 0) {
-                // no doubles or ints
-                out.writeVectorUInt((Vector<Long>) value);
+            } else if (obj instanceof Iterator) {
+                writeIterator(out, (Iterator<Object>) obj);
+            } else if (writeObjectType(out, obj)) {
+                log.trace("Wrote as object type");
             } else {
-                // handle any other types of numbers
-                ((org.red5.io.amf3.Output) out).enforceAMF3();
-                out.writeVectorNumber((Vector<Double>) value);
-            }
-        } else {
-            if (writeBasic(out, value)) {
-                log.trace("Wrote as basic");
-            } else if (!writeComplex(out, value)) {
-                log.trace("Unable to serialize: {}", value);
+                log.trace("Unable to serialize: {}", obj);
             }
         }
     }
@@ -151,32 +176,6 @@ public class Serializer {
         return true;
     }
 
-    /**
-     * Writes a complex type object out
-     *
-     * @param out
-     *            Output writer
-     * @param complex
-     *            Complex datatype object
-     * @return boolean true if object was successfully serialized, false otherwise
-     */
-    public static boolean writeComplex(Output out, Object complex) {
-        log.trace("writeComplex");
-        if (writeListType(out, complex)) {
-            return true;
-        } else if (writeArrayType(out, complex)) {
-            return true;
-        } else if (writeXMLType(out, complex)) {
-            return true;
-        } else if (writeCustomType(out, complex)) {
-            return true;
-        } else if (writeObjectType(out, complex)) {
-            return true;
-        } else {
-            return false;
-        }
-    }
-
     /**
      * Writes Lists out as a data type
      *
@@ -241,15 +240,15 @@ public class Serializer {
      */
     @SuppressWarnings("all")
     protected static boolean writeArrayType(Output out, Object arrType) {
-        log.trace("writeArrayType");
-        if (arrType instanceof Collection) {
-            out.writeArray((Collection<Object>) arrType);
-        } else if (arrType instanceof Iterator) {
-            writeIterator(out, (Iterator<Object>) arrType);
-        } else if (arrType.getClass().isArray() && arrType.getClass().getComponentType().isPrimitive()) {
+        log.trace("writeArrayType: {}", arrType);
+        if (arrType.getClass().isArray() && arrType.getClass().getComponentType().isPrimitive()) {
             out.writeArray(arrType);
         } else if (arrType instanceof Object[]) {
             out.writeArray((Object[]) arrType);
+        } else if (arrType instanceof Collection) {
+            out.writeArray((Collection<Object>) arrType);
+        } else if (arrType instanceof Iterator) {
+            writeIterator(out, (Iterator<Object>) arrType);
         } else {
             return false;
         }
@@ -318,6 +317,7 @@ public class Serializer {
      */
     @SuppressWarnings("all")
     protected static boolean writeObjectType(Output out, Object obj) {
+        log.trace("writeObjectType: {} {}", obj.getClass().getName(), obj);
         if (obj instanceof ObjectMap || obj instanceof BeanMap) {
             out.writeObject((Map) obj);
         } else if (obj instanceof Map) {
@@ -330,25 +330,6 @@ public class Serializer {
         return true;
     }
 
-    /**
-     * Writes a custom data to the output
-     *
-     * @param out
-     *            Output writer
-     * @param obj
-     *            Custom data
-     * @return true if the object has been written, otherwise false
-     */
-    protected static boolean writeCustomType(Output out, Object obj) {
-        if (out.isCustom(obj)) {
-            // Write custom data
-            out.writeCustom(obj);
-            return true;
-        } else {
-            return false;
-        }
-    }
-
     /**
      * Checks whether the field should be serialized or not
      *
diff --git a/io/src/main/java/org/red5/io/utils/ConversionUtils.java b/io/src/main/java/org/red5/io/utils/ConversionUtils.java
index cec9178f8f0e427af2511a932921327f303db40d..6970568d824e4edf760f91f2acee252cacce3ecc 100644
--- a/io/src/main/java/org/red5/io/utils/ConversionUtils.java
+++ b/io/src/main/java/org/red5/io/utils/ConversionUtils.java
@@ -312,19 +312,14 @@ public class ConversionUtils {
      * @return Array of converted objects
      */
     public static Class<?>[] convertParams(Object[] source) {
-        Class<?>[] converted = new Class<?>[0];
+        Class<?>[] converted = source != null ? new Class<?>[source.length] : new Class<?>[0];
         if (source != null) {
-            converted = new Class<?>[source.length];
             for (int i = 0; i < source.length; i++) {
-                if (source[i] != null) {
-                    converted[i] = source[i].getClass();
-                } else {
-                    converted[i] = null;
-                }
+                converted[i] = source[i] != null ? source[i].getClass() : null;
+            }
+            if (log.isTraceEnabled()) {
+                log.trace("Converted parameters: {}", Arrays.toString(converted));
             }
-        }
-        if (log.isTraceEnabled()) {
-            log.trace("Converted parameters: {}", Arrays.toString(converted));
         }
         return converted;
     }
diff --git a/server/src/test/java/org/red5/io/utils/ConversionUtilsTest.java b/server/src/test/java/org/red5/io/utils/ConversionUtilsTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..7c3674dd3e4073ec9068c44afef2c428f4b408f7
--- /dev/null
+++ b/server/src/test/java/org/red5/io/utils/ConversionUtilsTest.java
@@ -0,0 +1,182 @@
+/*
+ * RED5 Open Source Media Server - https://github.com/Red5/ Copyright 2006-2023 by respective authors (see below). All rights reserved. Licensed under the Apache License, Version
+ * 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless
+ * required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions and limitations under the License.
+ */
+
+package org.red5.io.utils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.beanutils.ConversionException;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author The Red5 Project
+ * @author Luke Hubbard, Codegent Ltd (luke@codegent.com)
+ */
+public class ConversionUtilsTest {
+
+    private static final Logger log = LoggerFactory.getLogger(ConversionUtilsTest.class);
+
+    class TestJavaBean {
+
+    }
+
+    @Test
+    public void testBasic() {
+        Object result = ConversionUtils.convert(Integer.valueOf(42), String.class);
+        if (!(result instanceof String)) {
+            fail("Should be a string");
+        }
+        String str = (String) result;
+        assertEquals("42", str);
+    }
+
+    @Test
+    public void testConvertListToStringArray() {
+        ArrayList<String> source = new ArrayList<String>();
+
+        source.add("Testing 1");
+        source.add("Testing 2");
+        source.add("Testing 3");
+
+        Class<? extends String[]> target = (new String[0]).getClass();
+
+        Object result = ConversionUtils.convert(source, target);
+        if (!(result.getClass().isArray() && result.getClass().getComponentType().equals(String.class))) {
+            fail("Should be String[]");
+        }
+        String[] results = (String[]) result;
+
+        assertEquals(results.length, source.size());
+        assertEquals(results[2], source.get(2));
+
+    }
+
+    @Test
+    public void testConvertListToPrimitiveArray() {
+        List<Integer> source = new ArrayList<Integer>();
+        source.add(1);
+        source.add(2);
+        source.add(3);
+
+        Class<? extends int[]> target = (new int[0]).getClass();
+
+        Object result = ConversionUtils.convert(source, target);
+        if (!(result.getClass().isArray() && result.getClass().getComponentType().equals(int.class))) {
+            fail("Should be int[]");
+        }
+        int[] results = (int[]) result;
+
+        assertEquals(results.length, source.size());
+        assertEquals(results[2], source.get(2).intValue());
+    }
+
+    @Test
+    public void testConvertObjectArrayToStringArray() {
+        Object[] source = new Object[3];
+
+        source[0] = Integer.valueOf(21);
+        source[1] = Boolean.FALSE;
+        source[2] = "Woot";
+
+        Class<? extends String[]> target = (new String[0]).getClass();
+
+        Object result = ConversionUtils.convert(source, target);
+        if (!(result.getClass().isArray() && result.getClass().getComponentType().equals(String.class))) {
+            fail("Should be String[]");
+        }
+        String[] results = (String[]) result;
+
+        assertEquals(results.length, source.length);
+        assertEquals(results[2], source[2]);
+
+    }
+
+    @Test
+    public void testConvertToSet() {
+        Object[] source = new Object[3];
+        source[0] = Integer.valueOf(21);
+        source[1] = Boolean.FALSE;
+        source[2] = "Woot";
+        Object result = ConversionUtils.convert(source, Set.class);
+        if (!(result instanceof Set<?>)) {
+            fail("Should be a set");
+        }
+        Set<?> results = (Set<?>) result;
+        assertEquals(results.size(), source.length);
+
+    }
+
+    @Test
+    public void testConvertArrayListToSet() {
+        List<String> source = new ArrayList<String>(3);
+        source.add("a");
+        source.add("b");
+        source.add("c");
+        Object result = ConversionUtils.convert(source, Set.class);
+        if (!(result instanceof Set<?>)) {
+            fail("Should be a set");
+        }
+        Set<?> results = (Set<?>) result;
+        assertEquals(source.size(), results.size());
+    }
+
+    @Test
+    public void testNoOppConvert() {
+        TestJavaBean source = new TestJavaBean();
+        Object result = ConversionUtils.convert(source, TestJavaBean.class);
+        assertEquals(result, source);
+    }
+
+    @Test
+    public void testNullConvert() {
+        Object result = ConversionUtils.convert(null, TestJavaBean.class);
+        assertNull(result);
+    }
+
+    @Test(expected = ConversionException.class)
+    public void testNullConvertNoClass() {
+        // should throw exception
+        ConversionUtils.convert(new TestJavaBean(), null);
+    }
+
+    @Test
+    public void testObjectArray() {
+        Object[] args = new Object[] { new Object[] { "test", 42 }, new Object[] { "blllllaaahhh" } };
+        // convert to object array (known / expected type)
+        Object result = ConversionUtils.convert(args, Object[].class);
+        if (!(result instanceof Object[])) {
+            fail("Should be a Object[]");
+        }
+        Object[] ret = (Object[]) result;
+        assertTrue(Arrays.equals(args, ret));
+        // convert to object array (unknown type)
+        Class<?>[] resultTypes = ConversionUtils.convertParams(args);
+        if (Object[].class.isAssignableFrom(resultTypes[0])) {
+            log.info("Result type: {}", resultTypes[0]);
+            assertTrue(true);
+            if (Object[].class.isAssignableFrom(resultTypes[1])) {
+                log.info("Result type 2: {}", resultTypes[1]);
+                assertTrue(true);
+            } else {
+                fail("Should be a Object[] for param 1");
+            }
+        } else {
+            fail("Should be a Object[] for params");
+        }
+    }
+
+}
diff --git a/server/src/test/java/org/red5/server/service/ReflectionUtilsTest.java b/server/src/test/java/org/red5/server/service/ReflectionUtilsTest.java
index 802351721fd763b5531ce3c04961d66b1f58006b..5f66ee3a6791622ca249d5985963371adcca4e6f 100644
--- a/server/src/test/java/org/red5/server/service/ReflectionUtilsTest.java
+++ b/server/src/test/java/org/red5/server/service/ReflectionUtilsTest.java
@@ -51,16 +51,6 @@ public class ReflectionUtilsTest {
             log.info("Result with no-args: {}", Arrays.asList(result));
         }
 
-        // call with two parameters string and int
-        call = new PendingCall("TestService.doTestObjectArray", new Object[] { "test", 42 });
-        methodName = "doTestObjectArray";
-        result = ReflectionUtils.findMethod(conn, call, service, methodName);
-        if (result[0] == null) {
-            log.info("Result is null");
-            fail("Result is null, method not found");
-        } else {
-            log.info("Result with Object array of string and int: {}", Arrays.asList(result));
-        }
         // call with two parameters string and int but expecting Connection as first parameter to hit on 2nd call
         methodName = "doTest";
         call = new PendingCall("TestService.doTest", new Object[] { "test", 42 });
@@ -83,6 +73,28 @@ public class ReflectionUtilsTest {
         } else {
             log.info("Result with list: {}", Arrays.asList(result));
         }
+
+        // call with two parameters string and int
+        call = new PendingCall("TestService.doTestObjectArray", new Object[] { "test", 42 });
+        methodName = "doTestObjectArray";
+        result = ReflectionUtils.findMethod(conn, call, service, methodName);
+        if (result[0] == null) {
+            log.info("Result is null");
+            fail("Result is null, method not found");
+        } else {
+            log.info("Result with Object array of string and int: {}", Arrays.asList(result));
+        }
+
+        // call with two parameters string and int
+        call = new PendingCall("TestService.doTestObjectArray", new Object[] { new Object[] { "test", 42 }, new Object[] { "blllllaaahhh" } });
+        methodName = "doTestObjectArray";
+        result = ReflectionUtils.findMethod(conn, call, service, methodName);
+        if (result[0] == null) {
+            log.info("Result is null");
+            fail("Result is null, method not found");
+        } else {
+            log.info("Result with Object array of string and int: {}", Arrays.asList(result));
+        }
     }
 
     private class DummyConnection extends RTMPMinaConnection {
@@ -126,6 +138,11 @@ public class ReflectionUtilsTest {
             log.info("doTestObjectArray - Object array: {}", Arrays.asList(param));
         }
 
+        // simple method generically taking an object array
+        public void doTestObjectArray(Object[] param0, Object[] param1) {
+            log.info("doTestObjectArray - Object array(s): {} {}", Arrays.asList(param0), Arrays.asList(param1));
+        }
+
     }
 
 }