mobile/bind: use objects to pass errors across the language barrier

Gobind uses strings for passing errors across the language barrier.
However, since Gobind doesn't have a concept of a nil string, it
can't separate an empty native string from a nil string.

In turn, that means that empty errors, exceptions or NSError * with
an empty description are treated as no error. With ObjC, empty errors
are replaced with a default string to workaround the issue, while
with Java empty errors are silently ignored.

Fix this by replacing strings with actual error objects, wrapping
the Go error, Java Throwable or ObjC NSError *, and letting the
existing bind machinery take care of passing the references across.

It's a large change for a small corner case, but I believe objects
are a better fit for exception that strings. Error objects also
naturally leads to future additions, for example accessing the
exception class name or chained exception.

Change-Id: Ie03b47cafcb231ad1e12a80195693fa7459c6265
Reviewed-on: https://go-review.googlesource.com/24100
Reviewed-by: David Crawshaw <crawshaw@golang.org>
diff --git a/bind/gen.go b/bind/gen.go
index 8a9889b..3d618c3 100644
--- a/bind/gen.go
+++ b/bind/gen.go
@@ -78,46 +78,59 @@
 // with the same name. Perhaps use the index from the order the package is
 // generated.
 func pkgPrefix(pkg *types.Package) string {
+	// The error type has no package
+	if pkg == nil {
+		return ""
+	}
 	return pkg.Name()
 }
 
 func (g *generator) init() {
-	g.pkgName = g.pkg.Name()
+	if g.pkg != nil {
+		g.pkgName = g.pkg.Name()
+	}
 	g.pkgPrefix = pkgPrefix(g.pkg)
 
-	scope := g.pkg.Scope()
-	hasExported := false
-	for _, name := range scope.Names() {
-		obj := scope.Lookup(name)
-		if !obj.Exported() {
-			continue
-		}
-		hasExported = true
-		switch obj := obj.(type) {
-		case *types.Func:
-			if isCallable(obj) {
-				g.funcs = append(g.funcs, obj)
+	if g.pkg != nil {
+		scope := g.pkg.Scope()
+		hasExported := false
+		for _, name := range scope.Names() {
+			obj := scope.Lookup(name)
+			if !obj.Exported() {
+				continue
 			}
-		case *types.TypeName:
-			named := obj.Type().(*types.Named)
-			switch t := named.Underlying().(type) {
-			case *types.Struct:
-				g.structs = append(g.structs, structInfo{obj, t})
-			case *types.Interface:
-				g.interfaces = append(g.interfaces, interfaceInfo{obj, t, makeIfaceSummary(t)})
+			hasExported = true
+			switch obj := obj.(type) {
+			case *types.Func:
+				if isCallable(obj) {
+					g.funcs = append(g.funcs, obj)
+				}
+			case *types.TypeName:
+				named := obj.Type().(*types.Named)
+				switch t := named.Underlying().(type) {
+				case *types.Struct:
+					g.structs = append(g.structs, structInfo{obj, t})
+				case *types.Interface:
+					g.interfaces = append(g.interfaces, interfaceInfo{obj, t, makeIfaceSummary(t)})
+				default:
+					g.otherNames = append(g.otherNames, obj)
+				}
+			case *types.Const:
+				g.constants = append(g.constants, obj)
+			case *types.Var:
+				g.vars = append(g.vars, obj)
 			default:
-				g.otherNames = append(g.otherNames, obj)
+				g.errorf("unsupported exported type for %s: %T", obj.Name(), obj)
 			}
-		case *types.Const:
-			g.constants = append(g.constants, obj)
-		case *types.Var:
-			g.vars = append(g.vars, obj)
-		default:
-			g.errorf("unsupported exported type for %s: %T", obj.Name(), obj)
 		}
-	}
-	if !hasExported {
-		g.errorf("no exported names in the package %q", g.pkg.Path())
+		if !hasExported {
+			g.errorf("no exported names in the package %q", g.pkg.Path())
+		}
+	} else {
+		// Bind the single supported type from the universe scope, error.
+		errType := types.Universe.Lookup("error").(*types.TypeName)
+		t := errType.Type().Underlying().(*types.Interface)
+		g.interfaces = append(g.interfaces, interfaceInfo{errType, t, makeIfaceSummary(t)})
 	}
 	for _, p := range g.allPkg {
 		scope := p.Scope()
@@ -150,9 +163,6 @@
 // cgoType returns the name of a Cgo type suitable for converting a value of
 // the given type.
 func (g *generator) cgoType(t types.Type) string {
-	if isErrorType(t) {
-		return g.cgoType(types.Typ[types.String])
-	}
 	switch t := t.(type) {
 	case *types.Basic:
 		switch t.Kind() {
diff --git a/bind/gengo.go b/bind/gengo.go
index cd06a04..6353a39 100644
--- a/bind/gengo.go
+++ b/bind/gengo.go
@@ -59,7 +59,7 @@
 		g.Printf(" := ")
 	}
 
-	g.Printf("%s.%s(", selectorLHS, o.Name())
+	g.Printf("%s%s(", selectorLHS, o.Name())
 	for i := 0; i < params.Len(); i++ {
 		if i > 0 {
 			g.Printf(", ")
@@ -85,16 +85,6 @@
 }
 
 func (g *goGen) genWrite(toVar, fromVar string, t types.Type, mode varMode) {
-	if isErrorType(t) {
-		g.Printf("var %s_str string\n", toVar)
-		g.Printf("if %s == nil {\n", fromVar)
-		g.Printf("    %s_str = \"\"\n", toVar)
-		g.Printf("} else {\n")
-		g.Printf("    %s_str = %s.Error()\n", toVar, fromVar)
-		g.Printf("}\n")
-		g.genWrite(toVar, toVar+"_str", types.Typ[types.String], mode)
-		return
-	}
 	switch t := t.(type) {
 	case *types.Basic:
 		switch t.Kind() {
@@ -205,7 +195,7 @@
 		g.Indent()
 		g.Printf("ref := _seq.FromRefNum(int32(refnum))\n")
 		g.genRead("_v", "v", f.Type(), modeRetained)
-		g.Printf("ref.Get().(*%s.%s).%s = _v\n", g.pkgName(g.pkg), obj.Name(), f.Name())
+		g.Printf("ref.Get().(*%s%s).%s = _v\n", g.pkgName(g.pkg), obj.Name(), f.Name())
 		g.Outdent()
 		g.Printf("}\n\n")
 
@@ -213,7 +203,7 @@
 		g.Printf("func proxy%s_%s_%s_Get(refnum C.int32_t) C.%s {\n", g.pkgPrefix, obj.Name(), f.Name(), g.cgoType(f.Type()))
 		g.Indent()
 		g.Printf("ref := _seq.FromRefNum(int32(refnum))\n")
-		g.Printf("v := ref.Get().(*%s.%s).%s\n", g.pkgName(g.pkg), obj.Name(), f.Name())
+		g.Printf("v := ref.Get().(*%s%s).%s\n", g.pkgName(g.pkg), obj.Name(), f.Name())
 		g.genWrite("_v", "v", f.Type(), modeRetained)
 		g.Printf("return _v\n")
 		g.Outdent()
@@ -228,8 +218,8 @@
 		g.genFuncSignature(m, obj.Name())
 		g.Indent()
 		g.Printf("ref := _seq.FromRefNum(int32(refnum))\n")
-		g.Printf("v := ref.Get().(*%s.%s)\n", g.pkgName(g.pkg), obj.Name())
-		g.genFuncBody(m, "v")
+		g.Printf("v := ref.Get().(*%s%s)\n", g.pkgName(g.pkg), obj.Name())
+		g.genFuncBody(m, "v.")
 		g.Outdent()
 		g.Printf("}\n\n")
 	}
@@ -242,7 +232,7 @@
 	}
 	// TODO(hyangah): non-struct pointer types (*int), struct type.
 
-	v := fmt.Sprintf("%s.%s", g.pkgName(g.pkg), o.Name())
+	v := fmt.Sprintf("%s%s", g.pkgName(g.pkg), o.Name())
 
 	// var I int
 	//
@@ -280,8 +270,8 @@
 		g.genFuncSignature(m, obj.Name())
 		g.Indent()
 		g.Printf("ref := _seq.FromRefNum(int32(refnum))\n")
-		g.Printf("v := ref.Get().(%s.%s)\n", g.pkgName(g.pkg), obj.Name())
-		g.genFuncBody(m, "v")
+		g.Printf("v := ref.Get().(%s%s)\n", g.pkgName(g.pkg), obj.Name())
+		g.genFuncBody(m, "v.")
 		g.Outdent()
 		g.Printf("}\n\n")
 	}
@@ -365,11 +355,6 @@
 }
 
 func (g *goGen) genRead(toVar, fromVar string, typ types.Type, mode varMode) {
-	if isErrorType(typ) {
-		g.genRead(toVar+"_str", fromVar, types.Typ[types.String], mode)
-		g.Printf("%s := toError(%s_str)\n", toVar, toVar)
-		return
-	}
 	switch t := typ.(type) {
 	case *types.Basic:
 		switch t.Kind() {
@@ -403,7 +388,7 @@
 			}
 			g.Printf("// Must be a Go object\n")
 			g.Printf("%s_ref := _seq.FromRefNum(int32(%s))\n", toVar, fromVar)
-			g.Printf("%s := %s_ref.Get().(*%s.%s)\n", toVar, toVar, g.pkgName(oPkg), o.Name())
+			g.Printf("%s := %s_ref.Get().(*%s%s)\n", toVar, toVar, g.pkgName(oPkg), o.Name())
 		default:
 			g.errorf("unsupported pointer type %s", t)
 		}
@@ -416,7 +401,7 @@
 			}
 			o := t.Obj()
 			oPkg := o.Pkg()
-			if !g.validPkg(oPkg) {
+			if !isErrorType(t) && !g.validPkg(oPkg) {
 				g.errorf("type %s is defined in %s, which is not bound", t, oPkg)
 				return
 			}
@@ -424,7 +409,7 @@
 			g.Printf("%s_ref := _seq.FromRefNum(int32(%s))\n", toVar, fromVar)
 			g.Printf("if %s_ref != nil {\n", toVar)
 			g.Printf("	if %s < 0 { // go object \n", fromVar)
-			g.Printf("  	 %s = %s_ref.Get().(%s.%s)\n", toVar, toVar, g.pkgName(oPkg), o.Name())
+			g.Printf("  	 %s = %s_ref.Get().(%s%s)\n", toVar, toVar, g.pkgName(oPkg), o.Name())
 			if hasProxy {
 				g.Printf("	} else { // foreign object \n")
 				g.Printf("	   %s = (*proxy%s_%s)(%s_ref)\n", toVar, pkgPrefix(oPkg), o.Name(), toVar)
@@ -456,7 +441,7 @@
 
 		switch t.Underlying().(type) {
 		case *types.Interface, *types.Struct:
-			return fmt.Sprintf("%s.%s", g.pkgName(oPkg), types.TypeString(typ, types.RelativeTo(oPkg)))
+			return fmt.Sprintf("%s%s", g.pkgName(oPkg), types.TypeString(typ, types.RelativeTo(oPkg)))
 		default:
 			g.errorf("unsupported named type %s / %T", t, t)
 		}
@@ -476,7 +461,15 @@
 // genPreamble generates the preamble. It is generated after everything
 // else, where we know which bound packages to import.
 func (g *goGen) genPreamble() {
-	g.Printf(goPreamble, g.pkg.Name(), g.pkg.Path())
+	pkgName := ""
+	pkgPath := ""
+	if g.pkg != nil {
+		pkgName = g.pkg.Name()
+		pkgPath = g.pkg.Path()
+	} else {
+		pkgName = "universe"
+	}
+	g.Printf(goPreamble, pkgName, pkgPath)
 	g.Printf("import (\n")
 	g.Indent()
 	g.Printf("_seq \"golang.org/x/mobile/bind/seq\"\n")
@@ -524,6 +517,10 @@
 // pkgName retuns the package name and adds the package to the list of
 // imports.
 func (g *goGen) pkgName(pkg *types.Package) string {
+	// The error type has no package
+	if pkg == nil {
+		return ""
+	}
 	g.imports[pkg.Path()] = struct{}{}
-	return pkg.Name()
+	return pkg.Name() + "."
 }
diff --git a/bind/genjava.go b/bind/genjava.go
index 0364b58..4099260 100644
--- a/bind/genjava.go
+++ b/bind/genjava.go
@@ -199,12 +199,6 @@
 
 // jniType returns a string that can be used as a JNI type.
 func (g *javaGen) jniType(T types.Type) string {
-	if isErrorType(T) {
-		// The error type is usually translated into an exception in
-		// Java, however the type can be exposed in other ways, such
-		// as an exported field.
-		return g.jniType(types.Typ[types.String])
-	}
 	switch T := T.(type) {
 	case *types.Basic:
 		switch T.Kind() {
@@ -305,7 +299,7 @@
 	case *types.Named:
 		n := T.Obj()
 		nPkg := n.Pkg()
-		if !g.validPkg(nPkg) {
+		if !isErrorType(T) && !g.validPkg(nPkg) {
 			g.errorf("type %s is in %s, which is not bound", n.Name(), nPkg)
 			break
 		}
@@ -442,10 +436,6 @@
 }
 
 func (g *javaGen) genJavaToC(varName string, t types.Type, mode varMode) {
-	if isErrorType(t) {
-		g.genJavaToC(varName, types.Typ[types.String], mode)
-		return
-	}
 	switch t := t.(type) {
 	case *types.Basic:
 		switch t.Kind() {
@@ -481,10 +471,6 @@
 }
 
 func (g *javaGen) genCToJava(toName, fromName string, t types.Type, mode varMode) {
-	if isErrorType(t) {
-		g.genCToJava(toName, fromName, types.Typ[types.String], mode)
-		return
-	}
 	switch t := t.(type) {
 	case *types.Basic:
 		switch t.Kind() {
@@ -530,7 +516,7 @@
 
 func (g *javaGen) genFromRefnum(toName, fromName string, t types.Type, o *types.TypeName) {
 	oPkg := o.Pkg()
-	if !g.validPkg(oPkg) {
+	if !isErrorType(o.Type()) && !g.validPkg(oPkg) {
 		g.errorf("type %s is defined in package %s, which is not bound", t, oPkg)
 		return
 	}
@@ -555,6 +541,9 @@
 	if g.javaPkg != "" {
 		return g.javaPkg
 	}
+	if pkg == nil {
+		return "go"
+	}
 	s := javaNameReplacer.Replace(pkg.Name())
 	// Look for Java keywords that are not Go keywords, and avoid using
 	// them as a package name.
@@ -581,6 +570,9 @@
 }
 
 func className(pkg *types.Package) string {
+	if pkg == nil {
+		return "Universe"
+	}
 	return strings.Title(javaNameReplacer.Replace(pkg.Name()))
 }
 
@@ -741,10 +733,6 @@
 
 // genRelease cleans up arguments that weren't copied in genJavaToC.
 func (g *javaGen) genRelease(varName string, t types.Type, mode varMode) {
-	if isErrorType(t) {
-		g.genRelease(varName, types.Typ[types.String], mode)
-		return
-	}
 	switch t := t.(type) {
 	case *types.Basic:
 	case *types.Slice:
@@ -801,9 +789,9 @@
 			rets = append(rets, retName)
 		}
 		if res.Len() == 2 || isErrorType(t) {
-			g.Printf("jstring exc = go_seq_get_exception_message(env);\n")
-			st := types.Typ[types.String]
-			g.genJavaToC("exc", st, modeRetained)
+			g.Printf("jobject exc = go_seq_wrap_exception(env);\n")
+			errType := types.Universe.Lookup("error").Type()
+			g.genJavaToC("exc", errType, modeRetained)
 			retName = "_exc"
 			rets = append(rets, "_exc")
 		}
@@ -824,7 +812,11 @@
 }
 
 func (g *javaGen) genH() error {
-	g.Printf(hPreamble, g.gobindOpts(), g.pkg.Path(), g.className())
+	pkgPath := ""
+	if g.pkg != nil {
+		pkgPath = g.pkg.Path()
+	}
+	g.Printf(hPreamble, g.gobindOpts(), pkgPath, g.className())
 	for _, iface := range g.interfaces {
 		g.Printf("extern jclass proxy_class_%s_%s;\n", g.pkgPrefix, iface.obj.Name())
 		g.Printf("extern jmethodID proxy_class_%s_%s_cons;\n", g.pkgPrefix, iface.obj.Name())
@@ -850,9 +842,6 @@
 }
 
 func (g *javaGen) jniCallType(t types.Type) string {
-	if isErrorType(t) {
-		return g.jniCallType(types.Typ[types.String])
-	}
 	switch t := t.(type) {
 	case *types.Basic:
 		switch t.Kind() {
@@ -897,9 +886,6 @@
 }
 
 func (g *javaGen) jniSigType(T types.Type) string {
-	if isErrorType(T) {
-		return g.jniSigType(types.Typ[types.String])
-	}
 	switch T := T.(type) {
 	case *types.Basic:
 		switch T.Kind() {
@@ -943,11 +929,20 @@
 }
 
 func (g *javaGen) genC() error {
-	g.Printf(cPreamble, g.gobindOpts(), g.pkg.Path(), g.pkg.Name())
-	g.Printf("#include %q\n", g.pkg.Name()+".h")
-	for _, pkg := range g.pkg.Imports() {
-		if g.validPkg(pkg) {
-			g.Printf("#include \"%s.h\"\n", pkg.Name())
+	var pkgName, pkgPath string
+	if g.pkg != nil {
+		pkgName = g.pkg.Name()
+		pkgPath = g.pkg.Path()
+	} else {
+		pkgName = "universe"
+	}
+	g.Printf(cPreamble, g.gobindOpts(), pkgPath)
+	g.Printf("#include %q\n", pkgName+".h")
+	if g.pkg != nil {
+		for _, pkg := range g.pkg.Imports() {
+			if g.validPkg(pkg) {
+				g.Printf("#include \"%s.h\"\n", pkg.Name())
+			}
 		}
 	}
 	g.Printf("\n")
@@ -1035,16 +1030,22 @@
 }
 
 func (g *javaGen) genJava() error {
-	g.Printf(javaPreamble, g.javaPkgName(g.pkg), g.className(), g.gobindOpts(), g.pkg.Path())
+	pkgPath := ""
+	if g.pkg != nil {
+		pkgPath = g.pkg.Path()
+	}
+	g.Printf(javaPreamble, g.javaPkgName(g.pkg), g.className(), g.gobindOpts(), pkgPath)
 
 	g.Printf("public abstract class %s {\n", g.className())
 	g.Indent()
 	g.Printf("static {\n")
 	g.Indent()
 	g.Printf("Seq.touch(); // for loading the native library\n")
-	for _, p := range g.pkg.Imports() {
-		if g.validPkg(p) {
-			g.Printf("%s.%s.touch();\n", g.javaPkgName(p), className(p))
+	if g.pkg != nil {
+		for _, p := range g.pkg.Imports() {
+			if g.validPkg(p) {
+				g.Printf("%s.%s.touch();\n", g.javaPkgName(p), className(p))
+			}
 		}
 	}
 	g.Printf("init();\n")
diff --git a/bind/genobjc.go b/bind/genobjc.go
index 7e7a8b3..219d5e0 100644
--- a/bind/genobjc.go
+++ b/bind/genobjc.go
@@ -47,6 +47,9 @@
 }
 
 func (g *objcGen) namePrefixOf(pkg *types.Package) string {
+	if pkg == nil {
+		return "GoUniverse"
+	}
 	p := g.prefix
 	if p == "" {
 		p = "Go"
@@ -55,7 +58,11 @@
 }
 
 func (g *objcGen) genGoH() error {
-	g.Printf(objcPreamble, g.pkg.Path(), g.gobindOpts(), g.pkg.Path())
+	var pkgPath string
+	if g.pkg != nil {
+		pkgPath = g.pkg.Path()
+	}
+	g.Printf(objcPreamble, pkgPath, g.gobindOpts(), pkgPath)
 	g.Printf("#ifndef __%s_H__\n", g.pkgName)
 	g.Printf("#define __%s_H__\n\n", g.pkgName)
 	g.Printf("#include <stdint.h>\n")
@@ -84,14 +91,21 @@
 }
 
 func (g *objcGen) genH() error {
-	g.Printf(objcPreamble, g.pkg.Path(), g.gobindOpts(), g.pkg.Path())
+	var pkgPath string
+	if g.pkg != nil {
+		pkgPath = g.pkg.Path()
+	}
+	g.Printf(objcPreamble, pkgPath, g.gobindOpts(), pkgPath)
 	g.Printf("#ifndef __%s_H__\n", g.namePrefix)
 	g.Printf("#define __%s_H__\n", g.namePrefix)
 	g.Printf("\n")
 	g.Printf("#include <Foundation/Foundation.h>\n")
-	for _, pkg := range g.pkg.Imports() {
-		if g.validPkg(pkg) {
-			g.Printf("#include %q\n", g.namePrefixOf(pkg)+".h")
+	g.Printf("#include \"GoUniverse.h\"\n")
+	if g.pkg != nil {
+		for _, pkg := range g.pkg.Imports() {
+			if g.validPkg(pkg) {
+				g.Printf("#include %q\n", g.namePrefixOf(pkg)+".h")
+			}
 		}
 	}
 	g.Printf("\n")
@@ -191,13 +205,21 @@
 }
 
 func (g *objcGen) genM() error {
-	g.Printf(objcPreamble, g.pkg.Path(), g.gobindOpts(), g.pkg.Path())
+	var pkgPath string
+	var errDomain string
+	if g.pkg != nil {
+		pkgPath = g.pkg.Path()
+		errDomain = "go." + pkgPath
+	} else {
+		errDomain = "go"
+	}
+	g.Printf(objcPreamble, pkgPath, g.gobindOpts(), pkgPath)
 	g.Printf("#include <Foundation/Foundation.h>\n")
 	g.Printf("#include \"seq.h\"\n")
 	g.Printf("#include \"_cgo_export.h\"\n")
 	g.Printf("#include %q\n", g.namePrefix+".h")
 	g.Printf("\n")
-	g.Printf("static NSString* errDomain = @\"go.%s\";\n", g.pkg.Path())
+	g.Printf("static NSString* errDomain = @%q;\n", errDomain)
 	g.Printf("\n")
 
 	// struct
@@ -500,9 +522,6 @@
 
 func (g *objcGen) genGetter(oName string, f *types.Var) {
 	t := f.Type()
-	if isErrorType(t) {
-		t = types.Typ[types.String]
-	}
 	g.Printf("- (%s)%s {\n", g.objcType(t), lowerFirst(f.Name()))
 	g.Indent()
 	g.Printf("int32_t refnum = go_seq_go_to_refnum(self._ref);\n")
@@ -516,9 +535,6 @@
 
 func (g *objcGen) genSetter(oName string, f *types.Var) {
 	t := f.Type()
-	if isErrorType(t) {
-		t = types.Typ[types.String]
-	}
 
 	g.Printf("- (void)set%s:(%s)v {\n", f.Name(), g.objcType(t))
 	g.Indent()
@@ -531,10 +547,6 @@
 }
 
 func (g *objcGen) genWrite(varName string, t types.Type, mode varMode) {
-	if isErrorType(t) {
-		g.genWrite(varName, types.Typ[types.String], mode)
-		return
-	}
 	switch t := t.(type) {
 	case *types.Basic:
 		switch t.Kind() {
@@ -584,22 +596,18 @@
 }
 
 func (g *objcGen) genRefRead(toName, fromName string, t types.Type) {
-	ptype := g.objcType(t)
-	g.Printf("%s %s = nil;\n", ptype, toName)
+	ptype := g.refTypeBase(t)
+	g.Printf("%s* %s = nil;\n", ptype, toName)
 	g.Printf("GoSeqRef* %s_ref = go_seq_from_refnum(%s);\n", toName, fromName)
 	g.Printf("if (%s_ref != NULL) {\n", toName)
 	g.Printf("	%s = %s_ref.obj;\n", toName, toName)
 	g.Printf("	if (%s == nil) {\n", toName)
-	g.Printf("		%s = [[%s alloc] initWithRef:%s_ref];\n", toName, g.refTypeBase(t), toName)
+	g.Printf("		%s = [[%s alloc] initWithRef:%s_ref];\n", toName, ptype, toName)
 	g.Printf("	}\n")
 	g.Printf("}\n")
 }
 
 func (g *objcGen) genRead(toName, fromName string, t types.Type, mode varMode) {
-	if isErrorType(t) {
-		g.genRead(toName, fromName, types.Typ[types.String], mode)
-		return
-	}
 	switch t := t.(type) {
 	case *types.Basic:
 		switch t.Kind() {
@@ -679,11 +687,9 @@
 	if !s.returnsVal() {
 		for _, p := range s.retParams {
 			if isErrorType(p.typ) {
-				g.Printf("if ([_%s length] != 0 && %s != nil) {\n", p.name, p.name)
+				g.Printf("if (_%s != nil && %s != nil) {\n", p.name, p.name)
 				g.Indent()
-				g.Printf("NSMutableDictionary* details = [NSMutableDictionary dictionary];\n")
-				g.Printf("[details setValue:_%s forKey:NSLocalizedDescriptionKey];\n", p.name)
-				g.Printf("*%s = [NSError errorWithDomain:errDomain code:1 userInfo:details];\n", p.name)
+				g.Printf("*%s = go_seq_to_nserror(_%s, errDomain);\n", p.name, p.name)
 				g.Outdent()
 				g.Printf("}\n")
 			} else {
@@ -695,7 +701,7 @@
 	if n := len(s.retParams); n > 0 {
 		p := s.retParams[n-1]
 		if isErrorType(p.typ) {
-			g.Printf("return ([_%s length] == 0);\n", p.name)
+			g.Printf("return (_%s == nil);\n", p.name)
 		} else {
 			g.Printf("return _%s;\n", p.name)
 		}
@@ -781,7 +787,7 @@
 	g.Indent()
 	g.Printf("@autoreleasepool {\n")
 	g.Indent()
-	g.Printf("%s o = go_seq_objc_from_refnum(refnum);\n", g.objcType(obj.Type()))
+	g.Printf("%s* o = go_seq_objc_from_refnum(refnum);\n", g.refTypeBase(obj.Type()))
 	for _, p := range s.params {
 		g.genRead("_"+p.name, p.name, p.typ, modeTransient)
 	}
@@ -812,23 +818,18 @@
 			var rets []string
 			for i, p := range s.retParams {
 				if isErrorType(p.typ) {
-					g.Printf("NSString *%s_str = nil;\n", p.name)
+					g.Printf("GoUniverseerror* _%s = nil;\n", p.name)
 					if i == len(s.retParams)-1 { // last param.
 						g.Printf("if (!returnVal) {\n")
 					} else {
 						g.Printf("if (%s != nil) {\n", p.name)
 					}
 					g.Indent()
-					g.Printf("%[1]s_str = [%[1]s localizedDescription];\n", p.name)
-					g.Printf("if (%[1]s_str == nil || %[1]s_str.length == 0) {\n", p.name)
-					g.Indent()
-					g.Printf("%[1]s_str = @\"gobind: unknown error\";\n", p.name)
+					g.Printf("_%[1]s = [[goSeqErrorWrapper alloc] initWithError:%[1]s];\n", p.name)
 					g.Outdent()
 					g.Printf("}\n")
-					g.Outdent()
-					g.Printf("}\n")
-					g.genWrite(p.name+"_str", p.typ, modeRetained)
-					rets = append(rets, fmt.Sprintf("_%s_str", p.name))
+					g.genWrite("_"+p.name, p.typ, modeRetained)
+					rets = append(rets, "__"+p.name)
 				} else {
 					g.genWrite(p.name, p.typ, modeRetained)
 					rets = append(rets, "_"+p.name)
@@ -852,10 +853,6 @@
 
 // genRelease cleans up arguments that weren't copied in genWrite.
 func (g *objcGen) genRelease(varName string, t types.Type, mode varMode) {
-	if isErrorType(t) {
-		g.genRelease(varName, types.Typ[types.String], mode)
-		return
-	}
 	switch t := t.(type) {
 	case *types.Slice:
 		switch e := t.Elem().(type) {
@@ -955,7 +952,7 @@
 		}
 	case *types.Named:
 		n := typ.Obj()
-		if g.validPkg(n.Pkg()) {
+		if isErrorType(typ) || g.validPkg(n.Pkg()) {
 			switch typ.Underlying().(type) {
 			case *types.Interface, *types.Struct:
 				return g.namePrefixOf(n.Pkg()) + n.Name()
@@ -1031,7 +1028,7 @@
 		return "TODO"
 	case *types.Named:
 		n := typ.Obj()
-		if !g.validPkg(n.Pkg()) {
+		if !isErrorType(typ) && !g.validPkg(n.Pkg()) {
 			g.errorf("type %s is in package %s, which is not bound", n.Name(), n.Pkg().Name())
 			return "TODO"
 		}
diff --git a/bind/java/Seq.java b/bind/java/Seq.java
index fa2456f..564dd08 100644
--- a/bind/java/Seq.java
+++ b/bind/java/Seq.java
@@ -8,6 +8,8 @@
 import java.util.IdentityHashMap;
 import java.util.logging.Logger;
 
+import go.Universe;
+
 // Seq is a sequence of machine-dependent encoded values.
 // Used by automatically generated language bindings to talk to Go.
 public class Seq {
@@ -34,6 +36,7 @@
 			log.severe("LoadJNI class bad field: " + e);
 		}
 		init();
+		Universe.touch();
 	}
 
 	private static native void init();
@@ -44,8 +47,16 @@
 	private Seq() {
 	}
 
-	private static void throwException(String msg) throws Exception { // JNI helper
-		throw new Exception(msg);
+	private static void throwException(Universe.error err) throws Exception {
+		throw new Exception(err.Error());
+	}
+
+	private static Universe.error wrapThrowable(final Throwable t) {
+		return new Universe.error() {
+			@Override public String Error() {
+				return t.getMessage();
+			}
+		};
 	}
 
 	// ctx is an android.context.Context.
diff --git a/bind/java/SeqTest.java b/bind/java/SeqTest.java
index 30763f7..bab54cf 100644
--- a/bind/java/SeqTest.java
+++ b/bind/java/SeqTest.java
@@ -518,4 +518,22 @@
     };
     assertTrue("Go struct passed through Java should not be wrapped", Testpkg.CallCDupper(cdup));
   }
+
+  public void testEmptyError() {
+    try {
+      Testpkg.EmptyError();
+      fail("Empty error wasn't caught");
+    } catch (Exception e) {
+    }
+    Testpkg.EmptyErrorer empty = new Testpkg.EmptyErrorer() {
+      @Override public void EmptyError() throws Exception {
+        throw new Exception("");
+      }
+    };
+    try {
+      Testpkg.CallEmptyError(empty);
+      fail("Empty exception wasn't caught");
+    } catch (Exception e) {
+    }
+  }
 }
diff --git a/bind/java/seq.h b/bind/java/seq.h
index 1d9a798..0b336cd 100644
--- a/bind/java/seq.h
+++ b/bind/java/seq.h
@@ -38,7 +38,9 @@
 extern jobject go_seq_from_refnum(JNIEnv *env, int32_t refnum, jclass proxy_class, jmethodID proxy_cons);
 
 extern void go_seq_maybe_throw_exception(JNIEnv *env, jobject msg);
-extern jstring go_seq_get_exception_message(JNIEnv *env);
+// go_seq_wrap_exception wraps a pending exception in a Java object implementing the
+// golang/x/mobile/bind/errors.Error interface. If there is no pending exception, it returns NULL.
+extern jobject go_seq_wrap_exception(JNIEnv *env);
 
 extern jbyteArray go_seq_to_java_bytearray(JNIEnv *env, nbyteslice s, int copy);
 extern nbyteslice go_seq_from_java_bytearray(JNIEnv *env, jbyteArray s, int copy);
diff --git a/bind/java/seq_android.c.support b/bind/java/seq_android.c.support
index d02d732..b491fa5 100644
--- a/bind/java/seq_android.c.support
+++ b/bind/java/seq_android.c.support
@@ -30,6 +30,7 @@
 static jmethodID seq_decRef;
 static jmethodID seq_incRef;
 static jmethodID seq_incRefnum;
+static jmethodID seq_wrapThrowable;
 
 static jmethodID throwable_getMessage;
 
@@ -59,18 +60,18 @@
 }
 
 void go_seq_maybe_throw_exception(JNIEnv *env, jobject msg) {
-	if (msg != NULL && (*env)->GetStringLength(env, msg) > 0) {
+	if (msg != NULL) {
 		(*env)->CallStaticVoidMethod(env, seq_class, seq_throw_exc, msg);
 	}
 }
 
-jstring go_seq_get_exception_message(JNIEnv *env) {
+jobject go_seq_wrap_exception(JNIEnv *env) {
 	jthrowable exc = (*env)->ExceptionOccurred(env);
 	if (!exc) {
 		return NULL;
 	}
 	(*env)->ExceptionClear(env);
-	return (*env)->CallObjectMethod(env, exc, throwable_getMessage);
+	return (*env)->CallStaticObjectMethod(env, seq_class, seq_wrapThrowable, exc);
 }
 
 jbyteArray go_seq_to_java_bytearray(JNIEnv *env, nbyteslice s, int copy) {
@@ -290,7 +291,7 @@
 	}
 
 	seq_class = (*env)->NewGlobalRef(env, clazz);
-	seq_throw_exc = (*env)->GetStaticMethodID(env, seq_class, "throwException", "(Ljava/lang/String;)V");
+	seq_throw_exc = (*env)->GetStaticMethodID(env, seq_class, "throwException", "(Lgo/Universe$error;)V");
 	if (seq_throw_exc == NULL) {
 		LOG_FATAL("failed to find method Seq.throwException");
 	}
@@ -311,6 +312,10 @@
 	if (seq_incRef == NULL) {
 		LOG_FATAL("failed to find method Seq.incRef");
 	}
+	seq_wrapThrowable = (*env)->GetStaticMethodID(env, seq_class, "wrapThrowable", "(Ljava/lang/Throwable;)Lgo/Universe$error;");
+	if (seq_wrapThrowable == NULL) {
+		LOG_FATAL("failed to find method Seq.wrapThrowable");
+	}
 	jclass throwable_class = (*env)->FindClass(env, "java/lang/Throwable");
 	if (throwable_class == NULL) {
 		LOG_FATAL("failed to find Throwable class");
diff --git a/bind/objc/SeqTest.m b/bind/objc/SeqTest.m
index c36d650..7e44a97 100644
--- a/bind/objc/SeqTest.m
+++ b/bind/objc/SeqTest.m
@@ -102,6 +102,21 @@
 }
 @end
 
+// Objective-C implementation of testpkg.EmptyThrower.
+@interface EmptyErrorer: NSObject <GoTestpkgEmptyErrorer> {
+}
+
+@end
+
+@implementation EmptyErrorer {
+}
+
+- (BOOL)emptyError:(NSError **)error {
+    *error = [NSError errorWithDomain:@"SeqTest" code:1 userInfo:NULL];
+    return NO;
+}
+@end
+
 @interface tests : XCTestCase
 
 @end
@@ -406,4 +421,12 @@
 	XCTAssertTrue(GoTestpkgCallCDupper(cdup), @"Go struct passed through ObjC should not be wrapped");
 }
 
+- (void)testEmptyError {
+    NSError *error;
+    XCTAssertFalse(GoTestpkgEmptyError(&error), @"GoTestpkgEmptyError succeeded; want error");
+    XCTAssertNotNil(error, @"GoTestpkgEmptyError returned nil error");
+    id<GoTestpkgEmptyErrorer> empty = [[EmptyErrorer alloc] init];
+    XCTAssertFalse(GoTestpkgCallEmptyError(empty, &error), @"GoTestpkgCallEmptyError succeeded; want error");
+    XCTAssertNotNil(error, @"GoTestpkgCallEmptyError returned nil error");
+}
 @end
diff --git a/bind/objc/seq.h b/bind/objc/seq.h
index 1d44f8e..8941760 100644
--- a/bind/objc/seq.h
+++ b/bind/objc/seq.h
@@ -6,6 +6,7 @@
 #define __GO_SEQ_HDR__
 
 #include <Foundation/Foundation.h>
+#include "GoUniverse.h"
 
 #ifdef DEBUG
 #define LOG_DEBUG(...) NSLog(__VA_ARGS__);
@@ -23,6 +24,14 @@
                               userInfo:NULL];                                  \
   }
 
+// goErrorWrapper is a adapter between an NSError * and the bind Error interface
+@interface goSeqErrorWrapper : NSObject<GoUniverseerror> {
+}
+@property NSError *err;
+
+- (id)initWithError:(NSError *)err;
+@end
+
 // GoSeqRef is an object tagged with an integer for passing back and
 // forth across the language boundary. A GoSeqRef may represent either
 // an instance of a Go object, or an Objective-C object passed to Go.
@@ -83,5 +92,6 @@
 
 extern NSData *go_seq_to_objc_bytearray(nbyteslice, int copy);
 extern NSString *go_seq_to_objc_string(nstring str);
+extern NSError *go_seq_to_nserror(id<GoUniverseerror> err, NSString *errDomain);
 
 #endif // __GO_SEQ_HDR__
diff --git a/bind/objc/seq_darwin.m.support b/bind/objc/seq_darwin.m.support
index 917a4ef..933a35f 100644
--- a/bind/objc/seq_darwin.m.support
+++ b/bind/objc/seq_darwin.m.support
@@ -49,6 +49,22 @@
 // Note that this file is copied into and compiled with the generated
 // bindings.
 
+@implementation goSeqErrorWrapper {
+}
+@synthesize err;
+
+- (id)initWithError:(NSError *)e {
+  if (!(self = [super init]))
+    return nil;
+  self.err = e;
+  return self;
+}
+
+- (NSString *)error {
+  return [self.err localizedDescription];
+}
+@end
+
 // A simple thread-safe mutable dictionary.
 @interface goSeqDictionary : NSObject {
 }
@@ -234,6 +250,13 @@
   return res;
 }
 
+NSError *go_seq_to_nserror(id<GoUniverseerror> err, NSString *errDomain) {
+	NSString *errStr = [err error];
+	NSMutableDictionary* details = [NSMutableDictionary dictionary];
+	[details setValue:errStr forKey:NSLocalizedDescriptionKey];
+	return [NSError errorWithDomain:errDomain code:1 userInfo:details];
+}
+
 @implementation GoSeqRef {
 }
 
diff --git a/bind/seq.go.support b/bind/seq.go.support
index 2c98085..d2f36d4 100644
--- a/bind/seq.go.support
+++ b/bind/seq.go.support
@@ -13,19 +13,11 @@
 import "C"
 
 import (
-	"errors"
 	"fmt"
 
 	_seq "golang.org/x/mobile/bind/seq"
 )
 
-func toError(s string) error {
-	if s == "" {
-		return nil
-	}
-	return errors.New(s)
-}
-
 func init() {
 	_seq.FinalizeRef = func(ref *_seq.Ref) {
 		refnum := ref.Bind_Num
diff --git a/bind/testdata/basictypes.go.golden b/bind/testdata/basictypes.go.golden
index a3eb955..128aade 100644
--- a/bind/testdata/basictypes.go.golden
+++ b/bind/testdata/basictypes.go.golden
@@ -41,29 +41,23 @@
 }
 
 //export proxybasictypes__Error
-func proxybasictypes__Error() C.nstring {
+func proxybasictypes__Error() C.int32_t {
 	res_0 := basictypes.Error()
-	var _res_0_str string
-	if res_0 == nil {
-		_res_0_str = ""
-	} else {
-		_res_0_str = res_0.Error()
+	var _res_0 C.int32_t = _seq.NullRefNum
+	if res_0 != nil {
+		_res_0 = C.int32_t(_seq.ToRefNum(res_0))
 	}
-	_res_0 := encodeString(_res_0_str)
 	return _res_0
 }
 
 //export proxybasictypes__ErrorPair
-func proxybasictypes__ErrorPair() (C.nint, C.nstring) {
+func proxybasictypes__ErrorPair() (C.nint, C.int32_t) {
 	res_0, res_1 := basictypes.ErrorPair()
 	_res_0 := C.nint(res_0)
-	var _res_1_str string
-	if res_1 == nil {
-		_res_1_str = ""
-	} else {
-		_res_1_str = res_1.Error()
+	var _res_1 C.int32_t = _seq.NullRefNum
+	if res_1 != nil {
+		_res_1 = C.int32_t(_seq.ToRefNum(res_1))
 	}
-	_res_1 := encodeString(_res_1_str)
 	return _res_0, _res_1
 }
 
diff --git a/bind/testdata/basictypes.java.c.golden b/bind/testdata/basictypes.java.c.golden
index caa1d1e..2688464 100644
--- a/bind/testdata/basictypes.java.c.golden
+++ b/bind/testdata/basictypes.java.c.golden
@@ -36,8 +36,8 @@
 
 JNIEXPORT void JNICALL
 Java_go_basictypes_Basictypes_Error(JNIEnv* env, jclass clazz) {
-    nstring r0 = proxybasictypes__Error();
-    jstring _r0 = go_seq_to_java_string(env, r0);
+    int32_t r0 = proxybasictypes__Error();
+    jobject _r0 = go_seq_from_refnum(env, r0, proxy_class__error, proxy_class__error_cons);
     go_seq_maybe_throw_exception(env, _r0);
 }
 
@@ -45,7 +45,7 @@
 Java_go_basictypes_Basictypes_ErrorPair(JNIEnv* env, jclass clazz) {
     struct proxybasictypes__ErrorPair_return res = proxybasictypes__ErrorPair();
     jlong _r0 = (jlong)res.r0;
-    jstring _r1 = go_seq_to_java_string(env, res.r1);
+    jobject _r1 = go_seq_from_refnum(env, res.r1, proxy_class__error, proxy_class__error_cons);
     go_seq_maybe_throw_exception(env, _r1);
     return _r0;
 }
diff --git a/bind/testdata/basictypes.objc.h.golden b/bind/testdata/basictypes.objc.h.golden
index 707e6ce..637abc6 100644
--- a/bind/testdata/basictypes.objc.h.golden
+++ b/bind/testdata/basictypes.objc.h.golden
@@ -7,6 +7,7 @@
 #define __GoBasictypes_H__
 
 #include <Foundation/Foundation.h>
+#include "GoUniverse.h"
 
 FOUNDATION_EXPORT const BOOL GoBasictypesABool;
 FOUNDATION_EXPORT const double GoBasictypesAFloat;
diff --git a/bind/testdata/basictypes.objc.m.golden b/bind/testdata/basictypes.objc.m.golden
index c3ffba6..b57aabd 100644
--- a/bind/testdata/basictypes.objc.m.golden
+++ b/bind/testdata/basictypes.objc.m.golden
@@ -37,27 +37,37 @@
 }
 
 BOOL GoBasictypesError(NSError** error) {
-	nstring r0 = proxybasictypes__Error();
-	NSString *_error = go_seq_to_objc_string(r0);
-	if ([_error length] != 0 && error != nil) {
-		NSMutableDictionary* details = [NSMutableDictionary dictionary];
-		[details setValue:_error forKey:NSLocalizedDescriptionKey];
-		*error = [NSError errorWithDomain:errDomain code:1 userInfo:details];
+	int32_t r0 = proxybasictypes__Error();
+	GoUniverseerror* _error = nil;
+	GoSeqRef* _error_ref = go_seq_from_refnum(r0);
+	if (_error_ref != NULL) {
+		_error = _error_ref.obj;
+		if (_error == nil) {
+			_error = [[GoUniverseerror alloc] initWithRef:_error_ref];
+		}
 	}
-	return ([_error length] == 0);
+	if (_error != nil && error != nil) {
+		*error = go_seq_to_nserror(_error, errDomain);
+	}
+	return (_error == nil);
 }
 
 BOOL GoBasictypesErrorPair(int* ret0_, NSError** error) {
 	struct proxybasictypes__ErrorPair_return res = proxybasictypes__ErrorPair();
 	int _ret0_ = (int)res.r0;
-	NSString *_error = go_seq_to_objc_string(res.r1);
-	*ret0_ = _ret0_;
-	if ([_error length] != 0 && error != nil) {
-		NSMutableDictionary* details = [NSMutableDictionary dictionary];
-		[details setValue:_error forKey:NSLocalizedDescriptionKey];
-		*error = [NSError errorWithDomain:errDomain code:1 userInfo:details];
+	GoUniverseerror* _error = nil;
+	GoSeqRef* _error_ref = go_seq_from_refnum(res.r1);
+	if (_error_ref != NULL) {
+		_error = _error_ref.obj;
+		if (_error == nil) {
+			_error = [[GoUniverseerror alloc] initWithRef:_error_ref];
+		}
 	}
-	return ([_error length] == 0);
+	*ret0_ = _ret0_;
+	if (_error != nil && error != nil) {
+		*error = go_seq_to_nserror(_error, errDomain);
+	}
+	return (_error == nil);
 }
 
 void GoBasictypesInts(int8_t x, int16_t y, int32_t z, int64_t t, int u) {
diff --git a/bind/testdata/customprefix.objc.h.golden b/bind/testdata/customprefix.objc.h.golden
index 5c5164a..6dc2598 100644
--- a/bind/testdata/customprefix.objc.h.golden
+++ b/bind/testdata/customprefix.objc.h.golden
@@ -7,6 +7,7 @@
 #define __EXCustomprefix_H__
 
 #include <Foundation/Foundation.h>
+#include "GoUniverse.h"
 
 FOUNDATION_EXPORT void EXCustomprefixF();
 
diff --git a/bind/testdata/ignore.objc.h.golden b/bind/testdata/ignore.objc.h.golden
index 56d86d7..3515ac5 100644
--- a/bind/testdata/ignore.objc.h.golden
+++ b/bind/testdata/ignore.objc.h.golden
@@ -7,6 +7,7 @@
 #define __GoIgnore_H__
 
 #include <Foundation/Foundation.h>
+#include "GoUniverse.h"
 
 @class GoIgnoreS;
 @protocol GoIgnoreI;
diff --git a/bind/testdata/interfaces.go.golden b/bind/testdata/interfaces.go.golden
index a614c53..16baa81 100644
--- a/bind/testdata/interfaces.go.golden
+++ b/bind/testdata/interfaces.go.golden
@@ -22,17 +22,14 @@
 var _ = _seq.FromRefNum
 
 //export proxyinterfaces_Error_Err
-func proxyinterfaces_Error_Err(refnum C.int32_t) C.nstring {
+func proxyinterfaces_Error_Err(refnum C.int32_t) C.int32_t {
 	ref := _seq.FromRefNum(int32(refnum))
 	v := ref.Get().(interfaces.Error)
 	res_0 := v.Err()
-	var _res_0_str string
-	if res_0 == nil {
-		_res_0_str = ""
-	} else {
-		_res_0_str = res_0.Error()
+	var _res_0 C.int32_t = _seq.NullRefNum
+	if res_0 != nil {
+		_res_0 = C.int32_t(_seq.ToRefNum(res_0))
 	}
-	_res_0 := encodeString(_res_0_str)
 	return _res_0
 }
 
@@ -42,8 +39,15 @@
 
 func (p *proxyinterfaces_Error) Err() error {
 	res := C.cproxyinterfaces_Error_Err(C.int32_t(p.Bind_proxy_refnum__()))
-	_res_str := decodeString(res)
-	_res := toError(_res_str)
+	var _res error
+	_res_ref := _seq.FromRefNum(int32(res))
+	if _res_ref != nil {
+		if res < 0 { // go object
+			_res = _res_ref.Get().(error)
+		} else { // foreign object
+			_res = (*proxy_error)(_res_ref)
+		}
+	}
 	return _res
 }
 
@@ -194,7 +198,7 @@
 }
 
 //export proxyinterfaces__CallErr
-func proxyinterfaces__CallErr(param_e C.int32_t) C.nstring {
+func proxyinterfaces__CallErr(param_e C.int32_t) C.int32_t {
 	var _param_e interfaces.Error
 	_param_e_ref := _seq.FromRefNum(int32(param_e))
 	if _param_e_ref != nil {
@@ -205,13 +209,10 @@
 		}
 	}
 	res_0 := interfaces.CallErr(_param_e)
-	var _res_0_str string
-	if res_0 == nil {
-		_res_0_str = ""
-	} else {
-		_res_0_str = res_0.Error()
+	var _res_0 C.int32_t = _seq.NullRefNum
+	if res_0 != nil {
+		_res_0 = C.int32_t(_seq.ToRefNum(res_0))
 	}
-	_res_0 := encodeString(_res_0_str)
 	return _res_0
 }
 
diff --git a/bind/testdata/interfaces.java.c.golden b/bind/testdata/interfaces.java.c.golden
index 974e1af..f151ded 100644
--- a/bind/testdata/interfaces.java.c.golden
+++ b/bind/testdata/interfaces.java.c.golden
@@ -100,8 +100,8 @@
 JNIEXPORT void JNICALL
 Java_go_interfaces_Interfaces_CallErr(JNIEnv* env, jclass clazz, jobject e) {
     int32_t _e = go_seq_to_refnum(env, e);
-    nstring r0 = proxyinterfaces__CallErr(_e);
-    jstring _r0 = go_seq_to_java_string(env, r0);
+    int32_t r0 = proxyinterfaces__CallErr(_e);
+    jobject _r0 = go_seq_from_refnum(env, r0, proxy_class__error, proxy_class__error_cons);
     go_seq_maybe_throw_exception(env, _r0);
 }
 
@@ -115,17 +115,17 @@
 JNIEXPORT void JNICALL
 Java_go_interfaces_Interfaces_00024proxyError_Err(JNIEnv* env, jobject this) {
     int32_t o = go_seq_to_refnum(env, this);
-    nstring r0 = proxyinterfaces_Error_Err(o);
-    jstring _r0 = go_seq_to_java_string(env, r0);
+    int32_t r0 = proxyinterfaces_Error_Err(o);
+    jobject _r0 = go_seq_from_refnum(env, r0, proxy_class__error, proxy_class__error_cons);
     go_seq_maybe_throw_exception(env, _r0);
 }
 
-nstring cproxyinterfaces_Error_Err(int32_t refnum) {
+int32_t cproxyinterfaces_Error_Err(int32_t refnum) {
     JNIEnv *env = go_seq_push_local_frame(10);
     jobject o = go_seq_from_refnum(env, refnum, proxy_class_interfaces_Error, proxy_class_interfaces_Error_cons);
     (*env)->CallVoidMethod(env, o, mid_Error_Err);
-    jstring exc = go_seq_get_exception_message(env);
-    nstring _exc = go_seq_from_java_string(env, exc);
+    jobject exc = go_seq_wrap_exception(env);
+    int32_t _exc = go_seq_to_refnum(env, exc);
     go_seq_pop_local_frame(env);
     return _exc;
 }
diff --git a/bind/testdata/interfaces.java.h.golden b/bind/testdata/interfaces.java.h.golden
index a511de4..05840e8 100644
--- a/bind/testdata/interfaces.java.h.golden
+++ b/bind/testdata/interfaces.java.h.golden
@@ -11,7 +11,7 @@
 extern jclass proxy_class_interfaces_Error;
 extern jmethodID proxy_class_interfaces_Error_cons;
 
-nstring cproxyinterfaces_Error_Err(int32_t refnum);
+int32_t cproxyinterfaces_Error_Err(int32_t refnum);
 
 extern jclass proxy_class_interfaces_I;
 extern jmethodID proxy_class_interfaces_I_cons;
diff --git a/bind/testdata/interfaces.objc.go.h.golden b/bind/testdata/interfaces.objc.go.h.golden
index 3915b55..8452626 100644
--- a/bind/testdata/interfaces.objc.go.h.golden
+++ b/bind/testdata/interfaces.objc.go.h.golden
@@ -8,7 +8,7 @@
 
 #include <stdint.h>
 #include <objc/objc.h>
-nstring cproxyinterfaces_Error_Err(int32_t refnum);
+int32_t cproxyinterfaces_Error_Err(int32_t refnum);
 
 int32_t cproxyinterfaces_I_Rand(int32_t refnum);
 
diff --git a/bind/testdata/interfaces.objc.h.golden b/bind/testdata/interfaces.objc.h.golden
index 71262da..4464d81 100644
--- a/bind/testdata/interfaces.objc.h.golden
+++ b/bind/testdata/interfaces.objc.h.golden
@@ -7,6 +7,7 @@
 #define __GoInterfaces_H__
 
 #include <Foundation/Foundation.h>
+#include "GoUniverse.h"
 
 @protocol GoInterfacesError;
 @class GoInterfacesError;
diff --git a/bind/testdata/interfaces.objc.m.golden b/bind/testdata/interfaces.objc.m.golden
index 06fcd45..b28f05e 100644
--- a/bind/testdata/interfaces.objc.m.golden
+++ b/bind/testdata/interfaces.objc.m.golden
@@ -21,14 +21,19 @@
 
 - (BOOL)err:(NSError**)error {
 	int32_t refnum = go_seq_go_to_refnum(self._ref);
-	nstring r0 = proxyinterfaces_Error_Err(refnum);
-	NSString *_error = go_seq_to_objc_string(r0);
-	if ([_error length] != 0 && error != nil) {
-		NSMutableDictionary* details = [NSMutableDictionary dictionary];
-		[details setValue:_error forKey:NSLocalizedDescriptionKey];
-		*error = [NSError errorWithDomain:errDomain code:1 userInfo:details];
+	int32_t r0 = proxyinterfaces_Error_Err(refnum);
+	GoUniverseerror* _error = nil;
+	GoSeqRef* _error_ref = go_seq_from_refnum(r0);
+	if (_error_ref != NULL) {
+		_error = _error_ref.obj;
+		if (_error == nil) {
+			_error = [[GoUniverseerror alloc] initWithRef:_error_ref];
+		}
 	}
-	return ([_error length] == 0);
+	if (_error != nil && error != nil) {
+		*error = go_seq_to_nserror(_error, errDomain);
+	}
+	return (_error == nil);
 }
 
 @end
@@ -196,19 +201,24 @@
 	} else {
 		_e = go_seq_to_refnum(e);
 	}
-	nstring r0 = proxyinterfaces__CallErr(_e);
-	NSString *_error = go_seq_to_objc_string(r0);
-	if ([_error length] != 0 && error != nil) {
-		NSMutableDictionary* details = [NSMutableDictionary dictionary];
-		[details setValue:_error forKey:NSLocalizedDescriptionKey];
-		*error = [NSError errorWithDomain:errDomain code:1 userInfo:details];
+	int32_t r0 = proxyinterfaces__CallErr(_e);
+	GoUniverseerror* _error = nil;
+	GoSeqRef* _error_ref = go_seq_from_refnum(r0);
+	if (_error_ref != NULL) {
+		_error = _error_ref.obj;
+		if (_error == nil) {
+			_error = [[GoUniverseerror alloc] initWithRef:_error_ref];
+		}
 	}
-	return ([_error length] == 0);
+	if (_error != nil && error != nil) {
+		*error = go_seq_to_nserror(_error, errDomain);
+	}
+	return (_error == nil);
 }
 
 id<GoInterfacesI> GoInterfacesSeven() {
 	int32_t r0 = proxyinterfaces__Seven();
-	id<GoInterfacesI> _ret0_ = nil;
+	GoInterfacesI* _ret0_ = nil;
 	GoSeqRef* _ret0__ref = go_seq_from_refnum(r0);
 	if (_ret0__ref != NULL) {
 		_ret0_ = _ret0__ref.obj;
@@ -219,26 +229,29 @@
 	return _ret0_;
 }
 
-nstring cproxyinterfaces_Error_Err(int32_t refnum) {
+int32_t cproxyinterfaces_Error_Err(int32_t refnum) {
 	@autoreleasepool {
-		id<GoInterfacesError> o = go_seq_objc_from_refnum(refnum);
+		GoInterfacesError* o = go_seq_objc_from_refnum(refnum);
 		NSError* error = nil;
 		BOOL returnVal = [o err:&error];
-		NSString *error_str = nil;
+		GoUniverseerror* _error = nil;
 		if (!returnVal) {
-			error_str = [error localizedDescription];
-			if (error_str == nil || error_str.length == 0) {
-				error_str = @"gobind: unknown error";
-			}
+			_error = [[goSeqErrorWrapper alloc] initWithError:error];
 		}
-		nstring _error_str = go_seq_from_objc_string(error_str);
-		return _error_str;
+		int32_t __error;
+		if ([(id<NSObject>)(_error) isKindOfClass:[GoUniverseerror class]]) {
+			id<goSeqRefInterface> _error_proxy = (id<goSeqRefInterface>)(_error);
+			__error = go_seq_go_to_refnum(_error_proxy._ref);
+		} else {
+			__error = go_seq_to_refnum(_error);
+		}
+		return __error;
 	}
 }
 
 int32_t cproxyinterfaces_I_Rand(int32_t refnum) {
 	@autoreleasepool {
-		id<GoInterfacesI> o = go_seq_objc_from_refnum(refnum);
+		GoInterfacesI* o = go_seq_objc_from_refnum(refnum);
 		int32_t returnVal = [o rand];
 		int32_t _returnVal = (int32_t)returnVal;
 		return _returnVal;
@@ -261,7 +274,7 @@
 
 int32_t cproxyinterfaces_I3_F(int32_t refnum) {
 	@autoreleasepool {
-		id<GoInterfacesI3> o = go_seq_objc_from_refnum(refnum);
+		GoInterfacesI3* o = go_seq_objc_from_refnum(refnum);
 		GoInterfacesI1* returnVal = [o f];
 		int32_t _returnVal;
 		if ([(id<NSObject>)(returnVal) isKindOfClass:[GoInterfacesI1 class]]) {
@@ -276,14 +289,14 @@
 
 void cproxyinterfaces_LargerI_AnotherFunc(int32_t refnum) {
 	@autoreleasepool {
-		id<GoInterfacesLargerI> o = go_seq_objc_from_refnum(refnum);
+		GoInterfacesLargerI* o = go_seq_objc_from_refnum(refnum);
 		[o anotherFunc];
 	}
 }
 
 int32_t cproxyinterfaces_LargerI_Rand(int32_t refnum) {
 	@autoreleasepool {
-		id<GoInterfacesLargerI> o = go_seq_objc_from_refnum(refnum);
+		GoInterfacesLargerI* o = go_seq_objc_from_refnum(refnum);
 		int32_t returnVal = [o rand];
 		int32_t _returnVal = (int32_t)returnVal;
 		return _returnVal;
@@ -292,7 +305,7 @@
 
 int32_t cproxyinterfaces_SameI_Rand(int32_t refnum) {
 	@autoreleasepool {
-		id<GoInterfacesSameI> o = go_seq_objc_from_refnum(refnum);
+		GoInterfacesSameI* o = go_seq_objc_from_refnum(refnum);
 		int32_t returnVal = [o rand];
 		int32_t _returnVal = (int32_t)returnVal;
 		return _returnVal;
@@ -301,7 +314,7 @@
 
 void cproxyinterfaces_WithParam_HasParam(int32_t refnum, char p0) {
 	@autoreleasepool {
-		id<GoInterfacesWithParam> o = go_seq_objc_from_refnum(refnum);
+		GoInterfacesWithParam* o = go_seq_objc_from_refnum(refnum);
 		BOOL _p0 = p0 ? YES : NO;
 		[o hasParam:_p0];
 	}
diff --git a/bind/testdata/issue10788.objc.h.golden b/bind/testdata/issue10788.objc.h.golden
index c391329..f93df41 100644
--- a/bind/testdata/issue10788.objc.h.golden
+++ b/bind/testdata/issue10788.objc.h.golden
@@ -7,6 +7,7 @@
 #define __GoIssue10788_H__
 
 #include <Foundation/Foundation.h>
+#include "GoUniverse.h"
 
 @class GoIssue10788TestStruct;
 @protocol GoIssue10788TestInterface;
diff --git a/bind/testdata/issue10788.objc.m.golden b/bind/testdata/issue10788.objc.m.golden
index 7dc4200..1253a00 100644
--- a/bind/testdata/issue10788.objc.m.golden
+++ b/bind/testdata/issue10788.objc.m.golden
@@ -70,7 +70,7 @@
 
 void cproxyissue10788_TestInterface_DoSomeWork(int32_t refnum, int32_t s) {
 	@autoreleasepool {
-		id<GoIssue10788TestInterface> o = go_seq_objc_from_refnum(refnum);
+		GoIssue10788TestInterface* o = go_seq_objc_from_refnum(refnum);
 		GoIssue10788TestStruct* _s = nil;
 		GoSeqRef* _s_ref = go_seq_from_refnum(s);
 		if (_s_ref != NULL) {
@@ -85,7 +85,7 @@
 
 void cproxyissue10788_TestInterface_MultipleUnnamedParams(int32_t refnum, nint p0, nstring p1, int64_t p2) {
 	@autoreleasepool {
-		id<GoIssue10788TestInterface> o = go_seq_objc_from_refnum(refnum);
+		GoIssue10788TestInterface* o = go_seq_objc_from_refnum(refnum);
 		int _p0 = (int)p0;
 		NSString *_p1 = go_seq_to_objc_string(p1);
 		int64_t _p2 = (int64_t)p2;
diff --git a/bind/testdata/issue12328.go.golden b/bind/testdata/issue12328.go.golden
index 4a238f7..4dca6cc 100644
--- a/bind/testdata/issue12328.go.golden
+++ b/bind/testdata/issue12328.go.golden
@@ -22,23 +22,27 @@
 var _ = _seq.FromRefNum
 
 //export proxyissue12328_T_Err_Set
-func proxyissue12328_T_Err_Set(refnum C.int32_t, v C.nstring) {
+func proxyissue12328_T_Err_Set(refnum C.int32_t, v C.int32_t) {
 	ref := _seq.FromRefNum(int32(refnum))
-	_v_str := decodeString(v)
-	_v := toError(_v_str)
+	var _v error
+	_v_ref := _seq.FromRefNum(int32(v))
+	if _v_ref != nil {
+		if v < 0 { // go object
+			_v = _v_ref.Get().(error)
+		} else { // foreign object
+			_v = (*proxy_error)(_v_ref)
+		}
+	}
 	ref.Get().(*issue12328.T).Err = _v
 }
 
 //export proxyissue12328_T_Err_Get
-func proxyissue12328_T_Err_Get(refnum C.int32_t) C.nstring {
+func proxyissue12328_T_Err_Get(refnum C.int32_t) C.int32_t {
 	ref := _seq.FromRefNum(int32(refnum))
 	v := ref.Get().(*issue12328.T).Err
-	var _v_str string
-	if v == nil {
-		_v_str = ""
-	} else {
-		_v_str = v.Error()
+	var _v C.int32_t = _seq.NullRefNum
+	if v != nil {
+		_v = C.int32_t(_seq.ToRefNum(v))
 	}
-	_v := encodeString(_v_str)
 	return _v
 }
diff --git a/bind/testdata/issue12328.java.c.golden b/bind/testdata/issue12328.java.c.golden
index 52b110d..9830f6b 100644
--- a/bind/testdata/issue12328.java.c.golden
+++ b/bind/testdata/issue12328.java.c.golden
@@ -21,17 +21,17 @@
 }
 
 JNIEXPORT void JNICALL
-Java_go_issue12328_Issue12328_00024T_setErr(JNIEnv *env, jobject this, jstring v) {
+Java_go_issue12328_Issue12328_00024T_setErr(JNIEnv *env, jobject this, jobject v) {
     int32_t o = go_seq_to_refnum(env, this);
-    nstring _v = go_seq_from_java_string(env, v);
+    int32_t _v = go_seq_to_refnum(env, v);
     proxyissue12328_T_Err_Set(o, _v);
 }
 
-JNIEXPORT jstring JNICALL
+JNIEXPORT jobject JNICALL
 Java_go_issue12328_Issue12328_00024T_getErr(JNIEnv *env, jobject this) {
     int32_t o = go_seq_to_refnum(env, this);
-    nstring r0 = proxyissue12328_T_Err_Get(o);
-    jstring _r0 = go_seq_to_java_string(env, r0);
+    int32_t r0 = proxyissue12328_T_Err_Get(o);
+    jobject _r0 = go_seq_from_refnum(env, r0, proxy_class__error, proxy_class__error_cons);
     return _r0;
 }
 
diff --git a/bind/testdata/issue12328.objc.h.golden b/bind/testdata/issue12328.objc.h.golden
index 381beaa..6ab1b99 100644
--- a/bind/testdata/issue12328.objc.h.golden
+++ b/bind/testdata/issue12328.objc.h.golden
@@ -7,6 +7,7 @@
 #define __GoIssue12328_H__
 
 #include <Foundation/Foundation.h>
+#include "GoUniverse.h"
 
 @class GoIssue12328T;
 
diff --git a/bind/testdata/issue12328.objc.m.golden b/bind/testdata/issue12328.objc.m.golden
index 1dd7e9a..66969f4 100644
--- a/bind/testdata/issue12328.objc.m.golden
+++ b/bind/testdata/issue12328.objc.m.golden
@@ -20,16 +20,29 @@
 	return self;
 }
 
-- (NSString*)err {
+- (NSError*)err {
 	int32_t refnum = go_seq_go_to_refnum(self._ref);
-	nstring r0 = proxyissue12328_T_Err_Get(refnum);
-	NSString *_r0 = go_seq_to_objc_string(r0);
+	int32_t r0 = proxyissue12328_T_Err_Get(refnum);
+	GoUniverseerror* _r0 = nil;
+	GoSeqRef* _r0_ref = go_seq_from_refnum(r0);
+	if (_r0_ref != NULL) {
+		_r0 = _r0_ref.obj;
+		if (_r0 == nil) {
+			_r0 = [[GoUniverseerror alloc] initWithRef:_r0_ref];
+		}
+	}
 	return _r0;
 }
 
-- (void)setErr:(NSString*)v {
+- (void)setErr:(NSError*)v {
 	int32_t refnum = go_seq_go_to_refnum(self._ref);
-	nstring _v = go_seq_from_objc_string(v);
+	int32_t _v;
+	if ([(id<NSObject>)(v) isKindOfClass:[GoUniverseerror class]]) {
+		id<goSeqRefInterface> v_proxy = (id<goSeqRefInterface>)(v);
+		_v = go_seq_go_to_refnum(v_proxy._ref);
+	} else {
+		_v = go_seq_to_refnum(v);
+	}
 	proxyissue12328_T_Err_Set(refnum, _v);
 }
 
diff --git a/bind/testdata/issue12403.go.golden b/bind/testdata/issue12403.go.golden
index 699217a..eb83831 100644
--- a/bind/testdata/issue12403.go.golden
+++ b/bind/testdata/issue12403.go.golden
@@ -32,18 +32,15 @@
 }
 
 //export proxyissue12403_Parsable_ToJSON
-func proxyissue12403_Parsable_ToJSON(refnum C.int32_t) (C.nstring, C.nstring) {
+func proxyissue12403_Parsable_ToJSON(refnum C.int32_t) (C.nstring, C.int32_t) {
 	ref := _seq.FromRefNum(int32(refnum))
 	v := ref.Get().(issue12403.Parsable)
 	res_0, res_1 := v.ToJSON()
 	_res_0 := encodeString(res_0)
-	var _res_1_str string
-	if res_1 == nil {
-		_res_1_str = ""
-	} else {
-		_res_1_str = res_1.Error()
+	var _res_1 C.int32_t = _seq.NullRefNum
+	if res_1 != nil {
+		_res_1 = C.int32_t(_seq.ToRefNum(res_1))
 	}
-	_res_1 := encodeString(_res_1_str)
 	return _res_0, _res_1
 }
 
@@ -61,7 +58,14 @@
 func (p *proxyissue12403_Parsable) ToJSON() (string, error) {
 	res := C.cproxyissue12403_Parsable_ToJSON(C.int32_t(p.Bind_proxy_refnum__()))
 	res_0 := decodeString(res.r0)
-	res_1_str := decodeString(res.r1)
-	res_1 := toError(res_1_str)
+	var res_1 error
+	res_1_ref := _seq.FromRefNum(int32(res.r1))
+	if res_1_ref != nil {
+		if res.r1 < 0 { // go object
+			res_1 = res_1_ref.Get().(error)
+		} else { // foreign object
+			res_1 = (*proxy_error)(res_1_ref)
+		}
+	}
 	return res_0, res_1
 }
diff --git a/bind/testdata/issue12403.java.c.golden b/bind/testdata/issue12403.java.c.golden
index bcd7501..f7323f1 100644
--- a/bind/testdata/issue12403.java.c.golden
+++ b/bind/testdata/issue12403.java.c.golden
@@ -50,7 +50,7 @@
     int32_t o = go_seq_to_refnum(env, this);
     struct proxyissue12403_Parsable_ToJSON_return res = proxyissue12403_Parsable_ToJSON(o);
     jstring _r0 = go_seq_to_java_string(env, res.r0);
-    jstring _r1 = go_seq_to_java_string(env, res.r1);
+    jobject _r1 = go_seq_from_refnum(env, res.r1, proxy_class__error, proxy_class__error_cons);
     go_seq_maybe_throw_exception(env, _r1);
     return _r0;
 }
@@ -60,8 +60,8 @@
     jobject o = go_seq_from_refnum(env, refnum, proxy_class_issue12403_Parsable, proxy_class_issue12403_Parsable_cons);
     jstring res = (*env)->CallObjectMethod(env, o, mid_Parsable_ToJSON);
     nstring _res = go_seq_from_java_string(env, res);
-    jstring exc = go_seq_get_exception_message(env);
-    nstring _exc = go_seq_from_java_string(env, exc);
+    jobject exc = go_seq_wrap_exception(env);
+    int32_t _exc = go_seq_to_refnum(env, exc);
     cproxyissue12403_Parsable_ToJSON_return sres = {
     	_res, _exc
     };
diff --git a/bind/testdata/issue12403.java.h.golden b/bind/testdata/issue12403.java.h.golden
index 1b79ac5..2424a0d 100644
--- a/bind/testdata/issue12403.java.h.golden
+++ b/bind/testdata/issue12403.java.h.golden
@@ -15,7 +15,7 @@
 
 typedef struct cproxyissue12403_Parsable_ToJSON_return {
     nstring r0;
-    nstring r1;
+    int32_t r1;
 } cproxyissue12403_Parsable_ToJSON_return;
 struct cproxyissue12403_Parsable_ToJSON_return cproxyissue12403_Parsable_ToJSON(int32_t refnum);
 
diff --git a/bind/testdata/issue12403.objc.go.h.golden b/bind/testdata/issue12403.objc.go.h.golden
index d418219..9d4e5fc 100644
--- a/bind/testdata/issue12403.objc.go.h.golden
+++ b/bind/testdata/issue12403.objc.go.h.golden
@@ -12,7 +12,7 @@
 
 typedef struct cproxyissue12403_Parsable_ToJSON_return {
 	nstring r0;
-	nstring r1;
+	int32_t r1;
 } cproxyissue12403_Parsable_ToJSON_return;
 struct cproxyissue12403_Parsable_ToJSON_return cproxyissue12403_Parsable_ToJSON(int32_t refnum);
 
diff --git a/bind/testdata/issue12403.objc.h.golden b/bind/testdata/issue12403.objc.h.golden
index cb81a42..5b9f310 100644
--- a/bind/testdata/issue12403.objc.h.golden
+++ b/bind/testdata/issue12403.objc.h.golden
@@ -7,6 +7,7 @@
 #define __GoIssue12403_H__
 
 #include <Foundation/Foundation.h>
+#include "GoUniverse.h"
 
 @protocol GoIssue12403Parsable;
 @class GoIssue12403Parsable;
diff --git a/bind/testdata/issue12403.objc.m.golden b/bind/testdata/issue12403.objc.m.golden
index 808db2c..072dfbc 100644
--- a/bind/testdata/issue12403.objc.m.golden
+++ b/bind/testdata/issue12403.objc.m.golden
@@ -31,14 +31,19 @@
 	int32_t refnum = go_seq_go_to_refnum(self._ref);
 	struct proxyissue12403_Parsable_ToJSON_return res = proxyissue12403_Parsable_ToJSON(refnum);
 	NSString *_ret0_ = go_seq_to_objc_string(res.r0);
-	NSString *_error = go_seq_to_objc_string(res.r1);
-	*ret0_ = _ret0_;
-	if ([_error length] != 0 && error != nil) {
-		NSMutableDictionary* details = [NSMutableDictionary dictionary];
-		[details setValue:_error forKey:NSLocalizedDescriptionKey];
-		*error = [NSError errorWithDomain:errDomain code:1 userInfo:details];
+	GoUniverseerror* _error = nil;
+	GoSeqRef* _error_ref = go_seq_from_refnum(res.r1);
+	if (_error_ref != NULL) {
+		_error = _error_ref.obj;
+		if (_error == nil) {
+			_error = [[GoUniverseerror alloc] initWithRef:_error_ref];
+		}
 	}
-	return ([_error length] == 0);
+	*ret0_ = _ret0_;
+	if (_error != nil && error != nil) {
+		*error = go_seq_to_nserror(_error, errDomain);
+	}
+	return (_error == nil);
 }
 
 @end
@@ -47,7 +52,7 @@
 
 nstring cproxyissue12403_Parsable_FromJSON(int32_t refnum, nstring jstr) {
 	@autoreleasepool {
-		id<GoIssue12403Parsable> o = go_seq_objc_from_refnum(refnum);
+		GoIssue12403Parsable* o = go_seq_objc_from_refnum(refnum);
 		NSString *_jstr = go_seq_to_objc_string(jstr);
 		NSString* returnVal = [o fromJSON:_jstr];
 		nstring _returnVal = go_seq_from_objc_string(returnVal);
@@ -57,21 +62,24 @@
 
 struct cproxyissue12403_Parsable_ToJSON_return cproxyissue12403_Parsable_ToJSON(int32_t refnum) {
 	@autoreleasepool {
-		id<GoIssue12403Parsable> o = go_seq_objc_from_refnum(refnum);
+		GoIssue12403Parsable* o = go_seq_objc_from_refnum(refnum);
 		NSString* ret0_;
 		NSError* error = nil;
 		BOOL returnVal = [o toJSON:&ret0_ error:&error];
 		nstring _ret0_ = go_seq_from_objc_string(ret0_);
-		NSString *error_str = nil;
+		GoUniverseerror* _error = nil;
 		if (!returnVal) {
-			error_str = [error localizedDescription];
-			if (error_str == nil || error_str.length == 0) {
-				error_str = @"gobind: unknown error";
-			}
+			_error = [[goSeqErrorWrapper alloc] initWithError:error];
 		}
-		nstring _error_str = go_seq_from_objc_string(error_str);
+		int32_t __error;
+		if ([(id<NSObject>)(_error) isKindOfClass:[GoUniverseerror class]]) {
+			id<goSeqRefInterface> _error_proxy = (id<goSeqRefInterface>)(_error);
+			__error = go_seq_go_to_refnum(_error_proxy._ref);
+		} else {
+			__error = go_seq_to_refnum(_error);
+		}
 		cproxyissue12403_Parsable_ToJSON_return _sres = {
-		  _ret0_, _error_str
+		  _ret0_, __error
 		};
 		return _sres;
 	}
diff --git a/bind/testdata/structs.go.golden b/bind/testdata/structs.go.golden
index 32f4c9c..5f6d741 100644
--- a/bind/testdata/structs.go.golden
+++ b/bind/testdata/structs.go.golden
@@ -52,7 +52,7 @@
 }
 
 //export proxystructs_S_Identity
-func proxystructs_S_Identity(refnum C.int32_t) (C.int32_t, C.nstring) {
+func proxystructs_S_Identity(refnum C.int32_t) (C.int32_t, C.int32_t) {
 	ref := _seq.FromRefNum(int32(refnum))
 	v := ref.Get().(*structs.S)
 	res_0, res_1 := v.Identity()
@@ -60,13 +60,10 @@
 	if res_0 != nil {
 		_res_0 = C.int32_t(_seq.ToRefNum(res_0))
 	}
-	var _res_1_str string
-	if res_1 == nil {
-		_res_1_str = ""
-	} else {
-		_res_1_str = res_1.Error()
+	var _res_1 C.int32_t = _seq.NullRefNum
+	if res_1 != nil {
+		_res_1 = C.int32_t(_seq.ToRefNum(res_1))
 	}
-	_res_1 := encodeString(_res_1_str)
 	return _res_0, _res_1
 }
 
@@ -124,7 +121,7 @@
 }
 
 //export proxystructs__IdentityWithError
-func proxystructs__IdentityWithError(param_s C.int32_t) (C.int32_t, C.nstring) {
+func proxystructs__IdentityWithError(param_s C.int32_t) (C.int32_t, C.int32_t) {
 	// Must be a Go object
 	_param_s_ref := _seq.FromRefNum(int32(param_s))
 	_param_s := _param_s_ref.Get().(*structs.S)
@@ -133,12 +130,9 @@
 	if res_0 != nil {
 		_res_0 = C.int32_t(_seq.ToRefNum(res_0))
 	}
-	var _res_1_str string
-	if res_1 == nil {
-		_res_1_str = ""
-	} else {
-		_res_1_str = res_1.Error()
+	var _res_1 C.int32_t = _seq.NullRefNum
+	if res_1 != nil {
+		_res_1 = C.int32_t(_seq.ToRefNum(res_1))
 	}
-	_res_1 := encodeString(_res_1_str)
 	return _res_0, _res_1
 }
diff --git a/bind/testdata/structs.java.c.golden b/bind/testdata/structs.java.c.golden
index c5f66e2..f2417cd 100644
--- a/bind/testdata/structs.java.c.golden
+++ b/bind/testdata/structs.java.c.golden
@@ -47,7 +47,7 @@
     int32_t _s = go_seq_to_refnum(env, s);
     struct proxystructs__IdentityWithError_return res = proxystructs__IdentityWithError(_s);
     jobject _r0 = go_seq_from_refnum(env, res.r0, proxy_class_structs_S, proxy_class_structs_S_cons);
-    jstring _r1 = go_seq_to_java_string(env, res.r1);
+    jobject _r1 = go_seq_from_refnum(env, res.r1, proxy_class__error, proxy_class__error_cons);
     go_seq_maybe_throw_exception(env, _r1);
     return _r0;
 }
@@ -57,7 +57,7 @@
     int32_t o = go_seq_to_refnum(env, this);
     struct proxystructs_S_Identity_return res = proxystructs_S_Identity(o);
     jobject _r0 = go_seq_from_refnum(env, res.r0, proxy_class_structs_S, proxy_class_structs_S_cons);
-    jstring _r1 = go_seq_to_java_string(env, res.r1);
+    jobject _r1 = go_seq_from_refnum(env, res.r1, proxy_class__error, proxy_class__error_cons);
     go_seq_maybe_throw_exception(env, _r1);
     return _r0;
 }
diff --git a/bind/testdata/structs.objc.h.golden b/bind/testdata/structs.objc.h.golden
index 0b9de0e..1512cf7 100644
--- a/bind/testdata/structs.objc.h.golden
+++ b/bind/testdata/structs.objc.h.golden
@@ -7,6 +7,7 @@
 #define __GoStructs_H__
 
 #include <Foundation/Foundation.h>
+#include "GoUniverse.h"
 
 @class GoStructsS;
 @class GoStructsS2;
diff --git a/bind/testdata/structs.objc.m.golden b/bind/testdata/structs.objc.m.golden
index 25dee7e..e48173e 100644
--- a/bind/testdata/structs.objc.m.golden
+++ b/bind/testdata/structs.objc.m.golden
@@ -57,14 +57,19 @@
 			_ret0_ = [[GoStructsS alloc] initWithRef:_ret0__ref];
 		}
 	}
-	NSString *_error = go_seq_to_objc_string(res.r1);
-	*ret0_ = _ret0_;
-	if ([_error length] != 0 && error != nil) {
-		NSMutableDictionary* details = [NSMutableDictionary dictionary];
-		[details setValue:_error forKey:NSLocalizedDescriptionKey];
-		*error = [NSError errorWithDomain:errDomain code:1 userInfo:details];
+	GoUniverseerror* _error = nil;
+	GoSeqRef* _error_ref = go_seq_from_refnum(res.r1);
+	if (_error_ref != NULL) {
+		_error = _error_ref.obj;
+		if (_error == nil) {
+			_error = [[GoUniverseerror alloc] initWithRef:_error_ref];
+		}
 	}
-	return ([_error length] == 0);
+	*ret0_ = _ret0_;
+	if (_error != nil && error != nil) {
+		*error = go_seq_to_nserror(_error, errDomain);
+	}
+	return (_error == nil);
 }
 
 - (double)sum {
@@ -155,19 +160,24 @@
 			_ret0_ = [[GoStructsS alloc] initWithRef:_ret0__ref];
 		}
 	}
-	NSString *_error = go_seq_to_objc_string(res.r1);
-	*ret0_ = _ret0_;
-	if ([_error length] != 0 && error != nil) {
-		NSMutableDictionary* details = [NSMutableDictionary dictionary];
-		[details setValue:_error forKey:NSLocalizedDescriptionKey];
-		*error = [NSError errorWithDomain:errDomain code:1 userInfo:details];
+	GoUniverseerror* _error = nil;
+	GoSeqRef* _error_ref = go_seq_from_refnum(res.r1);
+	if (_error_ref != NULL) {
+		_error = _error_ref.obj;
+		if (_error == nil) {
+			_error = [[GoUniverseerror alloc] initWithRef:_error_ref];
+		}
 	}
-	return ([_error length] == 0);
+	*ret0_ = _ret0_;
+	if (_error != nil && error != nil) {
+		*error = go_seq_to_nserror(_error, errDomain);
+	}
+	return (_error == nil);
 }
 
 void cproxystructs_I_M(int32_t refnum) {
 	@autoreleasepool {
-		id<GoStructsI> o = go_seq_objc_from_refnum(refnum);
+		GoStructsI* o = go_seq_objc_from_refnum(refnum);
 		[o m];
 	}
 }
diff --git a/bind/testdata/try.objc.h.golden b/bind/testdata/try.objc.h.golden
index 62b4107..6bbd53c 100644
--- a/bind/testdata/try.objc.h.golden
+++ b/bind/testdata/try.objc.h.golden
@@ -7,6 +7,7 @@
 #define __GoTry_H__
 
 #include <Foundation/Foundation.h>
+#include "GoUniverse.h"
 
 FOUNDATION_EXPORT NSString* GoTryThis();
 
diff --git a/bind/testdata/vars.objc.h.golden b/bind/testdata/vars.objc.h.golden
index e5b24ef..33bd824 100644
--- a/bind/testdata/vars.objc.h.golden
+++ b/bind/testdata/vars.objc.h.golden
@@ -7,6 +7,7 @@
 #define __GoVars_H__
 
 #include <Foundation/Foundation.h>
+#include "GoUniverse.h"
 
 @class GoVarsS;
 @protocol GoVarsI;
diff --git a/bind/testdata/vars.objc.m.golden b/bind/testdata/vars.objc.m.golden
index dbc5fd1..4bff376 100644
--- a/bind/testdata/vars.objc.m.golden
+++ b/bind/testdata/vars.objc.m.golden
@@ -182,7 +182,7 @@
 
 + (id<GoVarsI>) anInterface {
 	int32_t r0 = var_getvars_AnInterface();
-	id<GoVarsI> _r0 = nil;
+	GoVarsI* _r0 = nil;
 	GoSeqRef* _r0_ref = go_seq_from_refnum(r0);
 	if (_r0_ref != NULL) {
 		_r0 = _r0_ref.obj;
diff --git a/bind/testpkg/testpkg.go b/bind/testpkg/testpkg.go
index 3c0c59a..881a832 100644
--- a/bind/testpkg/testpkg.go
+++ b/bind/testpkg/testpkg.go
@@ -511,3 +511,15 @@
 	got := d.CDup(want)
 	return got == want
 }
+
+type EmptyErrorer interface {
+	EmptyError() error
+}
+
+func EmptyError() error {
+	return errors.New("")
+}
+
+func CallEmptyError(c EmptyErrorer) error {
+	return c.EmptyError()
+}
diff --git a/cmd/gomobile/bind.go b/cmd/gomobile/bind.go
index 8f388c9..41e8c8d 100644
--- a/cmd/gomobile/bind.go
+++ b/cmd/gomobile/bind.go
@@ -174,19 +174,26 @@
 
 func (b *binder) GenObjc(pkg *types.Package, allPkg []*types.Package, outdir string) (string, error) {
 	const bindPrefixDefault = "Go"
-	if bindPrefix == "" {
+	if bindPrefix == "" || pkg == nil {
 		bindPrefix = bindPrefixDefault
 	}
-	name := strings.Title(pkg.Name())
+	pkgName := ""
+	pkgPath := ""
+	if pkg != nil {
+		pkgName = pkg.Name()
+		pkgPath = pkg.Path()
+	} else {
+		pkgName = "universe"
+	}
 	bindOption := "-lang=objc"
 	if bindPrefix != bindPrefixDefault {
 		bindOption += " -prefix=" + bindPrefix
 	}
 
-	fileBase := bindPrefix + name
+	fileBase := bindPrefix + strings.Title(pkgName)
 	mfile := filepath.Join(outdir, fileBase+".m")
 	hfile := filepath.Join(outdir, fileBase+".h")
-	gohfile := filepath.Join(outdir, pkg.Name()+".h")
+	gohfile := filepath.Join(outdir, pkgName+".h")
 
 	conf := &bind.GeneratorConfig{
 		Fset:   b.fset,
@@ -195,7 +202,7 @@
 	}
 	generate := func(w io.Writer) error {
 		if buildX {
-			printcmd("gobind %s -outdir=%s %s", bindOption, outdir, pkg.Path())
+			printcmd("gobind %s -outdir=%s %s", bindOption, outdir, pkgPath)
 		}
 		if buildN {
 			return nil
@@ -245,13 +252,25 @@
 }
 
 func (b *binder) GenJava(pkg *types.Package, allPkg []*types.Package, outdir, javadir string) error {
-	className := strings.Title(pkg.Name())
+	var className string
+	pkgName := ""
+	pkgPath := ""
+	javaPkg := ""
+	if pkg != nil {
+		className = strings.Title(pkg.Name())
+		pkgName = pkg.Name()
+		pkgPath = pkg.Path()
+		javaPkg = bindJavaPkg
+	} else {
+		pkgName = "universe"
+		className = "Universe"
+	}
 	javaFile := filepath.Join(javadir, className+".java")
-	cFile := filepath.Join(outdir, "java_"+pkg.Name()+".c")
-	hFile := filepath.Join(outdir, pkg.Name()+".h")
+	cFile := filepath.Join(outdir, "java_"+pkgName+".c")
+	hFile := filepath.Join(outdir, pkgName+".h")
 	bindOption := "-lang=java"
-	if bindJavaPkg != "" {
-		bindOption += " -javapkg=" + bindJavaPkg
+	if javaPkg != "" {
+		bindOption += " -javapkg=" + javaPkg
 	}
 
 	conf := &bind.GeneratorConfig{
@@ -261,13 +280,13 @@
 	}
 	generate := func(w io.Writer) error {
 		if buildX {
-			printcmd("gobind %s -outdir=%s %s", bindOption, javadir, pkg.Path())
+			printcmd("gobind %s -outdir=%s %s", bindOption, javadir, pkgPath)
 		}
 		if buildN {
 			return nil
 		}
 		conf.Writer = w
-		return bind.GenJava(conf, bindJavaPkg, bind.Java)
+		return bind.GenJava(conf, javaPkg, bind.Java)
 	}
 	if err := writeFile(javaFile, generate); err != nil {
 		return err
@@ -293,12 +312,17 @@
 }
 
 func (b *binder) GenGo(pkg *types.Package, allPkg []*types.Package, outdir string) error {
-	pkgName := "go_" + pkg.Name()
+	pkgName := "go_"
+	pkgPath := ""
+	if pkg != nil {
+		pkgName += pkg.Name()
+		pkgPath = pkg.Path()
+	}
 	goFile := filepath.Join(outdir, pkgName+"main.go")
 
 	generate := func(w io.Writer) error {
 		if buildX {
-			printcmd("gobind -lang=go -outdir=%s %s", outdir, pkg.Path())
+			printcmd("gobind -lang=go -outdir=%s %s", outdir, pkgPath)
 		}
 		if buildN {
 			return nil
diff --git a/cmd/gomobile/bind_androidapp.go b/cmd/gomobile/bind_androidapp.go
index e6d8568..60f9166 100644
--- a/cmd/gomobile/bind_androidapp.go
+++ b/cmd/gomobile/bind_androidapp.go
@@ -76,6 +76,10 @@
 				return err
 			}
 		}
+		// Generate the error type.
+		if err := binder.GenGo(nil, binder.pkgs, srcDir); err != nil {
+			return err
+		}
 
 		err = writeFile(mainFile, func(w io.Writer) error {
 			_, err := w.Write(androidMainFile)
@@ -100,6 +104,9 @@
 				return err
 			}
 		}
+		if err := binder.GenJava(nil, binder.pkgs, srcDir, filepath.Join(androidDir, "src/main/java/go")); err != nil {
+			return err
+		}
 		if err := binder.GenJavaSupport(srcDir); err != nil {
 			return err
 		}
diff --git a/cmd/gomobile/bind_iosapp.go b/cmd/gomobile/bind_iosapp.go
index 1ec010c..c4753c0 100644
--- a/cmd/gomobile/bind_iosapp.go
+++ b/cmd/gomobile/bind_iosapp.go
@@ -41,6 +41,10 @@
 			return err
 		}
 	}
+	// Generate the error type.
+	if err := binder.GenGo(nil, binder.pkgs, srcDir); err != nil {
+		return err
+	}
 	mainFile := filepath.Join(tmpdir, "src/iosbin/main.go")
 	err = writeFile(mainFile, func(w io.Writer) error {
 		_, err := w.Write(iosBindFile)
@@ -50,12 +54,15 @@
 		return fmt.Errorf("failed to create the binding package for iOS: %v", err)
 	}
 
-	fileBases := make([]string, len(typesPkgs))
+	fileBases := make([]string, len(typesPkgs)+1)
 	for i, pkg := range binder.pkgs {
 		if fileBases[i], err = binder.GenObjc(pkg, binder.pkgs, srcDir); err != nil {
 			return err
 		}
 	}
+	if fileBases[len(fileBases)-1], err = binder.GenObjc(nil, binder.pkgs, srcDir); err != nil {
+		return err
+	}
 	if err := binder.GenObjcSupport(srcDir); err != nil {
 		return err
 	}
diff --git a/cmd/gomobile/bind_test.go b/cmd/gomobile/bind_test.go
index d0b7736..51d7d60 100644
--- a/cmd/gomobile/bind_test.go
+++ b/cmd/gomobile/bind_test.go
@@ -108,11 +108,17 @@
 mkdir -p $WORK/fakegopath/pkg/android_arm/golang.org/x/mobile
 mkdir -p $WORK/gomobile_bind
 gobind -lang=go -outdir=$WORK/gomobile_bind golang.org/x/mobile/asset
+mkdir -p $WORK/gomobile_bind
+gobind -lang=go -outdir=$WORK/gomobile_bind 
 mkdir -p $WORK/androidlib
 mkdir -p $WORK/android/src/main/java/{{.JavaPkgDir}}
 {{.GobindJavaCmd}} -outdir=$WORK/android/src/main/java/{{.JavaPkgDir}} golang.org/x/mobile/asset
 mkdir -p $WORK/gomobile_bind
 mkdir -p $WORK/gomobile_bind
+mkdir -p $WORK/android/src/main/java/go
+gobind -lang=java -outdir=$WORK/android/src/main/java/go 
+mkdir -p $WORK/gomobile_bind
+mkdir -p $WORK/gomobile_bind
 cp $GOPATH/src/golang.org/x/mobile/bind/java/seq_android.go.support $WORK/gomobile_bind/seq_android.go
 mkdir -p $WORK/gomobile_bind
 cp $GOPATH/src/golang.org/x/mobile/bind/java/seq_android.c.support $WORK/gomobile_bind/seq_android.c