Merge pull request #61 from cloverstd/master

Fix invalid data
diff --git a/README.md b/README.md
index 074fe4c..165a483 100644
--- a/README.md
+++ b/README.md
@@ -51,7 +51,7 @@
 
 ## Customize Usage Examples
 
-#### Encoding filed name 
+#### Encoding filed name
 
 Hessian encoder default converts filed names of struct to lower camelcase, but you can customize it using `hessian` tag.
 
@@ -76,7 +76,7 @@
 if err != nil {
     panic(err)
 }
-``` 
+```
 
 The encoded bytes of the struct `MyUser` is as following:
 ```text
@@ -87,7 +87,7 @@
  00000040  0c 30 31 30 2d 31 32 33  34 35 36 37 38           |.010-12345678|
 ```
 
-#### Decoding filed name 
+#### Decoding filed name
 
 Hessian decoder finds the correct target field though comparing all filed names of struct one by one until matching.
 
@@ -107,7 +107,7 @@
 
 
 type MyUser struct {
-	MobilePhone      string  
+	MobilePhone      string
 }
 
 // The following encoded binary bytes will be hit automatically:
@@ -125,7 +125,7 @@
 //
 // mobile-phone(tag lookup) => mobilePhone(lowerCameCase) => MobilePhone(SameCase) => mobilephone(lowercase)
 //                                                           ^ will matched
-// 
+//
 // 00000000  43 12 63 6f 6d 2e 63 6f  6d 70 61 6e 79 2e 6d 79  |C.com.company.my|
 // 00000010  75 73 65 72 91 0b 6d 6f  62 69 6c 65 70 68 6f 6e  |user..mobilephon|
 // 00000020  65 60 0b 31 37 36 31 32  33 34 31 32 33 34        |e`.17612341234|
@@ -138,7 +138,7 @@
 
 ##### hessian.SetTagIdentifier
 
-You can use `hessian.SetTagIdentifier` to customize tag-identifier of hessian, which takes effect to both encoder and decoder. 
+You can use `hessian.SetTagIdentifier` to customize tag-identifier of hessian, which takes effect to both encoder and decoder.
 
 Example:
 ```go
@@ -163,7 +163,7 @@
 if err != nil {
     panic(err)
 }
-``` 
+```
 
 The encoded bytes of the struct `MyUser` is as following:
 ```text
@@ -173,8 +173,3 @@
  00000030  4e 75 6d 62 65 72 60 08  75 73 65 72 6e 61 6d 65  |Number`.username|
  00000040  0c 30 31 30 2d 31 32 33  34 35 36 37 38           |.010-12345678|
 ```
-
-
-## Dubbo Service
-
-TODO
diff --git a/const.go b/const.go
index a8ad45b..53a056f 100644
--- a/const.go
+++ b/const.go
@@ -20,6 +20,7 @@
 
 import (
 	perrors "github.com/pkg/errors"
+	"reflect"
 )
 
 const (
@@ -232,3 +233,5 @@
 
 // DescRegex ...
 var DescRegex, _ = regexp.Compile(DESC_REGEX)
+
+var NilValue = reflect.Zero(reflect.TypeOf((*interface{})(nil)).Elem())
diff --git a/date_test.go b/date_test.go
index 5e4f789..ed7f96c 100644
--- a/date_test.go
+++ b/date_test.go
@@ -43,7 +43,7 @@
 }
 
 func testDateFramework(t *testing.T, method string, expected time.Time) {
-	r, e := decodeResponse(method)
+	r, e := decodeJavaResponse(method, "")
 	if e != nil {
 		t.Errorf("%s: decode fail with error %+v", method, e)
 		return
diff --git a/decode_test.go b/decode_test.go
index 0220dc5..8dae2b6 100644
--- a/decode_test.go
+++ b/decode_test.go
@@ -55,9 +55,13 @@
 	}
 }
 
-func getReply(method string) []byte {
+func getJavaReply(method, className string) []byte {
 	genHessianJar()
-	cmd := exec.Command("java", "-jar", hessianJar, method)
+	cmdArgs := []string{"-jar", hessianJar, method}
+	if className != "" {
+		cmdArgs = append(cmdArgs, className)
+	}
+	cmd := exec.Command("java", cmdArgs...)
 	out, err := cmd.Output()
 	if err != nil {
 		log.Fatal(err)
@@ -65,8 +69,8 @@
 	return out
 }
 
-func decodeResponse(method string) (interface{}, error) {
-	b := getReply(method)
+func decodeJavaResponse(method, className string) (interface{}, error) {
+	b := getJavaReply(method, className)
 	d := NewDecoder(b)
 	r, e := d.Decode()
 	if e != nil {
@@ -76,7 +80,11 @@
 }
 
 func testDecodeFramework(t *testing.T, method string, expected interface{}) {
-	r, e := decodeResponse(method)
+	testDecodeJavaData(t, method, "", expected)
+}
+
+func testDecodeJavaData(t *testing.T, method, className string, expected interface{}) {
+	r, e := decodeJavaResponse(method, className)
 	if e != nil {
 		t.Errorf("%s: decode fail with error %v", method, e)
 		return
@@ -90,3 +98,17 @@
 		t.Errorf("%s: got %v, wanted %v", method, r, expected)
 	}
 }
+
+func testDecodeFrameworkFunc(t *testing.T, method string, expected func(interface{})) {
+	r, e := decodeJavaResponse(method, "")
+	if e != nil {
+		t.Errorf("%s: decode fail with error %v", method, e)
+		return
+	}
+
+	tmp, ok := r.(*_refHolder)
+	if ok {
+		r = tmp.value.Interface()
+	}
+	expected(r)
+}
diff --git a/encode.go b/encode.go
index 7a6b082..30d6364 100644
--- a/encode.go
+++ b/encode.go
@@ -70,8 +70,18 @@
 	case bool:
 		e.buffer = encBool(e.buffer, val)
 
-	case int8, int16, int32:
-		e.buffer = encInt32(e.buffer, v.(int32))
+	case uint8:
+		e.buffer = encInt32(e.buffer, int32(val))
+	case int8:
+		e.buffer = encInt32(e.buffer, int32(val))
+	case int16:
+		e.buffer = encInt32(e.buffer, int32(val))
+	case uint16:
+		e.buffer = encInt32(e.buffer, int32(val))
+	case int32:
+		e.buffer = encInt32(e.buffer, int32(val))
+	case uint32:
+		e.buffer = encInt64(e.buffer, int64(val))
 
 	case int:
 		// if v.(int) >= -2147483648 && v.(int) <= 2147483647 {
@@ -85,6 +95,8 @@
 
 	case int64:
 		e.buffer = encInt64(e.buffer, val)
+	case uint64:
+		e.buffer = encInt64(e.buffer, int64(val))
 
 	case time.Time:
 		e.buffer = encDateInMs(e.buffer, val)
diff --git a/encode_test.go b/encode_test.go
index fd7c49b..462851f 100644
--- a/encode_test.go
+++ b/encode_test.go
@@ -16,6 +16,7 @@
 
 import (
 	"bytes"
+	"os/exec"
 	"testing"
 )
 
@@ -24,3 +25,49 @@
 		t.Fatalf("want %v , got %v", want, got)
 	}
 }
+
+func encodeTarget(target interface{}) ([]byte, error) {
+	e := NewEncoder()
+	err := e.Encode(target)
+	if err != nil {
+		return nil, err
+	}
+	return e.Buffer(), nil
+}
+
+func javaDecodeValidate(method string, target interface{}) (string, error) {
+	b, e := encodeTarget(target)
+	if e != nil {
+		return "", e
+	}
+
+	genHessianJar()
+	cmd := exec.Command("java", "-jar", hessianJar, method)
+
+	stdin, _ := cmd.StdinPipe()
+	_, e = stdin.Write(b)
+	if e != nil {
+		return "", e
+	}
+	e = stdin.Close()
+	if e != nil {
+		return "", e
+	}
+
+	out, e := cmd.Output()
+	if e != nil {
+		return "", e
+	}
+	return string(out), nil
+}
+
+func testJavaDecode(t *testing.T, method string, target interface{}) {
+	r, e := javaDecodeValidate(method, target)
+	if e != nil {
+		t.Errorf("%s: encode fail with error %v", method, e)
+	}
+
+	if r != "true" {
+		t.Errorf("%s: encode %v to bytes wrongly", method, target)
+	}
+}
diff --git a/hessian.go b/hessian.go
index e3e4e66..171191a 100644
--- a/hessian.go
+++ b/hessian.go
@@ -17,6 +17,7 @@
 import (
 	"bufio"
 	"encoding/binary"
+	"reflect"
 	"time"
 )
 
@@ -26,11 +27,12 @@
 
 // enum part
 const (
-	PackageError          = PackageType(0x01)
-	PackageRequest        = PackageType(0x02)
-	PackageResponse       = PackageType(0x04)
-	PackageHeartbeat      = PackageType(0x08)
-	PackageRequest_TwoWay = PackageType(0x10)
+	PackageError              = PackageType(0x01)
+	PackageRequest            = PackageType(0x02)
+	PackageResponse           = PackageType(0x04)
+	PackageHeartbeat          = PackageType(0x08)
+	PackageRequest_TwoWay     = PackageType(0x10)
+	PackageResponse_Exception = PackageType(0x20)
 )
 
 // PackageType ...
@@ -94,6 +96,9 @@
 
 	var err error
 
+	if h.reader.Size() < HEADER_LENGTH {
+		return ErrHeaderNotEnough
+	}
 	buf, err := h.reader.Peek(HEADER_LENGTH)
 	if err != nil { // this is impossible
 		return perrors.WithStack(err)
@@ -128,18 +133,8 @@
 	} else {
 		header.Type |= PackageResponse
 		header.ResponseStatus = buf[3]
-
-		// Header{status}
-		if buf[3] != Response_OK {
-			err = ErrJavaException
-			header.Type |= PackageError
-			bufSize := h.reader.Buffered()
-			if bufSize > 2 { // responseType + objectType + error content,so it's size > 2
-				expBuf, expErr := h.reader.Peek(bufSize)
-				if expErr == nil {
-					err = perrors.Errorf("java exception:%s", string(expBuf[2:bufSize-1]))
-				}
-			}
+		if header.ResponseStatus != Response_OK {
+			header.Type |= PackageResponse_Exception
 		}
 	}
 
@@ -155,6 +150,10 @@
 	h.pkgType = header.Type
 	h.bodyLen = header.BodyLen
 
+	if h.reader.Buffered() < h.bodyLen {
+		return ErrBodyNotEnough
+	}
+
 	return perrors.WithStack(err)
 
 }
@@ -162,10 +161,10 @@
 // ReadBody uses hessian codec to read response body
 func (h *HessianCodec) ReadBody(rspObj interface{}) error {
 
-	buf, err := h.reader.Peek(h.bodyLen)
-	if err == bufio.ErrBufferFull {
+	if h.reader.Buffered() < h.bodyLen {
 		return ErrBodyNotEnough
 	}
+	buf, err := h.reader.Peek(h.bodyLen)
 	if err != nil {
 		return perrors.WithStack(err)
 	}
@@ -174,21 +173,34 @@
 		return perrors.WithStack(err)
 	}
 
-	switch h.pkgType & 0x0f {
-	case PackageRequest | PackageHeartbeat, PackageResponse | PackageHeartbeat:
+	switch h.pkgType & 0x2f {
+	case PackageResponse | PackageHeartbeat | PackageResponse_Exception, PackageResponse | PackageResponse_Exception:
+		rsp, ok := rspObj.(*Response)
+		if !ok {
+			return perrors.Errorf("@rspObj is not *Response, it is %s", reflect.TypeOf(rspObj).String())
+		}
+		rsp.Exception = ErrJavaException
+		decoder := NewDecoder(buf[:])
+		exception, err := decoder.Decode()
+		if err != nil {
+			return perrors.WithStack(err)
+		}
+		rsp.Exception = perrors.Errorf("java exception:%s", exception.(string))
 		return nil
+	case PackageRequest | PackageHeartbeat, PackageResponse | PackageHeartbeat:
 	case PackageRequest:
 		if rspObj != nil {
 			if err = unpackRequestBody(buf, rspObj); err != nil {
 				return perrors.WithStack(err)
 			}
 		}
-
-		return nil
-
 	case PackageResponse:
 		if rspObj != nil {
-			if err = unpackResponseBody(buf, rspObj); err != nil {
+			rsp, ok := rspObj.(*Response)
+			if !ok {
+				rsp = &Response{RspObj: rspObj}
+			}
+			if err = unpackResponseBody(buf, rsp); err != nil {
 				return perrors.WithStack(err)
 			}
 		}
diff --git a/hessian_test.go b/hessian_test.go
index b68c780..45b4768 100644
--- a/hessian_test.go
+++ b/hessian_test.go
@@ -55,67 +55,75 @@
 	return resp, err
 }
 
-func doTestResponse(t *testing.T, packageType PackageType, responseStatus byte, body interface{}, decodedObject interface{}, assertFunc func()) {
+func doTestResponse(t *testing.T, packageType PackageType, responseStatus byte, body interface{}, decodedResponse *Response, assertFunc func()) {
 	resp, err := doTestHessianEncodeHeader(t, packageType, responseStatus, body)
 
 	codecR := NewHessianCodec(bufio.NewReader(bytes.NewReader(resp)))
 
 	h := &DubboHeader{}
 	err = codecR.ReadHeader(h)
-	if responseStatus == Response_OK {
-		assert.Nil(t, err)
-	} else {
-		t.Log(err)
-		assert.NotNil(t, err)
-		return
-	}
+	assert.Nil(t, err)
+
 	assert.Equal(t, byte(2), h.SerialID)
 	assert.Equal(t, packageType, h.Type&(PackageRequest|PackageResponse|PackageHeartbeat))
 	assert.Equal(t, int64(1), h.ID)
 	assert.Equal(t, responseStatus, h.ResponseStatus)
 
-	err = codecR.ReadBody(decodedObject)
+	err = codecR.ReadBody(decodedResponse)
 	assert.Nil(t, err)
-	t.Log(decodedObject)
+	t.Log(decodedResponse)
 
 	if assertFunc != nil {
 		assertFunc()
 		return
 	}
 
+	if h.ResponseStatus != Zero && h.ResponseStatus != Response_OK {
+		assert.Equal(t, "java exception:"+body.(string), decodedResponse.Exception.Error())
+		return
+	}
+
 	in, _ := EnsureInterface(UnpackPtrValue(EnsurePackValue(body)), nil)
-	out, _ := EnsureInterface(UnpackPtrValue(EnsurePackValue(decodedObject)), nil)
+	out, _ := EnsureInterface(UnpackPtrValue(EnsurePackValue(decodedResponse.RspObj)), nil)
 	assert.Equal(t, in, out)
 }
 
 func TestResponse(t *testing.T) {
 	caseObj := Case{A: "a", B: 1}
+	decodedResponse := &Response{}
 
 	arr := []*Case{&caseObj}
 	var arrRes []interface{}
-	doTestResponse(t, PackageResponse, Response_OK, arr, &arrRes, func() {
+	decodedResponse.RspObj = &arrRes
+	doTestResponse(t, PackageResponse, Response_OK, arr, decodedResponse, func() {
 		assert.Equal(t, 1, len(arrRes))
 		assert.Equal(t, &caseObj, arrRes[0])
 	})
 
-	doTestResponse(t, PackageResponse, Response_OK, &Case{A: "a", B: 1}, &Case{}, nil)
+	decodedResponse.RspObj = &Case{}
+	doTestResponse(t, PackageResponse, Response_OK, &Case{A: "a", B: 1}, decodedResponse, nil)
 
 	s := "ok!!!!!"
 	strObj := ""
-	doTestResponse(t, PackageResponse, Response_OK, s, &strObj, nil)
+	decodedResponse.RspObj = &strObj
+	doTestResponse(t, PackageResponse, Response_OK, s, decodedResponse, nil)
 
 	var intObj int64
-	doTestResponse(t, PackageResponse, Response_OK, int64(3), &intObj, nil)
+	decodedResponse.RspObj = &intObj
+	doTestResponse(t, PackageResponse, Response_OK, int64(3), decodedResponse, nil)
 
 	boolObj := false
-	doTestResponse(t, PackageResponse, Response_OK, true, &boolObj, nil)
+	decodedResponse.RspObj = &boolObj
+	doTestResponse(t, PackageResponse, Response_OK, true, decodedResponse, nil)
 
 	strObj = ""
-	doTestResponse(t, PackageResponse, Response_SERVER_ERROR, "error!!!!!", &strObj, nil)
+	decodedResponse.RspObj = &strObj
+	doTestResponse(t, PackageResponse, Response_SERVER_ERROR, "error!!!!!", decodedResponse, nil)
 
 	mapObj := map[string][]*Case{"key": {&caseObj}}
 	mapRes := map[interface{}]interface{}{}
-	doTestResponse(t, PackageResponse, Response_OK, mapObj, &mapRes, func() {
+	decodedResponse.RspObj = &mapRes
+	doTestResponse(t, PackageResponse, Response_OK, mapObj, decodedResponse, func() {
 		c, ok := mapRes["key"]
 		if !ok {
 			assert.FailNow(t, "no key in decoded response map")
diff --git a/java_exception.go b/java_exception.go
new file mode 100644
index 0000000..8193062
--- /dev/null
+++ b/java_exception.go
@@ -0,0 +1,89 @@
+// Copyright 2016-2019 Yincheng Fang
+//
+// 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 hessian
+
+func init() {
+	RegisterPOJO(&Throwable{})
+	RegisterPOJO(&Exception{})
+	RegisterPOJO(&StackTraceElement{})
+}
+
+////////////////////////////
+// Throwable interface
+////////////////////////////
+
+type Throwabler interface {
+	Error() string
+	JavaClassName() string
+}
+
+////////////////////////////
+// Throwable
+////////////////////////////
+
+type Throwable struct {
+	SerialVersionUID     int64
+	DetailMessage        string
+	SuppressedExceptions []Throwable
+	StackTrace           []StackTraceElement
+	Cause                *Throwable
+}
+
+func NewThrowable(detailMessage string) *Throwable {
+	return &Throwable{DetailMessage: detailMessage, StackTrace: []StackTraceElement{}}
+}
+
+func (e Throwable) Error() string {
+	return e.DetailMessage
+}
+
+func (Throwable) JavaClassName() string {
+	return "java.lang.Throwable"
+}
+
+////////////////////////////
+// Exception
+////////////////////////////
+
+type Exception struct {
+	SerialVersionUID     int64
+	DetailMessage        string
+	SuppressedExceptions []Exception
+	StackTrace           []StackTraceElement
+	Cause                *Exception
+}
+
+func NewException(detailMessage string) *Exception {
+	return &Exception{DetailMessage: detailMessage, StackTrace: []StackTraceElement{}}
+}
+
+func (e Exception) Error() string {
+	return e.DetailMessage
+}
+
+func (Exception) JavaClassName() string {
+	return "java.lang.Exception"
+}
+
+type StackTraceElement struct {
+	DeclaringClass string
+	MethodName     string
+	FileName       string
+	LineNumber     int
+}
+
+func (StackTraceElement) JavaClassName() string {
+	return "java.lang.StackTraceElement"
+}
diff --git a/java_exception_test.go b/java_exception_test.go
new file mode 100644
index 0000000..2421588
--- /dev/null
+++ b/java_exception_test.go
@@ -0,0 +1,32 @@
+// Copyright 2016-2019 Yincheng Fang
+//
+// 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 hessian
+
+import (
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestThrowable(t *testing.T) {
+	testDecodeFrameworkFunc(t, "throw_throwable", func(r interface{}) {
+		assert.Equal(t, "exception", r.(error).Error())
+	})
+}
+
+func TestException(t *testing.T) {
+	testDecodeFrameworkFunc(t, "throw_exception", func(r interface{}) {
+		assert.Equal(t, "exception", r.(error).Error())
+	})
+}
diff --git a/list.go b/list.go
index 3525981..c55ef5e 100644
--- a/list.go
+++ b/list.go
@@ -186,16 +186,15 @@
 			return nil, perrors.WithStack(err)
 		}
 
-		if it == nil {
-			break
-		}
-
-		v := EnsureRawValue(it)
 		if isVariableArr {
-			aryValue = reflect.Append(aryValue, v)
+			if it != nil {
+				aryValue = reflect.Append(aryValue, EnsureRawValue(it))
+			} else {
+				aryValue = reflect.Append(aryValue, NilValue)
+			}
 			holder.change(aryValue)
 		} else {
-			SetValue(aryValue.Index(j), v)
+			arr[j] = it
 		}
 	}
 
@@ -239,7 +238,11 @@
 		}
 
 		if isVariableArr {
-			aryValue = reflect.Append(aryValue, EnsureRawValue(it))
+			if it != nil {
+				aryValue = reflect.Append(aryValue, EnsureRawValue(it))
+			} else {
+				aryValue = reflect.Append(aryValue, NilValue)
+			}
 			holder.change(aryValue)
 		} else {
 			ary[j] = it
diff --git a/list_test.go b/list_test.go
index 12558c5..1832c8e 100644
--- a/list_test.go
+++ b/list_test.go
@@ -43,6 +43,8 @@
 }
 
 func TestList(t *testing.T) {
+	RegisterPOJOs(new(A0), new(A1))
+
 	testDecodeFramework(t, "replyTypedFixedList_0", []interface{}{})
 	testDecodeFramework(t, "replyTypedFixedList_1", []interface{}{"1"})
 	testDecodeFramework(t, "replyTypedFixedList_7", []interface{}{"1", "2", "3", "4", "5", "6", "7"})
@@ -51,4 +53,18 @@
 	testDecodeFramework(t, "replyUntypedFixedList_1", []interface{}{"1"})
 	testDecodeFramework(t, "replyUntypedFixedList_7", []interface{}{"1", "2", "3", "4", "5", "6", "7"})
 	testDecodeFramework(t, "replyUntypedFixedList_8", []interface{}{"1", "2", "3", "4", "5", "6", "7", "8"})
+
+	testDecodeFramework(t, "customReplyTypedFixedListHasNull", []interface{}{new(A0), new(A1), nil})
+	testDecodeFramework(t, "customReplyTypedVariableListHasNull", []interface{}{new(A0), new(A1), nil})
+	testDecodeFramework(t, "customReplyUntypedFixedListHasNull", []interface{}{new(A0), new(A1), nil})
+	testDecodeFramework(t, "customReplyUntypedVariableListHasNull", []interface{}{new(A0), new(A1), nil})
+}
+
+func TestListEncode(t *testing.T) {
+	testJavaDecode(t, "argUntypedFixedList_0", []interface{}{})
+	testJavaDecode(t, "argUntypedFixedList_1", []interface{}{"1"})
+	testJavaDecode(t, "argUntypedFixedList_7", []interface{}{"1", "2", "3", "4", "5", "6", "7"})
+	testJavaDecode(t, "argUntypedFixedList_8", []interface{}{"1", "2", "3", "4", "5", "6", "7", "8"})
+
+	testJavaDecode(t, "customArgUntypedFixedListHasNull", []interface{}{new(A0), new(A1), nil})
 }
diff --git a/object.go b/object.go
index 2a13170..b653f6b 100644
--- a/object.go
+++ b/object.go
@@ -330,15 +330,15 @@
 		fldRawValue := UnpackPtrValue(field)
 
 		kind := fldTyp.Kind()
-		switch {
-		case kind == reflect.String:
+		switch kind {
+		case reflect.String:
 			str, err := d.decString(TAG_READ)
 			if err != nil {
 				return nil, perrors.Wrapf(err, "decInstance->ReadString: %s", fieldName)
 			}
 			fldRawValue.SetString(str)
 
-		case kind == reflect.Int32 || kind == reflect.Int16:
+		case reflect.Int32, reflect.Int16, reflect.Int8:
 			num, err := d.decInt32(TAG_READ)
 			if err != nil {
 				// java enum
@@ -351,13 +351,17 @@
 					enumValue, _ := s.(JavaEnum)
 					num = int32(enumValue)
 				} else {
-					return nil, perrors.Wrapf(err, "decInstance->ParseInt, field name:%s", fieldName)
+					return nil, perrors.Wrapf(err, "decInstance->decInt32, field name:%s", fieldName)
 				}
 			}
-
 			fldRawValue.SetInt(int64(num))
-
-		case kind == reflect.Int || kind == reflect.Int64 || kind == reflect.Uint64:
+		case reflect.Uint16, reflect.Uint8:
+			num, err := d.decInt32(TAG_READ)
+			if err != nil {
+				return nil, perrors.Wrapf(err, "decInstance->decInt32, field name:%s", fieldName)
+			}
+			fldRawValue.SetUint(uint64(num))
+		case reflect.Uint, reflect.Int, reflect.Int64:
 			num, err := d.decInt64(TAG_READ)
 			if err != nil {
 				if fldTyp.Implements(javaEnumType) {
@@ -374,29 +378,34 @@
 			}
 
 			fldRawValue.SetInt(num)
-
-		case kind == reflect.Bool:
+		case reflect.Uint32, reflect.Uint64:
+			num, err := d.decInt64(TAG_READ)
+			if err != nil {
+				return nil, perrors.Wrapf(err, "decInstance->decInt64, field name:%s", fieldName)
+			}
+			fldRawValue.SetUint(uint64(num))
+		case reflect.Bool:
 			b, err := d.Decode()
 			if err != nil {
 				return nil, perrors.Wrapf(err, "decInstance->Decode field name:%s", fieldName)
 			}
 			fldRawValue.SetBool(b.(bool))
 
-		case kind == reflect.Float32 || kind == reflect.Float64:
+		case reflect.Float32, reflect.Float64:
 			num, err := d.decDouble(TAG_READ)
 			if err != nil {
 				return nil, perrors.Wrapf(err, "decInstance->decDouble field name:%s", fieldName)
 			}
 			fldRawValue.SetFloat(num.(float64))
 
-		case kind == reflect.Map:
+		case reflect.Map:
 			// decode map should use the original field value for correct value setting
 			err := d.decMapByValue(field)
 			if err != nil {
 				return nil, perrors.Wrapf(err, "decInstance->decMapByValue field name: %s", fieldName)
 			}
 
-		case kind == reflect.Slice || kind == reflect.Array:
+		case reflect.Slice, reflect.Array:
 			m, err := d.decList(TAG_READ)
 			if err != nil {
 				if err == io.EOF {
@@ -410,7 +419,7 @@
 			if err != nil {
 				return nil, err
 			}
-		case kind == reflect.Struct:
+		case reflect.Struct:
 			var (
 				err error
 				s   interface{}
diff --git a/object_test.go b/object_test.go
index 4a530b2..6c5b44e 100644
--- a/object_test.go
+++ b/object_test.go
@@ -15,6 +15,7 @@
 package hessian
 
 import (
+	"math"
 	"reflect"
 	"testing"
 )
@@ -328,25 +329,27 @@
 }
 
 func TestObject(t *testing.T) {
-	RegisterPOJO(&A0{})
-	RegisterPOJO(&A1{})
-	RegisterPOJO(&A2{})
-	RegisterPOJO(&A3{})
-	RegisterPOJO(&A4{})
-	RegisterPOJO(&A5{})
-	RegisterPOJO(&A6{})
-	RegisterPOJO(&A7{})
-	RegisterPOJO(&A8{})
-	RegisterPOJO(&A9{})
-	RegisterPOJO(&A10{})
-	RegisterPOJO(&A11{})
-	RegisterPOJO(&A12{})
-	RegisterPOJO(&A13{})
-	RegisterPOJO(&A14{})
-	RegisterPOJO(&A15{})
-	RegisterPOJO(&A16{})
-	RegisterPOJO(&TestObjectStruct{})
-	RegisterPOJO(&TestConsStruct{})
+	RegisterPOJOs(
+		&A0{},
+		&A1{},
+		&A2{},
+		&A3{},
+		&A4{},
+		&A5{},
+		&A6{},
+		&A7{},
+		&A8{},
+		&A9{},
+		&A10{},
+		&A11{},
+		&A12{},
+		&A13{},
+		&A14{},
+		&A15{},
+		&A16{},
+		&TestObjectStruct{},
+		&TestConsStruct{},
+	)
 
 	testDecodeFramework(t, "replyObject_0", &A0{})
 	testDecodeFramework(t, "replyObject_1", &TestObjectStruct{Value: 0})
@@ -363,3 +366,100 @@
 	cons.Rest = &cons
 	testDecodeFramework(t, "replyObject_3", &cons)
 }
+
+type Tuple struct {
+	Byte    int8
+	Short   int16
+	Integer int32
+	Long    int64
+	Double  float32
+	B       uint8
+	S       uint16
+	I       uint32
+	L       uint64
+	D       float64
+}
+
+func (t Tuple) JavaClassName() string {
+	return "test.tuple.Tuple"
+}
+
+func TestDecodeJavaTupleObject(t *testing.T) {
+	tuple := &Tuple{
+		Byte:    1,
+		Short:   1,
+		Integer: 1,
+		Long:    1,
+		Double:  1.23,
+		B:       0x01,
+		S:       1,
+		I:       1,
+		L:       1,
+		D:       1.23,
+	}
+
+	RegisterPOJO(tuple)
+
+	testDecodeJavaData(t, "getTheTuple", "test.tuple.TupleProviderImpl", tuple)
+}
+
+func TestEncodeDecodeTuple(t *testing.T) {
+	doTestEncodeDecodeTuple(t, &Tuple{
+		Byte:    1,
+		Short:   1,
+		Integer: 1,
+		Long:    1,
+		Double:  1.23,
+		B:       0x01,
+		S:       1,
+		I:       1,
+		L:       1,
+		D:       1.23,
+	})
+
+	doTestEncodeDecodeTuple(t, &Tuple{
+		Byte:    math.MinInt8,
+		Short:   math.MinInt16,
+		Integer: math.MinInt32,
+		Long:    math.MinInt64,
+		Double:  -99.99,
+		B:       0x00,
+		S:       0,
+		I:       0,
+		L:       0,
+		D:       -9999.9999,
+	})
+
+	doTestEncodeDecodeTuple(t, &Tuple{
+		Byte:    math.MaxInt8,
+		Short:   math.MaxInt16,
+		Integer: math.MaxInt32,
+		Long:    math.MaxInt64,
+		Double:  math.MaxFloat32,
+		B:       0xFF,
+		S:       0xFFFF,
+		I:       0xFFFFFFFF,
+		L:       0xFFFFFFFFFFFFFFFF,
+		D:       math.MaxFloat64,
+	})
+}
+
+func doTestEncodeDecodeTuple(t *testing.T, tuple *Tuple) {
+	e := NewEncoder()
+	err := e.encObject(tuple)
+	if err != nil {
+		t.Error(err)
+		t.FailNow()
+	}
+
+	d := NewDecoder(e.buffer)
+	decObj, err := d.Decode()
+	if err != nil {
+		t.Error(err)
+		t.FailNow()
+	}
+
+	if !reflect.DeepEqual(tuple, decObj) {
+		t.Errorf("expect: %v, but get: %v", tuple, decObj)
+	}
+}
diff --git a/pojo.go b/pojo.go
index d4403e4..1494b33 100644
--- a/pojo.go
+++ b/pojo.go
@@ -184,6 +184,17 @@
 	return structInfo.index
 }
 
+// RegisterPOJOs register a POJO instance arr @os. The return value is @os's
+// mathching index array, in which "-1" means its matching POJO has been registered.
+func RegisterPOJOs(os ...POJO) []int {
+	arr := make([]int, len(os))
+	for i := range os {
+		arr[i] = RegisterPOJO(os[i])
+	}
+
+	return arr
+}
+
 // RegisterJavaEnum Register a value type JavaEnum variable.
 func RegisterJavaEnum(o POJOEnum) int {
 	var (
diff --git a/ref.go b/ref.go
index 299556a..e9c08d9 100644
--- a/ref.go
+++ b/ref.go
@@ -28,6 +28,10 @@
 	// record the kind of target, objects are the same only if the address and kind are the same
 	kind reflect.Kind
 
+	// Different struct may share the same address and kind,
+	// so using type information to distinguish them.
+	tp reflect.Type
+
 	// ref index
 	index int
 }
@@ -87,6 +91,7 @@
 func (e *Encoder) checkRefMap(v reflect.Value) (int, bool) {
 	var (
 		kind reflect.Kind
+		tp   reflect.Type
 		addr unsafe.Pointer
 	)
 
@@ -95,6 +100,9 @@
 			v = v.Elem()
 		}
 		kind = v.Elem().Kind()
+		if kind != reflect.Invalid {
+			tp = v.Elem().Type()
+		}
 		if kind == reflect.Slice || kind == reflect.Map {
 			addr = unsafe.Pointer(v.Elem().Pointer())
 		} else {
@@ -102,6 +110,7 @@
 		}
 	} else {
 		kind = v.Kind()
+		tp = v.Type()
 		switch kind {
 		case reflect.Slice, reflect.Map:
 			addr = unsafe.Pointer(v.Pointer())
@@ -111,15 +120,21 @@
 	}
 
 	if elem, ok := e.refMap[addr]; ok {
-		// the array addr is equal to the first elem, which must ignore
 		if elem.kind == kind {
-			return elem.index, ok
+			// If kind is not struct, just return the index. Otherwise,
+			// check whether the types are same, because the different
+			// empty struct may share the same address and kind.
+			if elem.kind != reflect.Struct {
+				return elem.index, ok
+			} else if elem.tp == tp {
+				return elem.index, ok
+			}
 		}
 		return 0, false
 	}
 
 	n := len(e.refMap)
-	e.refMap[addr] = _refElem{kind, n}
+	e.refMap[addr] = _refElem{kind, tp, n}
 	return 0, false
 }
 
diff --git a/response.go b/response.go
index c610d34..7ccc57e 100644
--- a/response.go
+++ b/response.go
@@ -26,6 +26,12 @@
 	perrors "github.com/pkg/errors"
 )
 
+type Response struct {
+	RspObj    interface{}
+	Exception error
+	//Attachments map[string]string
+}
+
 // dubbo-remoting/dubbo-remoting-api/src/main/java/com/alibaba/dubbo/remoting/exchange/codec/ExchangeCodec.java
 // v2.7.1 line 256 encodeResponse
 // hessian encode response
@@ -56,39 +62,55 @@
 	encoder := NewEncoder()
 	encoder.Append(byteArray[:HEADER_LENGTH])
 
-	if hb {
-		encoder.Encode(nil)
-	} else {
-		// com.alibaba.dubbo.rpc.protocol.dubbo.DubboCodec.DubboCodec.java
-		// v2.7.1 line191 encodeRequestData
-
-		atta := isSupportResponseAttachment(attachments[DUBBO_VERSION_KEY])
-
-		var resWithException, resValue, resNullValue int32
-		if atta {
-			resWithException = RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS
-			resValue = RESPONSE_VALUE_WITH_ATTACHMENTS
-			resNullValue = RESPONSE_NULL_VALUE_WITH_ATTACHMENTS
+	if header.ResponseStatus == Response_OK {
+		if hb {
+			encoder.Encode(nil)
 		} else {
-			resWithException = RESPONSE_WITH_EXCEPTION
-			resValue = RESPONSE_VALUE
-			resNullValue = RESPONSE_NULL_VALUE
-		}
+			// com.alibaba.dubbo.rpc.protocol.dubbo.DubboCodec.DubboCodec.java
+			// v2.7.1 line191 encodeResponseData
 
-		if e, ok := ret.(error); ok { // throw error
-			encoder.Encode(resWithException)
-			encoder.Encode(e.Error())
-		} else {
-			if ret == nil {
-				encoder.Encode(resNullValue)
+			atta := isSupportResponseAttachment(attachments[DUBBO_VERSION_KEY])
+
+			var resWithException, resValue, resNullValue int32
+			if atta {
+				resWithException = RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS
+				resValue = RESPONSE_VALUE_WITH_ATTACHMENTS
+				resNullValue = RESPONSE_NULL_VALUE_WITH_ATTACHMENTS
 			} else {
-				encoder.Encode(resValue)
-				encoder.Encode(ret) // result
+				resWithException = RESPONSE_WITH_EXCEPTION
+				resValue = RESPONSE_VALUE
+				resNullValue = RESPONSE_NULL_VALUE
+			}
+
+			if e, ok := ret.(error); ok { // throw error
+				encoder.Encode(resWithException)
+				if t, ok := e.(Throwabler); ok {
+					encoder.Encode(t)
+				} else {
+					encoder.Encode(NewThrowable(e.Error()))
+				}
+			} else {
+				if ret == nil {
+					encoder.Encode(resNullValue)
+				} else {
+					encoder.Encode(resValue)
+					encoder.Encode(ret) // result
+				}
+			}
+
+			if atta {
+				encoder.Encode(attachments) // attachments
 			}
 		}
-
-		if atta {
-			encoder.Encode(attachments) // attachments
+	} else {
+		// com.alibaba.dubbo.remoting.exchange.codec.ExchangeCodec
+		// v2.6.5 line280 encodeResponse
+		if e, ok := ret.(error); ok { // throw error
+			encoder.Encode(e.Error())
+		} else if e, ok := ret.(string); ok {
+			encoder.Encode(e)
+		} else {
+			return nil, perrors.New("Ret must be error or string!")
 		}
 	}
 
@@ -106,7 +128,7 @@
 
 // hessian decode response body
 // todo: need to read attachments
-func unpackResponseBody(buf []byte, rspObj interface{}) error {
+func unpackResponseBody(buf []byte, response *Response) error {
 	// body
 	decoder := NewDecoder(buf[:])
 	rspType, err := decoder.Decode()
@@ -120,17 +142,22 @@
 		if err != nil {
 			return perrors.WithStack(err)
 		}
-		return perrors.Errorf("got exception: %+v", expt)
+		if e, ok := expt.(error); ok {
+			response.Exception = e
+			return nil
+		}
+		response.Exception = perrors.Errorf("got exception: %+v", expt)
+		return nil
 
 	case RESPONSE_VALUE, RESPONSE_VALUE_WITH_ATTACHMENTS:
 		rsp, err := decoder.Decode()
 		if err != nil {
 			return perrors.WithStack(err)
 		}
-		return perrors.WithStack(ReflectResponse(rsp, rspObj))
+		return perrors.WithStack(ReflectResponse(rsp, response.RspObj))
 
 	case RESPONSE_NULL_VALUE, RESPONSE_NULL_VALUE_WITH_ATTACHMENTS:
-		return perrors.New("Received null")
+		return nil
 	}
 
 	return nil
diff --git a/test_dubbo/pom.xml b/test_dubbo/pom.xml
index df315c6..92051cf 100644
--- a/test_dubbo/pom.xml
+++ b/test_dubbo/pom.xml
@@ -14,7 +14,7 @@
         <dependency>
             <groupId>org.apache.dubbo</groupId>
             <artifactId>dubbo</artifactId>
-            <version>2.7.0</version>
+            <version>2.7.1</version>
             <scope>compile</scope>
         </dependency>
     </dependencies>
diff --git a/test_hessian/pom.xml b/test_hessian/pom.xml
index f05ecf9..031ddd1 100644
--- a/test_hessian/pom.xml
+++ b/test_hessian/pom.xml
@@ -1,17 +1,19 @@
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
- 
+
     <groupId>test</groupId>
     <artifactId>test_hessian</artifactId>
     <version>1.0.0</version>
 
-    <properties>
-        <maven.compiler.source>1.8</maven.compiler.source>
-        <maven.compiler.target>1.8</maven.compiler.target>
-    </properties>
- 
     <dependencies>
         <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>hessian-lite</artifactId>
+            <version>3.2.6</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
             <groupId>com.caucho</groupId>
             <artifactId>hessian</artifactId>
             <version>4.0.60</version>
@@ -28,6 +30,13 @@
     <build>
         <plugins>
             <plugin>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>1.8</source>
+                    <target>1.8</target>
+                </configuration>
+            </plugin>
+            <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-shade-plugin</artifactId>
                 <version>3.1.1</version>
@@ -39,7 +48,8 @@
                         </goals>
                         <configuration>
                             <transformers>
-                                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+                                <transformer
+                                        implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                     <mainClass>test.Hessian</mainClass>
                                 </transformer>
                             </transformers>
diff --git a/test_hessian/src/main/java/test/Hessian.java b/test_hessian/src/main/java/test/Hessian.java
index 1e23005..5f89645 100644
--- a/test_hessian/src/main/java/test/Hessian.java
+++ b/test_hessian/src/main/java/test/Hessian.java
@@ -14,7 +14,8 @@
 
 package test;
 
-import com.caucho.hessian.io.Hessian2Output;
+import com.alibaba.com.caucho.hessian.io.Hessian2Input;
+import com.alibaba.com.caucho.hessian.io.Hessian2Output;
 import com.caucho.hessian.test.TestHessian2Servlet;
 
 import java.lang.reflect.Method;
@@ -22,13 +23,52 @@
 
 public class Hessian {
     public static void main(String[] args) throws Exception {
-        Method method = TestHessian2Servlet.class.getMethod(args[0]);
+        if (args.length > 1) {
+            testCustomClassMethod(args[0], args[1]);
+            return;
+        }
 
-        TestHessian2Servlet servlet = new TestHessian2Servlet();
-        Object object = method.invoke(servlet);
+        if (args[0].startsWith("reply")) {
+            Method method = TestHessian2Servlet.class.getMethod(args[0]);
+            TestHessian2Servlet servlet = new TestHessian2Servlet();
+            Object object = method.invoke(servlet);
 
+            Hessian2Output output = new Hessian2Output(System.out);
+            output.writeObject(object);
+            output.flush();
+        } else if (args[0].startsWith("customReply")) {
+            Method method = TestCustomReply.class.getMethod(args[0]);
+            TestCustomReply testCustomReply = new TestCustomReply(System.out);
+            method.invoke(testCustomReply);
+        } else if (args[0].startsWith("arg")) {
+            Hessian2Input input = new Hessian2Input(System.in);
+            Object o = input.readObject();
+
+            Method method = TestHessian2Servlet.class.getMethod(args[0], Object.class);
+            TestHessian2Servlet servlet = new TestHessian2Servlet();
+            System.out.print(method.invoke(servlet, o));
+        } else if (args[0].startsWith("customArg")) {
+            Method method = TestCustomDecode.class.getMethod(args[0]);
+            TestCustomDecode testCustomDecode = new TestCustomDecode(System.in);
+            System.out.print(method.invoke(testCustomDecode));
+        } else if (args[0].startsWith("throw_")) {
+            Method method = method = TestThrowable.class.getMethod(args[0]);
+            TestHessian2Servlet servlet = new TestHessian2Servlet();
+            Object object = method.invoke(servlet);
+
+            Hessian2Output output = new Hessian2Output(System.out);
+            output.writeObject(object);
+            output.flush();
+        }
+    }
+
+    private static void testCustomClassMethod(String methodName, String className) throws Exception {
+        Class<?> clazz = Class.forName(className);
+        Method method = clazz.getMethod(methodName);
+        Object target = clazz.newInstance();
+        Object result = method.invoke(target);
         Hessian2Output output = new Hessian2Output(System.out);
-        output.writeObject(object);
+        output.writeObject(result);
         output.flush();
     }
 }
\ No newline at end of file
diff --git a/test_hessian/src/main/java/test/TestCustomDecode.java b/test_hessian/src/main/java/test/TestCustomDecode.java
new file mode 100644
index 0000000..05413f0
--- /dev/null
+++ b/test_hessian/src/main/java/test/TestCustomDecode.java
@@ -0,0 +1,43 @@
+// Copyright 2019 Xinge Gao

+//

+// 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 test;

+

+import com.caucho.hessian.io.Hessian2Input;

+import com.caucho.hessian.test.A0;

+import com.caucho.hessian.test.A1;

+

+import java.io.InputStream;

+import java.util.ArrayList;

+import java.util.List;

+

+

+public class TestCustomDecode {

+

+    private Hessian2Input input;

+

+    TestCustomDecode(InputStream is) {

+        input = new Hessian2Input(is);

+    }

+

+    public Object customArgUntypedFixedListHasNull() throws Exception {

+        List list = new ArrayList();

+        list.add(new A0());

+        list.add(new A1());

+        list.add(null);

+

+        Object o = input.readObject();

+        return list.equals(o);

+    }

+}
\ No newline at end of file
diff --git a/test_hessian/src/main/java/test/TestCustomReply.java b/test_hessian/src/main/java/test/TestCustomReply.java
new file mode 100644
index 0000000..e50a49a
--- /dev/null
+++ b/test_hessian/src/main/java/test/TestCustomReply.java
@@ -0,0 +1,118 @@
+// Copyright 2019 Xinge Gao
+//
+// 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 test;
+
+import com.alibaba.com.caucho.hessian.io.Hessian2Output;
+import com.caucho.hessian.test.A0;
+import com.caucho.hessian.test.A1;
+
+import java.io.OutputStream;
+import java.util.Date;
+import java.util.HashMap;
+
+
+public class TestCustomReply {
+
+    private Hessian2Output output;
+    private HashMap<Class<?>, String> typeMap;
+
+    TestCustomReply(OutputStream os) {
+        output = new Hessian2Output(os);
+
+        typeMap = new HashMap<>();
+        typeMap.put(Void.TYPE, "void");
+        typeMap.put(Boolean.class, "boolean");
+        typeMap.put(Byte.class, "byte");
+        typeMap.put(Short.class, "short");
+        typeMap.put(Integer.class, "int");
+        typeMap.put(Long.class, "long");
+        typeMap.put(Float.class, "float");
+        typeMap.put(Double.class, "double");
+        typeMap.put(Character.class, "char");
+        typeMap.put(String.class, "string");
+        typeMap.put(StringBuilder.class, "string");
+        typeMap.put(Object.class, "object");
+        typeMap.put(Date.class, "date");
+        typeMap.put(Boolean.TYPE, "boolean");
+        typeMap.put(Byte.TYPE, "byte");
+        typeMap.put(Short.TYPE, "short");
+        typeMap.put(Integer.TYPE, "int");
+        typeMap.put(Long.TYPE, "long");
+        typeMap.put(Float.TYPE, "float");
+        typeMap.put(Double.TYPE, "double");
+        typeMap.put(Character.TYPE, "char");
+        typeMap.put(boolean[].class, "[boolean");
+        typeMap.put(byte[].class, "[byte");
+        typeMap.put(short[].class, "[short");
+        typeMap.put(int[].class, "[int");
+        typeMap.put(long[].class, "[long");
+        typeMap.put(float[].class, "[float");
+        typeMap.put(double[].class, "[double");
+        typeMap.put(char[].class, "[char");
+        typeMap.put(String[].class, "[string");
+        typeMap.put(Object[].class, "[object");
+    }
+
+    public void customReplyTypedFixedListHasNull() throws Exception {
+        Object[] o = new Object[]{new A0(), new A1(), null};
+        output.writeObject(o);
+        output.flush();
+    }
+
+    public void customReplyTypedVariableListHasNull() throws Exception {
+        Object[] o = new Object[]{new A0(), new A1(), null};
+        if (output.addRef(o)) {
+            return;
+        }
+        boolean hasEnd = output.writeListBegin(-1, typeMap.get(o.getClass()));
+        for (Object tmp: o) {
+            output.writeObject(tmp);
+        }
+        if (hasEnd) {
+            output.writeListEnd();
+        }
+        output.flush();
+    }
+
+    public void customReplyUntypedFixedListHasNull() throws Exception {
+        Object[] o = new Object[]{new A0(), new A1(), null};
+        if (output.addRef(o)) {
+            return;
+        }
+        boolean hasEnd = output.writeListBegin(o.length, null);
+        for (Object tmp: o) {
+            output.writeObject(tmp);
+        }
+        if (hasEnd) {
+            output.writeListEnd();
+        }
+        output.flush();
+    }
+
+    public void customReplyUntypedVariableListHasNull() throws Exception {
+        Object[] o = new Object[]{new A0(), new A1(), null};
+        if (output.addRef(o)) {
+            return;
+        }
+        boolean hasEnd = output.writeListBegin(-1, null);
+        for (Object tmp: o) {
+            output.writeObject(tmp);
+        }
+        if (hasEnd) {
+            output.writeListEnd();
+        }
+        output.flush();
+    }
+}
\ No newline at end of file
diff --git a/test_hessian/src/main/java/test/TestThrowable.java b/test_hessian/src/main/java/test/TestThrowable.java
new file mode 100644
index 0000000..9c9134f
--- /dev/null
+++ b/test_hessian/src/main/java/test/TestThrowable.java
@@ -0,0 +1,24 @@
+// Copyright 2016-2019 Yincheng Fang

+//

+// 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 test;

+

+public class TestThrowable {

+    public static Object throw_exception()  {

+        return new Exception("exception");

+    }

+    public static Object throw_throwable()  {

+        return new Throwable("exception");

+    }

+}

diff --git a/test_hessian/src/main/java/test/tuple/Tuple.java b/test_hessian/src/main/java/test/tuple/Tuple.java
new file mode 100644
index 0000000..3854a91
--- /dev/null
+++ b/test_hessian/src/main/java/test/tuple/Tuple.java
@@ -0,0 +1,99 @@
+package test.tuple;
+
+import java.io.Serializable;
+
+public class Tuple implements Serializable {
+    private static final long serialVersionUID = -1L;
+
+    Integer Integer;
+    Byte Byte;
+    Short Short;
+    Long Long;
+    Double Double;
+    int i;
+    byte b;
+    short s;
+    long l;
+    double d;
+
+
+    public java.lang.Integer getInteger() {
+        return Integer;
+    }
+
+    public void setInteger(java.lang.Integer integer) {
+        Integer = integer;
+    }
+
+    public java.lang.Byte getByte() {
+        return Byte;
+    }
+
+    public void setByte(java.lang.Byte aByte) {
+        Byte = aByte;
+    }
+
+    public java.lang.Short getShort() {
+        return Short;
+    }
+
+    public void setShort(java.lang.Short aShort) {
+        Short = aShort;
+    }
+
+    public java.lang.Long getLong() {
+        return Long;
+    }
+
+    public void setLong(java.lang.Long aLong) {
+        Long = aLong;
+    }
+
+    public int getI() {
+        return i;
+    }
+
+    public void setI(int i) {
+        this.i = i;
+    }
+
+    public byte getB() {
+        return b;
+    }
+
+    public void setB(byte b) {
+        this.b = b;
+    }
+
+    public short getS() {
+        return s;
+    }
+
+    public void setS(short s) {
+        this.s = s;
+    }
+
+    public long getL() {
+        return l;
+    }
+
+    public void setL(long l) {
+        this.l = l;
+    }
+
+    public java.lang.Double getDouble() {
+        return Double;
+    }
+
+    public void setDouble(java.lang.Double aDouble) {
+        Double = aDouble;
+    }
+
+    public double getD() {
+        return d;
+    }
+
+    public void setD(double d) {
+        this.d = d;
+    }
+}
\ No newline at end of file
diff --git a/test_hessian/src/main/java/test/tuple/TupleProvider.java b/test_hessian/src/main/java/test/tuple/TupleProvider.java
new file mode 100644
index 0000000..075a098
--- /dev/null
+++ b/test_hessian/src/main/java/test/tuple/TupleProvider.java
@@ -0,0 +1,6 @@
+package test.tuple;
+
+public interface TupleProvider {
+
+    Tuple getTheTuple();
+}
diff --git a/test_hessian/src/main/java/test/tuple/TupleProviderImpl.java b/test_hessian/src/main/java/test/tuple/TupleProviderImpl.java
new file mode 100644
index 0000000..5237680
--- /dev/null
+++ b/test_hessian/src/main/java/test/tuple/TupleProviderImpl.java
@@ -0,0 +1,21 @@
+package test.tuple;
+
+public class TupleProviderImpl implements TupleProvider {
+
+    @Override
+    public Tuple getTheTuple() {
+        Tuple result = new Tuple();
+        result.setB((byte) 1);
+        result.setByte(Byte.valueOf("1"));
+        result.setI(1);
+        result.setInteger(Integer.valueOf("1"));
+        result.setL(1L);
+        result.setLong(Long.valueOf("1"));
+        result.setS((short) 1);
+        result.setShort(Short.valueOf("1"));
+        result.setD(1.23);
+        result.setDouble(Double.valueOf("1.23"));
+        return result;
+    }
+
+}