Skip to content
JavaGo
Part I · Orientation

Go Mental Model

Go is not Java with different syntax. The ten differences that shape everything else, and how to read the rest of this guide.

Section 00 of 31 3 min read

Before writing a line of Go, re-wire how you think about programs. Go has a different philosophy about nearly everything a Java developer takes for granted: types, memory, concurrency, errors and how an application is wired together. Learn the philosophy first and the syntax becomes obvious.

The core differences at a glance

JavaGo
Classes bundle data and behaviourStructs hold data; methods are declared separately
Inheritance with extendsComposition via struct embedding
Exceptions, checked and uncheckedErrors are return values, handled explicitly
JVM: bytecode, JIT, tunable GCNative binary with a small runtime and a low-latency GC
Platform threads (~1 MB stack each), or virtual threads since Java 21Goroutines: start at a few KB, scheduled by the Go runtime
Spring wires dependencies (IoC)You wire dependencies by hand in main()
Reverse-domain packages, one class per fileShort package names; a package is a directory
Any object reference can be nullnil only for pointers, slices, maps, channels, functions and interfaces
Generics with erasure and wildcardsGenerics with constraints, no wildcards or variance
implements declared up frontInterfaces are satisfied implicitly

The Go philosophy in three sentences

Go's design philosophy

Simplicity over cleverness. Go deliberately has fewer features than Java: no inheritance, no exceptions, no annotations, no overloading. Generics arrived only in 2022, a decade after Go 1.0. Each omission is a deliberate trade to keep code readable by anyone on the team.

Explicit over implicit. Errors are returned, not thrown. Dependencies are passed in, not injected by a framework. Whether a function can mutate its argument is visible in its signature. You can follow control flow by reading, without knowing what a framework does at runtime.

Concurrency as a first-class citizen. Goroutines and channels are part of the language, and the runtime schedules them. You do not choose a thread pool or a reactive library; blocking code is the normal, efficient way to write concurrent Go.

How to read this guide

Most sections show the same job in idiomatic Java and in idiomatic Go: side by side when the code is short, Java above Go when it is not. Under many comparisons is a Why Go does this box explaining the reasoning behind the design choice. Read those carefully: they build the mental model, not just the syntax.

The guide is written against Go 1.27 and Java 25. When a feature is recent, the version that introduced it is called out, because you will meet older code in real codebases.

One thing to internalise before anything else

Java — methods live inside the class
public final class Celsius {
    private final double value;

    public Celsius(double value) {
        this.value = value;
    }

    public double toFahrenheit() {
        return value * 9 / 5 + 32;
    }
}
Go — methods are declared beside the type
// A new named type built on float64
type Celsius float64

// A method is a function with a receiver
func (c Celsius) Fahrenheit() float64 {
    return float64(c)*9/5 + 32
}

temp := Celsius(21.5)
fmt.Println(temp.Fahrenheit()) // 70.7

Java: behaviour inside the class

Go: behaviour beside the type

Fig. 0.1A Java class encloses its fields and methods. In Go a type is just data, and a method is an ordinary function whose receiver ties it to the type. Any non-interface type defined in your package can have methods, even one built on float64.
↑ ↓ to navigateEnter to openEsc to close