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
| Java | Go |
|---|---|
| Classes bundle data and behaviour | Structs hold data; methods are declared separately |
Inheritance with extends | Composition via struct embedding |
| Exceptions, checked and unchecked | Errors are return values, handled explicitly |
| JVM: bytecode, JIT, tunable GC | Native binary with a small runtime and a low-latency GC |
| Platform threads (~1 MB stack each), or virtual threads since Java 21 | Goroutines: 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 file | Short package names; a package is a directory |
Any object reference can be null | nil only for pointers, slices, maps, channels, functions and interfaces |
| Generics with erasure and wildcards | Generics with constraints, no wildcards or variance |
implements declared up front | Interfaces 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
public final class Celsius {
private final double value;
public Celsius(double value) {
this.value = value;
}
public double toFahrenheit() {
return value * 9 / 5 + 32;
}
}// 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.7Java: behaviour inside the class
Go: behaviour beside the type
float64.