Skip to content
JavaGo
Part VII · Wrap-up

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

JavaGo
ClassStruct, with methods declared beside it
ConstructorNewX() function, or a struct literal
thisAn explicit, named receiver: func (s *Server) …
extendsEmbedding: promotion of fields and methods, not is-a
implementsNothing: interfaces are satisfied implicitly
Interface beside its implementationSmall interface declared by the consumer
Checked and unchecked exceptionserror values, wrapped with %w, inspected with errors.Is/As
try/catchif err != nil { … }
finally / try-with-resourcesdefer, right after acquiring the resource
synchronized / ReentrantLocksync.Mutex + defer Unlock() (not reentrant)
volatile / AtomicLongsync/atomic types
CountDownLatch / invokeAllsync.WaitGroup / errgroup
Thread, ExecutorService, virtual threadsgo f()
BlockingQueueBuffered channel
Thread.interrupt(), timeoutscontext.Context
ThreadLocal / MDCcontext.Context values; attributes on slog.Logger
Spring DIConstructor functions wired in main()
nullnil for pointers, slices, maps, channels, functions, interfaces; zero values elsewhere
Objectany
Optional<T>(T, bool) comma-ok results, or *T
List<T> / Map<K,V> / Set<T>[]T / map[K]V / map[T]struct{}
StreamsLoops; slices/maps helpers; iter.Seq for lazy sequences
enumNamed type + iota constants
AnnotationsStruct tags, and //go: directives
Maven / GradleThe 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

Fig. 30.1The three shifts this guide keeps returning to, with the sections where each one is worked through.

Reading a Go codebase cold

  1. Start at cmd/*/main.go. It is the wiring diagram: what gets constructed, from what, and in which order.
  2. Read the package list under internal/. Package names are the architecture.
  3. Find the interfaces. They are the seams between packages, and they sit next to the code that consumes them.
  4. Search for go statements. For each goroutine, find how it stops: a context, a closed channel, or the end of its input.
  5. Follow if err != nil branches to see the failure paths, and defer lines to see resource lifetimes.
  6. Run go test -race ./... and go vet ./... before you change anything.

Where to go next

↑ ↓ to navigateEnter to openEsc to close