Repository: scalecube/scalecube-services
Issue: Client-sdk doesn't parse type with nested generics

For example `java.util.List<io.scalecube.FetchResponse>>` throws exception

Changed files:
services-gateway-tests/src/test/java/io/scalecube/services/gateway/http/HttpLocalGatewayTest.java
services-gateway-clientsdk/src/main/java/io/scalecube/services/gateway/clientsdk/ClientCodec.java
services-api/src/main/java/io/scalecube/services/transport/api/DataCodec.java
services-gateway-tests/src/test/java/io/scalecube/services/gateway/rsocket/RSocketGatewayTest.java
services/src/main/java/io/scalecube/services/ServiceCall.java
services-api/src/main/java/io/scalecube/services/transport/api/ServiceMessageCodec.java
services/src/main/java/io/scalecube/services/Reflect.java
services-examples/src/main/java/io/scalecube/services/examples/GreetingService.java
services-transport-protostuff/src/main/java/io/scalecube/services/transport/protostuff/ProtostuffCodec.java
services-examples/src/main/java/io/scalecube/services/examples/GreetingServiceImpl.java
services-gateway-tests/src/test/java/io/scalecube/services/gateway/http/HttpGatewayTest.java
services-gateway-clientsdk/src/main/java/io/scalecube/services/gateway/clientsdk/Reflect.java
services-gateway-clientsdk/src/main/java/io/scalecube/services/gateway/clientsdk/RemoteInvocationHandler.java
services-transport-jackson/src/main/java/io/scalecube/services/transport/jackson/JacksonCodec.java
services-api/src/main/java/io/scalecube/services/methods/MethodInfo.java
services-gateway-tests/src/test/java/io/scalecube/services/gateway/websocket/WebsocketGatewayTest.java
services-gateway-tests/src/test/java/io/scalecube/services/gateway/websocket/WebsocketLocalGatewayTest.java
services-gateway-tests/src/test/java/io/scalecube/services/gateway/rsocket/RSocketLocalGatewayTest.java
services-examples/src/main/java/io/scalecube/services/examples/GreetingServiceCancelCallback.java

diff --git a/services-api/src/main/java/io/scalecube/services/methods/MethodInfo.java b/services-api/src/main/java/io/scalecube/services/methods/MethodInfo.java
index 0fcdb9fb..2d6c4463 100644
--- a/services-api/src/main/java/io/scalecube/services/methods/MethodInfo.java
+++ b/services-api/src/main/java/io/scalecube/services/methods/MethodInfo.java
@@ -3,13 +3,14 @@ package io.scalecube.services.methods;
 import io.scalecube.services.CommunicationMode;
 import io.scalecube.services.api.Qualifier;
 import io.scalecube.services.api.ServiceMessage;
+import java.lang.reflect.Type;
 
 public final class MethodInfo {
 
   private final String serviceName;
   private final String methodName;
   private final String qualifier;
-  private final Class<?> parameterizedReturnType;
+  private final Type parameterizedReturnType;
   private final CommunicationMode communicationMode;
   private final int parameterCount;
   private final Class<?> requestType;
@@ -27,7 +28,7 @@ public final class MethodInfo {
   public MethodInfo(
       String serviceName,
       String methodName,
-      Class<?> parameterizedReturnType,
+      Type parameterizedReturnType,
       CommunicationMode communicationMode,
       int parameterCount,
       Class<?> requestType) {
@@ -53,7 +54,7 @@ public final class MethodInfo {
     return qualifier;
   }
 
-  public Class<?> parameterizedReturnType() {
+  public Type parameterizedReturnType() {
     return parameterizedReturnType;
   }
 
diff --git a/services-api/src/main/java/io/scalecube/services/transport/api/DataCodec.java b/services-api/src/main/java/io/scalecube/services/transport/api/DataCodec.java
index 1d62b68c..7b9bd9ab 100644
--- a/services-api/src/main/java/io/scalecube/services/transport/api/DataCodec.java
+++ b/services-api/src/main/java/io/scalecube/services/transport/api/DataCodec.java
@@ -4,6 +4,7 @@ import io.scalecube.services.ServiceLoaderUtil;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.lang.reflect.Type;
 import java.util.Collection;
 import java.util.Map;
 import java.util.function.Function;
@@ -41,5 +42,5 @@ public interface DataCodec {
 
   void encode(OutputStream stream, Object value) throws IOException;
 
-  Object decode(InputStream stream, Class<?> type) throws IOException;
+  Object decode(InputStream stream, Type type) throws IOException;
 }
diff --git a/services-api/src/main/java/io/scalecube/services/transport/api/ServiceMessageCodec.java b/services-api/src/main/java/io/scalecube/services/transport/api/ServiceMessageCodec.java
index 9c261a50..9bb54845 100644
--- a/services-api/src/main/java/io/scalecube/services/transport/api/ServiceMessageCodec.java
+++ b/services-api/src/main/java/io/scalecube/services/transport/api/ServiceMessageCodec.java
@@ -8,6 +8,7 @@ import io.netty.buffer.Unpooled;
 import io.scalecube.services.api.ErrorData;
 import io.scalecube.services.api.ServiceMessage;
 import io.scalecube.services.exceptions.MessageCodecException;
+import java.lang.reflect.Type;
 import java.util.function.BiFunction;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -104,7 +105,7 @@ public final class ServiceMessageCodec {
    *     (of type data type)
    * @throws MessageCodecException when decode fails
    */
-  public static ServiceMessage decodeData(ServiceMessage message, Class<?> dataType)
+  public static ServiceMessage decodeData(ServiceMessage message, Type dataType)
       throws MessageCodecException {
     if (dataType == null
         || !message.hasData(ByteBuf.class)
@@ -113,7 +114,7 @@ public final class ServiceMessageCodec {
     }
 
     Object data;
-    Class<?> targetType = message.isError() ? ErrorData.class : dataType;
+    Type targetType = message.isError() ? ErrorData.class : dataType;
 
     ByteBuf dataBuffer = message.data();
     try (ByteBufInputStream inputStream = new ByteBufInputStream(dataBuffer, true)) {
diff --git a/services-examples/src/main/java/io/scalecube/services/examples/GreetingService.java b/services-examples/src/main/java/io/scalecube/services/examples/GreetingService.java
index de8856ba..93895fd1 100644
--- a/services-examples/src/main/java/io/scalecube/services/examples/GreetingService.java
+++ b/services-examples/src/main/java/io/scalecube/services/examples/GreetingService.java
@@ -2,6 +2,7 @@ package io.scalecube.services.examples;
 
 import io.scalecube.services.annotations.Service;
 import io.scalecube.services.annotations.ServiceMethod;
+import java.util.List;
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 
@@ -28,6 +29,9 @@ public interface GreetingService {
   @ServiceMethod("pojo/one")
   Mono<GreetingResponse> pojoOne(GreetingRequest request);
 
+  @ServiceMethod("pojo/list")
+  Mono<List<GreetingResponse>> pojoList(GreetingRequest request);
+
   @ServiceMethod("pojo/many")
   Flux<GreetingResponse> pojoMany(GreetingRequest request);
 
diff --git a/services-examples/src/main/java/io/scalecube/services/examples/GreetingServiceCancelCallback.java b/services-examples/src/main/java/io/scalecube/services/examples/GreetingServiceCancelCallback.java
index df94b777..50779793 100644
--- a/services-examples/src/main/java/io/scalecube/services/examples/GreetingServiceCancelCallback.java
+++ b/services-examples/src/main/java/io/scalecube/services/examples/GreetingServiceCancelCallback.java
@@ -1,5 +1,6 @@
 package io.scalecube.services.examples;
 
+import java.util.List;
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 
@@ -42,6 +43,11 @@ public class GreetingServiceCancelCallback implements GreetingService {
     return greetingService.pojoOne(request).doOnCancel(onCancel);
   }
 
+  @Override
+  public Mono<List<GreetingResponse>> pojoList(GreetingRequest request) {
+    return greetingService.pojoList(request).doOnCancel(onCancel);
+  }
+
   @Override
   public Flux<GreetingResponse> pojoMany(GreetingRequest request) {
     return greetingService.pojoMany(request).doOnCancel(onCancel);
diff --git a/services-examples/src/main/java/io/scalecube/services/examples/GreetingServiceImpl.java b/services-examples/src/main/java/io/scalecube/services/examples/GreetingServiceImpl.java
index fefe0773..ab0a3f48 100644
--- a/services-examples/src/main/java/io/scalecube/services/examples/GreetingServiceImpl.java
+++ b/services-examples/src/main/java/io/scalecube/services/examples/GreetingServiceImpl.java
@@ -1,6 +1,8 @@
 package io.scalecube.services.examples;
 
 import java.time.Duration;
+import java.util.Collections;
+import java.util.List;
 import java.util.stream.LongStream;
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
@@ -47,6 +49,12 @@ public class GreetingServiceImpl implements GreetingService {
     return Mono.defer(() -> one(request.getText()).map(GreetingResponse::new));
   }
 
+  @Override
+  public Mono<List<GreetingResponse>> pojoList(GreetingRequest request) {
+    return Mono.defer(
+        () -> one(request.getText()).map(GreetingResponse::new).map(Collections::singletonList));
+  }
+
   @Override
   public Flux<GreetingResponse> pojoMany(GreetingRequest request) {
     return Flux.defer(() -> many(request.getText()).map(GreetingResponse::new));
diff --git a/services-gateway-clientsdk/src/main/java/io/scalecube/services/gateway/clientsdk/ClientCodec.java b/services-gateway-clientsdk/src/main/java/io/scalecube/services/gateway/clientsdk/ClientCodec.java
index 6394a7fc..2849dc2d 100644
--- a/services-gateway-clientsdk/src/main/java/io/scalecube/services/gateway/clientsdk/ClientCodec.java
+++ b/services-gateway-clientsdk/src/main/java/io/scalecube/services/gateway/clientsdk/ClientCodec.java
@@ -4,6 +4,7 @@ import io.netty.buffer.ByteBuf;
 import io.netty.buffer.ByteBufInputStream;
 import io.scalecube.services.gateway.clientsdk.exceptions.MessageCodecException;
 import io.scalecube.services.transport.api.DataCodec;
+import java.lang.reflect.Type;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -25,14 +26,14 @@ public interface ClientCodec<T> {
    * @return client message object.
    * @throws MessageCodecException in case if data decoding fails.
    */
-  default ClientMessage decodeData(ClientMessage message, Class<?> dataType)
+  default ClientMessage decodeData(ClientMessage message, Type dataType)
       throws MessageCodecException {
     if (!message.hasData(ByteBuf.class) || dataType == null) {
       return message;
     }
 
     Object data;
-    Class<?> targetType = message.isError() ? ErrorData.class : dataType;
+    Type targetType = message.isError() ? ErrorData.class : dataType;
 
     ByteBuf dataBuffer = message.data();
     try (ByteBufInputStream inputStream = new ByteBufInputStream(dataBuffer, true)) {
@@ -48,7 +49,7 @@ public interface ClientCodec<T> {
   /**
    * Returns codec which is used to decode data object of {@link ClientMessage}.
    *
-   * @see ClientCodec#decodeData(ClientMessage, Class)
+   * @see ClientCodec#decodeData(ClientMessage, Type)
    * @return data codec
    */
   DataCodec getDataCodec();
diff --git a/services-gateway-clientsdk/src/main/java/io/scalecube/services/gateway/clientsdk/Reflect.java b/services-gateway-clientsdk/src/main/java/io/scalecube/services/gateway/clientsdk/Reflect.java
index af30d76a..ddf06b4f 100644
--- a/services-gateway-clientsdk/src/main/java/io/scalecube/services/gateway/clientsdk/Reflect.java
+++ b/services-gateway-clientsdk/src/main/java/io/scalecube/services/gateway/clientsdk/Reflect.java
@@ -70,15 +70,10 @@ class Reflect {
    * @param method to extract type from.
    * @return the generic type of the return value or object.
    */
-  private static Class<?> parameterizedReturnType(Method method) {
+  private static Type parameterizedReturnType(Method method) {
     Type type = method.getGenericReturnType();
     if (type instanceof ParameterizedType) {
-      try {
-        return Class.forName(
-            (((ParameterizedType) type).getActualTypeArguments()[0]).getTypeName());
-      } catch (ClassNotFoundException e) {
-        return Object.class;
-      }
+      return ((ParameterizedType) type).getActualTypeArguments()[0];
     } else {
       return Object.class;
     }
diff --git a/services-gateway-clientsdk/src/main/java/io/scalecube/services/gateway/clientsdk/RemoteInvocationHandler.java b/services-gateway-clientsdk/src/main/java/io/scalecube/services/gateway/clientsdk/RemoteInvocationHandler.java
index ff69d869..0a78f730 100644
--- a/services-gateway-clientsdk/src/main/java/io/scalecube/services/gateway/clientsdk/RemoteInvocationHandler.java
+++ b/services-gateway-clientsdk/src/main/java/io/scalecube/services/gateway/clientsdk/RemoteInvocationHandler.java
@@ -4,6 +4,7 @@ import io.scalecube.services.gateway.clientsdk.exceptions.ClientErrorMapper;
 import io.scalecube.services.methods.MethodInfo;
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
+import java.lang.reflect.Type;
 import java.util.Map;
 import reactor.core.Exceptions;
 
@@ -42,7 +43,7 @@ public class RemoteInvocationHandler implements InvocationHandler {
             .data(methodInfo.parameterCount() != 0 ? args[0] : null)
             .build();
 
-    Class<?> responseType = methodInfo.parameterizedReturnType();
+    Type responseType = methodInfo.parameterizedReturnType();
 
     switch (methodInfo.communicationMode()) {
       case REQUEST_RESPONSE:
diff --git a/services-gateway-tests/src/test/java/io/scalecube/services/gateway/http/HttpGatewayTest.java b/services-gateway-tests/src/test/java/io/scalecube/services/gateway/http/HttpGatewayTest.java
index 9e46c1fe..e8cdd0fa 100644
--- a/services-gateway-tests/src/test/java/io/scalecube/services/gateway/http/HttpGatewayTest.java
+++ b/services-gateway-tests/src/test/java/io/scalecube/services/gateway/http/HttpGatewayTest.java
@@ -54,6 +54,14 @@ class HttpGatewayTest {
         .verify(TIMEOUT);
   }
 
+  @Test
+  void shouldReturnListResponseWithPojoRequest() {
+    StepVerifier.create(service.pojoList(new GreetingRequest("hello")))
+        .expectNextMatches(response -> "Echo:hello".equals(response.get(0).getText()))
+        .expectComplete()
+        .verify(TIMEOUT);
+  }
+
   @Test
   void shouldReturnNoContentWhenResponseIsEmpty() {
     StepVerifier.create(service.emptyOne("hello")).expectComplete().verify(TIMEOUT);
diff --git a/services-gateway-tests/src/test/java/io/scalecube/services/gateway/http/HttpLocalGatewayTest.java b/services-gateway-tests/src/test/java/io/scalecube/services/gateway/http/HttpLocalGatewayTest.java
index caa83e17..c20fb32f 100644
--- a/services-gateway-tests/src/test/java/io/scalecube/services/gateway/http/HttpLocalGatewayTest.java
+++ b/services-gateway-tests/src/test/java/io/scalecube/services/gateway/http/HttpLocalGatewayTest.java
@@ -52,6 +52,14 @@ class HttpLocalGatewayTest {
         .verify(TIMEOUT);
   }
 
+  @Test
+  void shouldReturnListResponseWithPojoRequest() {
+    StepVerifier.create(service.pojoList(new GreetingRequest("hello")))
+        .expectNextMatches(response -> "Echo:hello".equals(response.get(0).getText()))
+        .expectComplete()
+        .verify(TIMEOUT);
+  }
+
   @Test
   void shouldReturnNoContentWhenResponseIsEmpty() {
     StepVerifier.create(service.emptyOne("hello")).expectComplete().verify(TIMEOUT);
diff --git a/services-gateway-tests/src/test/java/io/scalecube/services/gateway/rsocket/RSocketGatewayTest.java b/services-gateway-tests/src/test/java/io/scalecube/services/gateway/rsocket/RSocketGatewayTest.java
index 7ed709f2..d09da7e7 100644
--- a/services-gateway-tests/src/test/java/io/scalecube/services/gateway/rsocket/RSocketGatewayTest.java
+++ b/services-gateway-tests/src/test/java/io/scalecube/services/gateway/rsocket/RSocketGatewayTest.java
@@ -55,6 +55,14 @@ class RSocketGatewayTest {
         .verify(TIMEOUT);
   }
 
+  @Test
+  void shouldReturnListResponseWithPojoRequest() {
+    StepVerifier.create(service.pojoList(new GreetingRequest("hello")))
+        .expectNextMatches(response -> "Echo:hello".equals(response.get(0).getText()))
+        .expectComplete()
+        .verify(TIMEOUT);
+  }
+
   @Test
   void shouldReturnManyResponses() {
     int expectedResponseNum = 3;
diff --git a/services-gateway-tests/src/test/java/io/scalecube/services/gateway/rsocket/RSocketLocalGatewayTest.java b/services-gateway-tests/src/test/java/io/scalecube/services/gateway/rsocket/RSocketLocalGatewayTest.java
index e1205fe4..5cb09604 100644
--- a/services-gateway-tests/src/test/java/io/scalecube/services/gateway/rsocket/RSocketLocalGatewayTest.java
+++ b/services-gateway-tests/src/test/java/io/scalecube/services/gateway/rsocket/RSocketLocalGatewayTest.java
@@ -55,6 +55,14 @@ class RSocketLocalGatewayTest {
         .verify(TIMEOUT);
   }
 
+  @Test
+  void shouldReturnListResponseWithPojoRequest() {
+    StepVerifier.create(service.pojoList(new GreetingRequest("hello")))
+        .expectNextMatches(response -> "Echo:hello".equals(response.get(0).getText()))
+        .expectComplete()
+        .verify(TIMEOUT);
+  }
+
   @Test
   void shouldReturnManyResponses() {
     int expectedResponseNum = 3;
diff --git a/services-gateway-tests/src/test/java/io/scalecube/services/gateway/websocket/WebsocketGatewayTest.java b/services-gateway-tests/src/test/java/io/scalecube/services/gateway/websocket/WebsocketGatewayTest.java
index 2989056c..0b32d249 100644
--- a/services-gateway-tests/src/test/java/io/scalecube/services/gateway/websocket/WebsocketGatewayTest.java
+++ b/services-gateway-tests/src/test/java/io/scalecube/services/gateway/websocket/WebsocketGatewayTest.java
@@ -55,6 +55,14 @@ class WebsocketGatewayTest {
         .verify(TIMEOUT);
   }
 
+  @Test
+  void shouldReturnListResponseWithPojoRequest() {
+    StepVerifier.create(service.pojoList(new GreetingRequest("hello")))
+        .expectNextMatches(response -> "Echo:hello".equals(response.get(0).getText()))
+        .expectComplete()
+        .verify(TIMEOUT);
+  }
+
   @Test
   void shouldReturnManyResponsesWithSimpleRequest() {
     int expectedResponseNum = 3;
diff --git a/services-gateway-tests/src/test/java/io/scalecube/services/gateway/websocket/WebsocketLocalGatewayTest.java b/services-gateway-tests/src/test/java/io/scalecube/services/gateway/websocket/WebsocketLocalGatewayTest.java
index 39adede8..6004ecfb 100644
--- a/services-gateway-tests/src/test/java/io/scalecube/services/gateway/websocket/WebsocketLocalGatewayTest.java
+++ b/services-gateway-tests/src/test/java/io/scalecube/services/gateway/websocket/WebsocketLocalGatewayTest.java
@@ -54,6 +54,14 @@ class WebsocketLocalGatewayTest {
         .verify(TIMEOUT);
   }
 
+  @Test
+  void shouldReturnListResponseWithPojoRequest() {
+    StepVerifier.create(service.pojoList(new GreetingRequest("hello")))
+        .expectNextMatches(response -> "Echo:hello".equals(response.get(0).getText()))
+        .expectComplete()
+        .verify(TIMEOUT);
+  }
+
   @Test
   void shouldReturnManyResponsesWithSimpleRequest() {
     int expectedResponseNum = 3;
diff --git a/services-transport-jackson/src/main/java/io/scalecube/services/transport/jackson/JacksonCodec.java b/services-transport-jackson/src/main/java/io/scalecube/services/transport/jackson/JacksonCodec.java
index ee8a3430..8c565e70 100644
--- a/services-transport-jackson/src/main/java/io/scalecube/services/transport/jackson/JacksonCodec.java
+++ b/services-transport-jackson/src/main/java/io/scalecube/services/transport/jackson/JacksonCodec.java
@@ -12,6 +12,7 @@ import io.scalecube.services.transport.api.HeadersCodec;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.lang.reflect.Type;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
@@ -53,8 +54,8 @@ public final class JacksonCodec implements DataCodec, HeadersCodec {
   }
 
   @Override
-  public Object decode(InputStream stream, Class<?> type) throws IOException {
-    return mapper.readValue(stream, type);
+  public Object decode(InputStream stream, Type type) throws IOException {
+    return mapper.readValue(stream, mapper.getTypeFactory().constructType(type));
   }
 
   private static ObjectMapper initMapper() {
diff --git a/services-transport-protostuff/src/main/java/io/scalecube/services/transport/protostuff/ProtostuffCodec.java b/services-transport-protostuff/src/main/java/io/scalecube/services/transport/protostuff/ProtostuffCodec.java
index ca8d26a7..c09d821e 100644
--- a/services-transport-protostuff/src/main/java/io/scalecube/services/transport/protostuff/ProtostuffCodec.java
+++ b/services-transport-protostuff/src/main/java/io/scalecube/services/transport/protostuff/ProtostuffCodec.java
@@ -5,11 +5,14 @@ import io.protostuff.ProtostuffIOUtil;
 import io.protostuff.Schema;
 import io.protostuff.StringMapSchema;
 import io.protostuff.runtime.RuntimeSchema;
+import io.scalecube.services.exceptions.MessageCodecException;
 import io.scalecube.services.transport.api.DataCodec;
 import io.scalecube.services.transport.api.HeadersCodec;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -40,14 +43,24 @@ public final class ProtostuffCodec implements HeadersCodec, DataCodec {
   }
 
   @Override
-  public Object decode(InputStream stream, Class<?> type) throws IOException {
-    Schema schema = RuntimeSchema.getSchema(type);
-    Object result = schema.newMessage();
+  public Object decode(InputStream stream, Type type) throws IOException {
+    try {
+      Class<?> clazz = null;
+      if (type instanceof Class<?>) {
+        clazz = (Class<?>) type;
+      } else if (type instanceof ParameterizedType) {
+        clazz = Class.forName(((ParameterizedType) type).getRawType().getTypeName());
+      }
+      Schema schema = RuntimeSchema.getSchema(clazz);
+      Object result = schema.newMessage();
 
-    try (RecyclableLinkedBuffer rlb = recyclableLinkedBuffer.get()) {
-      ProtobufIOUtil.mergeFrom(stream, result, schema, rlb.buffer());
+      try (RecyclableLinkedBuffer rlb = recyclableLinkedBuffer.get()) {
+        ProtobufIOUtil.mergeFrom(stream, result, schema, rlb.buffer());
+      }
+      return result;
+    } catch (ClassNotFoundException e) {
+      throw new MessageCodecException("Couldn't decode message", e);
     }
-    return result;
   }
 
   @Override
diff --git a/services/src/main/java/io/scalecube/services/Reflect.java b/services/src/main/java/io/scalecube/services/Reflect.java
index 4b53f2ab..358e772f 100644
--- a/services/src/main/java/io/scalecube/services/Reflect.java
+++ b/services/src/main/java/io/scalecube/services/Reflect.java
@@ -62,15 +62,10 @@ public final class Reflect {
    * @param method to extract type from.
    * @return the generic type of the return value or object.
    */
-  public static Class<?> parameterizedReturnType(Method method) {
+  public static Type parameterizedReturnType(Method method) {
     Type type = method.getGenericReturnType();
     if (type instanceof ParameterizedType) {
-      try {
-        return Class.forName(
-            (((ParameterizedType) type).getActualTypeArguments()[0]).getTypeName());
-      } catch (ClassNotFoundException e) {
-        return Object.class;
-      }
+      return ((ParameterizedType) type).getActualTypeArguments()[0];
     } else {
       return Object.class;
     }
diff --git a/services/src/main/java/io/scalecube/services/ServiceCall.java b/services/src/main/java/io/scalecube/services/ServiceCall.java
index fe1212cd..9ef8986e 100644
--- a/services/src/main/java/io/scalecube/services/ServiceCall.java
+++ b/services/src/main/java/io/scalecube/services/ServiceCall.java
@@ -19,6 +19,7 @@ import io.scalecube.services.transport.api.ReferenceCountUtil;
 import io.scalecube.services.transport.api.ServiceMessageCodec;
 import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
+import java.lang.reflect.Type;
 import java.util.Map;
 import java.util.Optional;
 import java.util.concurrent.Callable;
@@ -150,7 +151,7 @@ public class ServiceCall {
    * @param responseType type of response.
    * @return mono publisher completing with single response message or with error.
    */
-  public Mono<ServiceMessage> requestOne(ServiceMessage request, Class<?> responseType) {
+  public Mono<ServiceMessage> requestOne(ServiceMessage request, Type responseType) {
     return Mono.defer(
         () -> {
           String qualifier = request.qualifier();
@@ -175,7 +176,7 @@ public class ServiceCall {
    * @return mono publisher completing with single response message or with error.
    */
   public Mono<ServiceMessage> requestOne(
-      ServiceMessage request, Class<?> responseType, Address address) {
+      ServiceMessage request, Type responseType, Address address) {
     return Mono.defer(
         () -> {
           requireNonNull(address, "requestOne address parameter is required and must not be null");
@@ -205,7 +206,7 @@ public class ServiceCall {
    * @param responseType type of responses.
    * @return flux publisher of service responses.
    */
-  public Flux<ServiceMessage> requestMany(ServiceMessage request, Class<?> responseType) {
+  public Flux<ServiceMessage> requestMany(ServiceMessage request, Type responseType) {
     return Flux.defer(
         () -> {
           String qualifier = request.qualifier();
@@ -232,7 +233,7 @@ public class ServiceCall {
    * @return flux publisher of service responses.
    */
   public Flux<ServiceMessage> requestMany(
-      ServiceMessage request, Class<?> responseType, Address address) {
+      ServiceMessage request, Type responseType, Address address) {
     return Flux.defer(
         () -> {
           requireNonNull(address, "requestMany address parameter is required and must not be null");
@@ -263,7 +264,7 @@ public class ServiceCall {
    * @return flux publisher of service responses.
    */
   public Flux<ServiceMessage> requestBidirectional(
-      Publisher<ServiceMessage> publisher, Class<?> responseType) {
+      Publisher<ServiceMessage> publisher, Type responseType) {
     return Flux.from(publisher)
         .switchOnFirst(
             (first, messages) -> {
@@ -298,7 +299,7 @@ public class ServiceCall {
    * @return flux publisher of service responses.
    */
   public Flux<ServiceMessage> requestBidirectional(
-      Publisher<ServiceMessage> publisher, Class<?> responseType, Address address) {
+      Publisher<ServiceMessage> publisher, Type responseType, Address address) {
     return Flux.defer(
         () -> {
           requireNonNull(
@@ -331,7 +332,7 @@ public class ServiceCall {
             new Class[] {serviceInterface},
             (proxy, method, params) -> {
               final MethodInfo methodInfo = genericReturnTypes.get(method);
-              final Class<?> returnType = methodInfo.parameterizedReturnType();
+              final Type returnType = methodInfo.parameterizedReturnType();
               final boolean isServiceMessage = methodInfo.isRequestTypeServiceMessage();
 
               Optional<Object> check =