A Go implementation of the Hessian 2.0 serialization protocol, enabling seamless cross-language communication between Go and Java applications. Primarily used by Apache Dubbo-Go for RPC serialization.
Note: From v1.6.0+, the decoder skips non-existent fields (matching Java hessian behavior). Versions before v1.6.0 returned errors for non-existent fields.
go get github.com/apache/dubbo-go-hessian2
Requires Go 1.21+.
// Define a struct and implement the POJO interface type User struct { Name string Age int32 } func (User) JavaClassName() string { return "com.example.User" } // Register the type before encoding/decoding hessian.RegisterPOJO(&User{}) // Encode user := &User{Name: "Alice", Age: 30} encoder := hessian.NewEncoder() encoder.Encode(user) data := encoder.Buffer() // Decode obj, _ := hessian.NewDecoder(data).Decode() decoded := obj.(*User)
Any Go struct that needs to be serialized as a Java object must implement the POJO interface:
type POJO interface { JavaClassName() string // Returns the fully qualified Java class name }
This establishes the mapping between your Go struct and the corresponding Java class.
Before encoding or decoding custom types, you must register them. This is typically done in an init() function:
func init() { // Register a single type hessian.RegisterPOJO(&MyStruct{}) // Register multiple types at once hessian.RegisterPOJOs(&TypeA{}, &TypeB{}, &TypeC{}) // Register with a custom Java class name (overrides JavaClassName()) hessian.RegisterPOJOMapping("com.example.CustomName", &MyStruct{}) }
If a type is not registered, the decoder will:
map[interface{}]interface{}| Hessian Type | Java Type | Go Type |
|---|---|---|
| null | null | nil |
| binary | byte[] | []byte |
| boolean | boolean | bool |
| date | java.util.Date | time.Time |
| double | double | float64 |
| int | int | int32 |
| long | long | int64 |
| string | java.lang.String | string |
| list | java.util.List | slice |
| map | java.util.Map | map |
| object | custom class | struct |
| Java Type | Go Type |
|---|---|
| java.lang.Integer | *int32 |
| java.lang.Long | *int64 |
| java.lang.Boolean | *bool |
| java.lang.Short | *int16 |
| java.lang.Byte | *uint8 |
| java.lang.Float | *float32 |
| java.lang.Double | *float64 |
| java.lang.Character | *hessian.Rune |
| Java Type | Go Type / Package |
|---|---|
| java.math.BigDecimal | github.com/dubbogo/gost/math/big Decimal |
| java.math.BigInteger | github.com/dubbogo/gost/math/big Integer |
| java.sql.Date | github.com/apache/dubbo-go-hessian2/java_sql_time Date |
| java.sql.Time | github.com/apache/dubbo-go-hessian2/java_sql_time Time |
| java.util.UUID | github.com/apache/dubbo-go-hessian2/java_util UUID |
| java.util.Locale | github.com/apache/dubbo-go-hessian2/java_util Locale |
| Java 8 time types (LocalDate, LocalTime, LocalDateTime, ZonedDateTime, Instant, Duration, Period, etc.) | github.com/apache/dubbo-go-hessian2/java8_time |
Tip: Avoid defining objects that only exist in one language. Use error codes/messages instead of Java exceptions for cross-language communication.
// Create a new encoder encoder := hessian.NewEncoder() // Encode a value (primitives, slices, maps, structs, etc.) err := encoder.Encode(value) // Get the encoded bytes data := encoder.Buffer() // Reset encoder state for reuse encoder.Clean() // Reset encoder state but reuse the underlying buffer encoder.ReuseBufferClean()
Special encoding methods:
// Encode a map as a typed Java object using "_class" key in the map encoder.EncodeMapClass(map[string]interface{}{"_class": "com.example.Foo", "name": "bar"}) // Encode a map as a specific Java class encoder.EncodeMapAsClass("com.example.Foo", map[string]interface{}{"name": "bar"})
// Standard decoder decoder := hessian.NewDecoder(data) // Strict mode - returns error for unregistered types decoder := hessian.NewStrictDecoder(data) // Decode the next value obj, err := decoder.Decode() // Reset decoder with new data (for reuse with object pools) decoder.Reset(newData)
Decoder modes:
| Constructor | Behavior |
|---|---|
NewDecoder(data) | Decodes unknown objects as maps |
NewStrictDecoder(data) | Returns error for unregistered objects |
NewDecoderWithSkip(data) | Skips non-existent fields |
NewCheapDecoderWithSkip(data) | Poolable decoder, use with Reset() |
hessian.RegisterPOJO(&MyStruct{}) // Register a POJO type hessian.RegisterPOJOs(&A{}, &B{}) // Register multiple POJOs hessian.RegisterPOJOMapping("com.example.Name", &Struct{}) // Register with custom Java class name hessian.RegisterJavaEnum(&MyEnum{}) // Register a Java enum type hessian.UnRegisterPOJOs(&A{}, &B{}) // Unregister POJOs hessian.SetCollectionSerialize(&MyHashSet{}) // Register a Java collection type hessian.SetSerializer("com.example.Foo", &FooSerializer{}) // Register a custom serializer
// Change the struct tag used for field name mapping (default: "hessian") hessian.SetTagIdentifier("json") // Look up a registered custom serializer serializer, ok := hessian.GetSerializer("com.example.Foo") // Find class info in the decoder classInfo := hessian.FindClassInfo("com.example.Foo")
type Circular struct { Value Previous *Circular Next *Circular } type Value struct { Num int } func (Circular) JavaClassName() string { return "com.company.Circular" } func init() { hessian.RegisterPOJO(&Circular{}) } // Encode c := &Circular{} c.Num = 12345 c.Previous = c // circular reference - handled automatically c.Next = c e := hessian.NewEncoder() if err := e.Encode(c); err != nil { panic(err) } data := e.Buffer() // Decode obj, err := hessian.NewDecoder(data).Decode() if err != nil { panic(err) } circular := obj.(*Circular) fmt.Println(circular.Num) // 12345
The encoder converts Go field names to lowerCamelCase by default. Use the hessian tag to override:
type MyUser struct { UserFullName string `hessian:"user_full_name"` // encoded as "user_full_name" FamilyPhoneNumber string // encoded as "familyPhoneNumber" (default) } func (MyUser) JavaClassName() string { return "com.company.myuser" }
When decoding, fields are matched in the following order:
hessian tag valuemobilePhone matches MobilePhoneMobilePhone matches MobilePhonemobilephone matches MobilePhonetype MyUser struct { MobilePhone string `hessian:"mobile-phone"` } // Incoming field "mobile-phone" -> matched via tag (rule 1) // Incoming field "mobilePhone" -> matched via lowerCamelCase (rule 2) // Incoming field "MobilePhone" -> matched via exact case (rule 3) // Incoming field "mobilephone" -> matched via lowercase (rule 4)
Use SetTagIdentifier to read field names from a different struct tag (e.g., json):
hessian.SetTagIdentifier("json")
type MyUser struct {
UserFullName string `json:"user_full_name"`
FamilyPhoneNumber string
}
func (MyUser) JavaClassName() string {
return "com.company.myuser"
}
When a Java method expects a parent class but you send a subclass, implement the Param interface:
Java side:
public abstract class User {} public class MyUser extends User implements Serializable { private String userFullName; private String familyPhoneNumber; } public interface UserProvider { String GetUser(User user); // accepts parent type }
Go side:
type MyUser struct { UserFullName string `hessian:"userFullName"` FamilyPhoneNumber string } func (m *MyUser) JavaClassName() string { return "com.company.MyUser" } // JavaParamName tells the encoder to use the parent class name in the method signature func (m *MyUser) JavaParamName() string { return "com.company.User" }
Map a Java collection class (e.g., HashSet) to a Go struct:
type JavaHashSet struct {
value []interface{}
}
func (j *JavaHashSet) Get() []interface{} { return j.value }
func (j *JavaHashSet) Set(v []interface{}) { j.value = v }
func (j *JavaHashSet) JavaClassName() string { return "java.util.HashSet" }
func init() {
hessian.SetCollectionSerialize(&JavaHashSet{})
}
Without this registration, Java collections are decoded as []interface{}.
type Color int32
const (
RED Color = 0
GREEN Color = 1
BLUE Color = 2
)
var colorNames = map[Color]string{
RED: "RED", GREEN: "GREEN", BLUE: "BLUE",
}
var colorValues = map[string]Color{
"RED": RED, "GREEN": GREEN, "BLUE": BLUE,
}
func (c Color) JavaClassName() string { return "com.example.Color" }
func (c Color) String() string { return colorNames[c] }
func (c Color) EnumValue(s string) hessian.JavaEnum {
return hessian.JavaEnum(colorValues[s])
}
func init() {
hessian.RegisterJavaEnum(RED)
}
Implement the Serializer interface for full control over encoding/decoding:
type MySerializer struct{} func (s *MySerializer) EncObject(encoder *hessian.Encoder, obj hessian.POJO) error { // Custom encoding logic return nil } func (s *MySerializer) DecObject(decoder *hessian.Decoder, typ reflect.Type, cls *hessian.ClassInfo) (interface{}, error) { // Custom decoding logic return nil, nil } func init() { hessian.SetSerializer("com.example.MyClass", &MySerializer{}) }
By default, unregistered objects are decoded as maps. Use strict mode to get errors instead:
decoder := hessian.NewDecoder(data) decoder.Strict = true // returns error for unregistered types // Or use the convenience constructor: decoder = hessian.NewStrictDecoder(data)
For high-performance scenarios, reuse encoder/decoder instances:
// Encoder reuse encoder := hessian.NewEncoder() encoder.Encode(obj1) data1 := encoder.Buffer() encoder.Clean() // or encoder.ReuseBufferClean() to keep the buffer encoder.Encode(obj2) data2 := encoder.Buffer() // Decoder reuse (poolable decoder) decoder := hessian.NewCheapDecoderWithSkip(data1) obj1, _ := decoder.Decode() decoder.Reset(data2) // reuse with new data obj2, _ := decoder.Decode()
Go struct embedding is supported for modeling Java inheritance:
type Animal struct { Name string } func (Animal) JavaClassName() string { return "com.example.Animal" } type Dog struct { Animal // embedded parent struct Breed string } func (Dog) JavaClassName() string { return "com.example.Dog" }
Avoid these patterns:
Duplicate field names across parents - ambiguous field resolution:
type A struct { Name string } type B struct { Name string } type C struct { A; B } // which Name?
Pointer embedding - nil parent at initialization, not supported:
type Dog struct { *Animal // will be nil in Dog{}, not supported }
A code generation tool for creating Go enum types compatible with Java enums. See tools/gen-go-enum/README.md for details.