Go Mental Model — Recap
The complete Java-to-Go mapping on one page, what Go leaves out on purpose, the three mental shifts, and where to go next.
Section 30 of 31 3 min read
You have seen the syntax and the patterns. Step back and look at how the pieces fit together into one coherent way of building software.
The complete Java → Go mapping
| Java | Go |
|---|---|
| Class | Struct, with methods declared beside it |
| Constructor | NewX() function, or a struct literal |
this | An explicit, named receiver: func (s *Server) … |
extends | Embedding: promotion of fields and methods, not is-a |
implements | Nothing: interfaces are satisfied implicitly |
| Interface beside its implementation | Small interface declared by the consumer |
| Checked and unchecked exceptions | error values, wrapped with %w, inspected with errors.Is/As |
try/catch | if err != nil { … } |
finally / try-with-resources | defer, right after acquiring the resource |
synchronized / ReentrantLock | sync.Mutex + defer Unlock() (not reentrant) |
volatile / AtomicLong | sync/atomic types |
CountDownLatch / invokeAll | sync.WaitGroup / errgroup |
Thread, ExecutorService, virtual threads | go f() |
BlockingQueue | Buffered channel |
Thread.interrupt(), timeouts | context.Context |
ThreadLocal / MDC | context.Context values; attributes on slog.Logger |
| Spring DI | Constructor functions wired in main() |
null | nil for pointers, slices, maps, channels, functions, interfaces; zero values elsewhere |
Object | any |
Optional<T> | (T, bool) comma-ok results, or *T |
List<T> / Map<K,V> / Set<T> | []T / map[K]V / map[T]struct{} |
| Streams | Loops; slices/maps helpers; iter.Seq for lazy sequences |
enum | Named type + iota constants |
| Annotations | Struct tags, and //go: directives |
| Maven / Gradle | The go command and go.mod |
What Go leaves out on purpose
Missing by design, not by oversight
No inheritance. Embedding is not inheritance: there is no overriding and no super. Calls through an interface are dynamically dispatched, which is where polymorphism lives.
No exceptions for ordinary failures. Panics exist for bugs; everything else is an error value.
No overloading and no default arguments. One name, one signature. Use distinct names, a config struct or functional options.
No implicit conversions. int32 to int64, int to float64, string to []byte: each is written out.
No annotations or runtime magic. No proxies, no aspect weaving, no classpath scanning. What runs is what you can read.
The three mental shifts
Shift 1: data and behaviour are separate
Stop asking "what can a User do?" and ask "what operations work on a User?" A struct is just data. SendWelcomeEmail(ctx, u User) can live in the email package. Behaviour attaches to types through small interfaces at the point of use, not through class hierarchies.
Shift 2: errors are just values
Stop reaching for try/catch. Call, check, then handle or wrap and return. if err != nil is not noise; it marks every place something can fail and shows what happens next. Reviewing error handling becomes a matter of reading, not of reasoning about what might be thrown.
1 · Data and behaviour are separate
2 · Errors are values
3 · Concurrency is ordinary, and bounded
Reading a Go codebase cold
- Start at
cmd/*/main.go. It is the wiring diagram: what gets constructed, from what, and in which order. - Read the package list under
internal/. Package names are the architecture. - Find the interfaces. They are the seams between packages, and they sit next to the code that consumes them.
- Search for
gostatements. For each goroutine, find how it stops: a context, a closed channel, or the end of its input. - Follow
if err != nilbranches to see the failure paths, anddeferlines to see resource lifetimes. - Run
go test -race ./...andgo vet ./...before you change anything.
Where to go next
- A Tour of Go (opens in a new tab): the interactive introduction, useful for filling syntax gaps.
- Effective Go (opens in a new tab) and Go Code Review Comments (opens in a new tab): the idioms, straight from the Go team.
- Google’s Go Style Guide (opens in a new tab): a thorough, opinionated style reference.
- Go by Example (opens in a new tab): short, runnable examples for every standard feature.
- The Go Memory Model (opens in a new tab): what the language guarantees about concurrent access.
- Release notes (opens in a new tab): each release is small; reading them keeps you current.