Java to Go in-depth tutorial

This tutorial is in­tended to help Java deve­lopers come up to speed quickly with Go.

Hello stack (example)

Let’s start with a small but complete example. It shows how to implement and use a simple abstract data type in Go.

// Package collection implements a stack of strings.
package collection

// The zero value for Stack is an empty stack ready to use.
type Stack struct {
    data []string

// Push adds x to the top of the stack.
func (s *Stack) Push(x string) { = append(, x)

// Pop removes and returns the top element of the stack.
// It’s a run-time error to call Pop on an empty stack.
func (s *Stack) Pop() string {
    n := len( - 1
    res :=[n][n] = "" // to avoid memory leak =[:n]
    return res

// Size returns the number of elements in the stack.
func (s *Stack) Size() int {
    return len(

Here is a Hello world program, which shows how to use the collection.Stack abstract data type.

package collection_test

import (

func ExampleStack() {
    var s collection.Stack
    s.Push("Hello, ")
    for s.Size() > 0 {
    // Output: Hello, world!

Note: The idiomatic way to implement a stack in Go is to use a slice directly. See, Implement a stack (LIFO).

Main differences

Object-oriented programming

Functional programming

Pointers and references

Built-in types

Error handling


Absent features


Syntax Terror


The declaration syntax is reversed compared to Java. You write the name followed by the type. Type declarations may be read easily from left to right.

Go Approximate Java equivalent
var v1 int int v1 = 0;
var v2 *int Integer v2 = null;
var v3 string String v3 = "";
var v4 [10]int int[] v4 = new int[10];
(Arrays are values in Go.)
var v5 []int int[] v5 = null;
var v6 *struct{ a int } class C { int a; }
C v6 = null;
var v7 map[string]int HashMap<String, Integer> v7;
v7 = null;
var v8 func(a int) int interface F {
  int f(int a);

F v8 = null;

Declarations generally take the form of a keyword followed by the name of the object being declared. The keyword is one of const, type, var, or func. You can also use a keyword followed by a series of declarations in parentheses.

var (
    n int
    x float64

When declaring a function, you must either provide a name for each parameter or not provide a name for any parameter; you can’t omit some names and provide others. You may group several names with the same type.

func f(i, j, k int, s, t string)

A variable may be initialized when it is declared. When this is done, specifying the type of the variable is permitted but not required. When the type is not specified, it defaults to the type of the initialization expression.

var v9 = *v2

If a variable is not initialized explicitly, the type must be specified. In that case it will be implicitly initialized to the type’s zero value (0, nil, "", etc.). There are no uninitialized variables in Go.

Short declarations

Within a function, a short declaration syntax is available with := . The statement

v10 := v1

is the same as

var v10 = v1

Function types

In Go, functions are first-class citizens. Go’s function type denotes the set of all functions with the same parameter and result types.

type binOp func(int, int) int

var op binOp
add := func(i, j int) int { return i + j }

op = add
n = op(100, 200)  // n = 100 + 200

Multiple assignment

Go permits multiple assignments. The expressions on the right are evaluated before assigning to any of the operands on the left.

i, j = j, i  // Swap i and j.

Functions may have multiple return values, indicated by a list in parentheses. The returned values can be stored by assignment to a list of variables.

func f() (i int, pj *int) { ... }
v1, v2 = f()

The blank identifier

The blank identifier, represented by the underscore character, provides a way to ignore values returned by a multi-valued expression:

v1, _ = f()  // Ignore second value returned by f().

Semicolons and formatting

Instead of worrying about semicolons and formatting, you may use the gofmt program to produce a single standard Go style. While this style may initially seem odd, it is as good as any other style, and familiarity will lead to comfort.

Go code uses very few semicolons in practice. Technically, all Go statements are terminated by a semicolon. However, Go implicitly inserts a semicolon at the end of a non-blank line unless the line is clearly incomplete. A consequence of this is that in some cases Go does not permit a line break. For example, you may not write

func g()
{            // INVALID: "{" should be on previous line.

A semicolon will be inserted after g() causing it to be a function declaration rather than a function definition. Similarly, you may not write

if n == 0 {
else {       // INVALID: "else {" should be on previous line.

A semicolon will be inserted after the } preceding the else, causing a syntax error.

Conditional statements

Go does not use parentheses around the condition of an if statement, or the expressions of a for statement, or the value of a switch statement. On the other hand, it does require curly braces around the body of an if or for statement.

if a < b { f() }
if (a < b) { f() }          // Parentheses are unnecessary.
if (a < b) f()              // INVALID
for i = 0; i < 10; i++ {}
for (i = 0; i < 10; i++) {} // INVALID

Furthermore, if and switch accept an optional initialization statement, which is commonly used to set up a local variable.

if err := file.Chmod(0664); err != nil {
    return err

For statements

Go does not have a while statement nor does it have a do-while statement. The for statement may be used with a single condition, which makes it equivalent to a while statement. Omitting the condition entirely produces an endless loop.

A for statement may contain a range clause for iterating over strings, arrays, slices, maps, or channels. Instead of writing

for i := 0; i < len(a); i++ { ... }

to loop over the elements of a, we could also write

for i, v := range a { ... }

This assigns i to the index and v to the value of the successive elements of an array, slice, or string.

Break and continue

Like Java, Go permits break and continue to specify a label, but the label must refer to a for, switch, or select statement.

Switch statements

In a switch statement, case labels do not fall through by default, but you can make them fall through by ending a case with a fallthrough statement.

switch n {
case 0: // empty case body
case 1:
    f() // f is not called when n == 0.

But a case can have multiple values.

switch n {
case 0, 1:
    f() // f is called if n == 0 || n == 1.

The values in a case can be any type that supports the equality comparison operator, such as strings or pointers. A missing switch expression is equivalent to the expression true.

switch {
case n < 0:
case n == 0:

Increment and decrement

The ++ and - - may only be used as postfix operators and only in statements, not in expressions. For example, you cannot write n = i++.

Defer statement

A defer statement invokes a function whose execution is deferred to the moment the surrounding function returns.

f, err := os.Open("filename")
defer f.Close() // f will be closed when this function returns.


In Go constants may be untyped. This applies to

A value derived from an untyped constant becomes typed when it is used within a context that requires a typed value. This permits constants to be used relatively freely even though Go has no implicit type conversion.

var a uint
f(a + 1)   // The untyped numeric constant 1 becomes typed as uint.
f(a + 1e3) // 1e3 is also typed as uint.

The language does not impose any limits on the size of an untyped numeric constant. A limit is only applied when a constant is used where a type is required.

const huge = 1 << 100
var n int = huge >> 98

If the type is absent in a variable declaration and the corresponding expression evaluates to an untyped numeric constant, the constant is converted to type rune, int, float64, or complex128 respectively, depending on whether the value is a character, integer, floating-point, or complex constant.

c := 'å'    // rune (alias for int32)
n := 1 + 2  // int
x := 2.7    // float64
z := 1 + 2i // complex128

Go does not have enumerated types. Instead, you can use the special name iota in a single const declaration to get a series of increasing value. When an initialization expression is omitted for a const, it reuses the preceding expression.

const (
    red = iota // red == 0
    blue       // blue == 1
    green      // green == 2


A struct corresponds to a class in Java, but the members of a struct cannot be methods, only variables. A pointer to a struct is similar to a reference variable in Java. As opposed to Java classes, structs may also be defined as direct values. In both cases you use . to access the members of a struct.

type MyStruct struct {
    s string
    n int64

var x MyStruct     // x is initialized to MyStruct{"", 0}.
var px *MyStruct   // px is initialized to nil.
px = new(MyStruct) // px points to the new struct MyStruct{"", 0}.

x.s = "Foo"
px.s = "Bar"

In Go, methods may be associated with any user-defined type, not just with structs; see the section Methods and interfaces.


If you have an int, a struct or an array, assign­ment copies the contents of the object. To achieve the effect of Java reference variables, Go uses pointers.

For any type T, there is a corresponding pointer type *T, denoting pointers to values of type T.

To allocate storage for a pointer variable, use the built-in function new, which takes a type and returns a pointer to the allocated storage. The allocated space will be zero-initialized for the type. For example, new(int) allocates storage for a new int, initializes it with the value 0, and returns its address, which has type *int.

The Java code T p = new T(), where T is a class with two instance variables a and b of type int, corresponds to

type T struct { a, b int }
var p *T = new(T)

or the more idiomatic

p := new(T)

The declaration var v T, which declares a variable that holds a value of type T, has no equivalent in Java. Values can also be created and initialized using a composite literal. For example:

v := T{1, 2}

is equivalent to

var v T
v.a = 1
v.b = 2

For an operand x of type T, the address operator &x gives the address of x, a value of type *T. For example:

p := &T{1, 2} // p has type *T

For an operand x of pointer type, the pointer indirection *x denotes the value pointed to by x. Pointer indirections are rarely used; Go, just like Java, can automatically take the address of a variable.

p := new(T)
p.a = 1 // equivalent to (*p).a = 1


A slice is conceptually a struct with three fields:

Slices support the [] operator to access elements of the underlying array.

Given an array, or another slice, a new slice is created via a[i:j].

The new slice refers to the same array to which a refers. That is, changes made to the elements using the new slice may be seen using a.

The capacity of the new slice is simply the capacity of a minus i. The capacity of an array is the length of the array.

var s []int
var a [10]int

s = a[:] // short for s = a[0:len(a)]

If you create a value of type [100]byte (an array of 100 bytes, perhaps a buffer) and you want to pass it to a function without copying it, declare the function parameter to have type []byte, and pass a slice of the array. Slices may also be created using the make function as described below.

Slices combined with the built-in function append offer much the same functionality as Java’s ArrayList.

s0 := []int{1, 2}
s1 := append(s0, 3)     // append a single element
s2 := append(s1, 4, 5)  // append multiple elements
s3 := append(s2, s0...) // append a slice

The slice syntax may also be used with a string. It returns a new string whose value is a substring of the original string.

Making values

Map and channel values must be allocated using the built-in make function. For example, calling


returns a newly allocated value of type map[string]int.

As opposed to new, make returns the actual object, not an address. This is consistent with the fact that maps and channels are reference types.

For maps, make takes a capacity hint as an optional second argument.

For channels, there is an optional second argument that sets the buffering capacity of the channel; the default is 0 (unbuffered).

The make function may also be used to allocate a slice. In this case it allocates memory for the underlying array and returns a slice referring to it. There is one required argument, which is the number of elements in the slice. A second optional argument is the capacity of the slice.

m := make([]int, 10, 20) // Same as new([20]int)[:10]

Methods and interfaces

A method looks like an ordinary function definition, except that it has a receiver. The receiver is similar to the this reference in a Java instance method.

type MyType struct { i int }

func (p *MyType) Get() int {
    return p.i

var pm = new(MyType)
var n = pm.Get()

This declares a method Get associated with MyType. The receiver is named p in the body of the function.

Methods are declared on defined types. If you convert the value to a different type, the new value will have the methods of the new type, not those of the old type.

You may define methods on a built-in type by declaring a new defined type derived from it. The new type is distinct from the built-in type.

type MyInt int

func (p MyInt) Get() int {
    return int(p) // The conversion is required.

func f(i int) {}
var v MyInt

v = v * v // The operators of the underlying type still apply.
f(int(v)) // int(v) has no declared methods.
f(v)      // INVALID


A Go interface is similar to a Java interface, but any type that provides the methods named in a Go interface may be treated as an implementation of that interface. No explicit declaration is required.

Let’s assume that this interface is defined:

type MyInterface interface {
    Get() int
    Set(i int)

Since MyType already has a Get method, we can make MyType satisfy the interface by adding

func (p *MyType) Set(i int) {
    p.i = i

Now any function which takes MyInterface as a parameter will accept a variable of type *MyType.

func GetAndSet(x MyInterface) {}

func f1() {
    var p MyType

In Java terms, defining Set and Get for *MyType made *MyType automatically implement MyInterface. A type may satisfy multiple interfaces. This is a form of duck typing.

When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck.
– James Whitcomb Riley

Embedding (delegation)

Embedding a type as an anonymous field may be used to implement a form of subtyping.

type MySubType struct {
    j int

func (p *MySubType) Get() int {
    return p.MyType.Get()

This effectively implements MySubType as a subtype of MyType.

func f2() {
    var p MySubType

The Set method is inherited from MyType, because methods associated with the anonymous field are promoted to become methods of the enclosing type.

In this case, because MySubType has an anonymous field of type MyType, the methods of MyType also become methods of MySubType. The Get method was overridden, and the Set method was inherited.

This is not the same as a subclass in Java, but a form of delegation. When a method of an anonymous field is called, its receiver is the field, not the surrounding struct. In other words, methods on anonymous fields are not dynamically dispatched. When you want the equivalent of Java’s dynamic method lookup, use an interface.

func f3() {
    var v MyInterface

    v = new(MyType)
    v.Get() // Call the Get method for *MyType.

    v = new(MySubType)
    v.Get() // Call the Get method for *MySubType.

Type assertions

A variable that has an interface type may be converted to have a different interface type using a type assertion. This is implemented dynamically at run time. Unlike Java, there does not need to be any declared relationship between the two interfaces.

type Printer interface {

func f4(x MyInterface) {
    x.(Printer).Print() // type assertion to Printer

The conversion to Printer is entirely dynamic. It will work as long as the dynamic type of x (the actual type of the value stored in x) defines a Print method.


Where Java typically uses exceptions, Go has two different mechanisms:

Go’s multivalued return makes it easy to return a detailed error message alongside the normal return value. By convention, such messages have type error, a simple built-in interface.

type error interface {
    Error() string

For example, the os.Open function returns a non-nil error value when it fails to open a file.

func Open(name string) (file *File, err error)

The following code uses os.Open to open a file. If an error occurs it calls log.Fatal to print the error message and stop.

f, err := os.Open("filename.ext")
if err != nil {
// do something with the open *File f

The error interface requires only an Error method, but specific error implementations often have additional methods, allowing callers to inspect the details of the error.

Panic and recover

A panic is a run-time error that unwinds the stack of the goroutine, running any deferred functions along the way, and then stops the program.

Panics are similar to Java exceptions, but are only intended for run-time errors, such as following a nil pointer or attempting to index an array out of bounds. To signify events such as end-of-file, Go programs use the built-in error type as described above.

The built-in function recover can be used to regain control of a panicking goroutine and resume normal execution:

Because the only code that runs while unwinding is inside deferred functions, recover is only useful inside deferred functions. If the goroutine is not panicking, recover returns nil.

Goroutines and channels

Bouncing balls


Go permits starting a new thread of execution, a goroutine, using the go statement. It runs a function in a different, newly created, goroutine. All goroutines in a single program share the same address space.

Goroutines are lightweight, costing little more than the allocation of stack space. The stacks start small and grow by allocating and freeing heap storage as required. Internally goroutines act like coroutines that are multiplexed among multiple operating system threads.

go list.Sort() // Run list.Sort in parallel; don’t wait for it.

Go has function literals, which can act as closures and are powerful when coupled with the go statement.

// Publish prints text to stdout after the given time has expired.
func Publish(text string, delay time.Duration) {
    go func() {
    }() // Note the parentheses. We must call the function.

The variables text and delay are shared between the surrounding function and the function literal; they survive as long as they are accessible.


A channel provides a mechanism for two goroutines to synchronize execution and communicate by passing a value of a specified element type. The <- operator specifies the channel direction, send or receive. If no direction is given, the channel is bi-directional.

chan Sushi     // can be used to send and receive values of type Sushi
chan<- float64 // can only be used to send float64s
<-chan int     // can only be used to receive ints

Channels are a reference type and are allocated with make.

ic := make(chan int)       // unbuffered channel of ints
wc := make(chan *Work, 10) // buffered channel of pointers to Work

To send a value on a channel, use <- as a binary operator. To receive a value on a channel, use it as a unary operator.

ic <- 3      // Send 3 on the channel.
work := <-wc // Receive a pointer to Work from the channel.

The close function records that no more values will be sent on a channel:

ch := make(chan string)
go func() {
    ch <- "Hello!"
fmt.Println(<-ch) // Print "Hello!".
fmt.Println(<-ch) // Print the zero value "" without blocking.
fmt.Println(<-ch) // Once again print "".
v, ok := <-ch     // v is "", ok is false.

In the next example we let the Publish function return a channel, which is used to broadcast a message when the text has been published.

// Publish prints text to stdout after the given time has expired.
// It closes the wait channel when the text has been published.
func Publish(text string, delay time.Duration) (wait <-chan struct{}) {
    ch := make(chan struct{})
    go func() {
    return ch

This is how you might use this Publish function.

wait := Publish("important news", 2 * time.Minute)
// Do some more work.
<-wait // blocks until the text has been published

Select statement

The select statement is the final tool in Go’s concurrency toolkit. It chooses which of a set of possible communications will proceed:

Here is a toy example showing how the select statement can be used to implement a random number generator.

rand := make(chan int)
for { // Send random sequence of bits to rand.
    select {
    case rand <- 0: // note: no statement
    case rand <- 1:

Somewhat more realistically, here is how a select statement could be used to set a time limit on a receive operation.

select {
case news := <-AFP:
case <-time.After(time.Minute):
    fmt.Println("Time out: no news in one minute.")

The function time.After is part of the standard library; it waits for a specified time to elapse and then sends the current time on the returned channel.

Hello server (example)

We end with a small example to show how the pieces fit together. The server package implements a server, which accepts Work requests through a channel:

package server

import "log"

// New creates a new server that accepts Work requests
// through the req channel.
func New() (req chan<- *Work) {
    wc := make(chan *Work)
    go serve(wc)
    return wc

type Work struct {
    Op    func(int, int) int
    A, B  int
    Reply chan int // Server sends result on this channel.

func serve(wc <-chan *Work) {
    for w := range wc {
        go safelyDo(w)

func safelyDo(w *Work) {
    // Regain control of a panicking goroutine to avoid
    // killing the other executing goroutines.
    defer func() {
        if err := recover(); err != nil {
            log.Println("work failed:", err)

func do(w *Work) {
    w.Reply <- w.Op(w.A, w.B)

This is how you might use it.

package server_test

import (

func main() {
    s := server.New()

    divideByZero := &server.Work{
        Op:    func(a, b int) int { return a / b },
        A:     100,
        B:     0,
        Reply: make(chan int),
    s <- divideByZero

    select {
    case res := <-divideByZero.Reply:
    case <-time.After(time.Second):
        fmt.Println("No result in one second.")
    // Output: No result in one second.

Further reading

Tutorials for beginners and experienced developers alike: best practices and production-quality code examples.

Share this page: