---
# === IDENTITY ===
id: software/migrations/java-to-go/2026
canonical_question: "How do I migrate a Java application to Go?"
aliases:
  - "Java to Go migration guide"
  - "convert Java to Golang"
  - "rewrite Java in Go"
  - "Java to Go guide"
  - "migrate Spring Boot to Go"
  - "Java to Golang microservices migration"
  - "replace Java with Go"
entity_type: software_reference
domain: software > migrations > java_to_go
region: global
jurisdiction: global
temporal_scope: 2020-2026

# === VERIFICATION ===
last_verified: 2026-05-29
confidence: 0.89
version: 2.1
first_published: 2026-02-17

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: evolving
  last_breaking_change: "Go 1.25 (2025-08) — container-aware GOMAXPROCS now respects Cgroup CPU limits by default"
  next_review: 2026-11-25
  change_sensitivity: medium

# === CONSTRAINTS ===
constraints:
  - "Go has no exceptions — all error handling must use explicit return values; never use panic/recover for control flow"
  - "Go does not support class inheritance — use struct embedding for composition and implicit interfaces for polymorphism"
  - "Never let Java-style circular package dependencies into Go — Go compiler rejects them; restructure with interfaces or flat packages"
  - "Always propagate context.Context as the first parameter in I/O functions — Go has no implicit request-scoped state like Spring's RequestContextHolder"
  - "Go maps must be initialized before write (make or literal) — var m map[K]V creates a nil map that panics on assignment"
  - "Run go vet ./... and go test -race ./... before every deployment — Go's static analysis and race detector catch bugs that Java's type system handles at compile time"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "User wants to migrate Java/Spring to Kotlin instead of Go"
    use_instead: "software/migrations/java-spring-to-kotlin/2026"
  - condition: "User wants to migrate legacy Java EE to Spring Boot rather than leave the JVM"
    use_instead: "software/migrations/java-ee-to-spring-boot/2026"
  - condition: "User wants to migrate PHP to Go (not Java)"
    use_instead: "software/migrations/php-to-go/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: architecture
    question: "Is the Java application a monolith or microservices?"
    type: choice
    options: ["Monolith", "Microservices", "Modular monolith"]
  - key: framework_depth
    question: "How heavily does the codebase depend on Spring/Jakarta EE?"
    type: choice
    options: ["Heavily (Spring Boot + JPA + Security)", "Moderately (Spring Web only)", "Lightly (stdlib + minimal frameworks)"]
  - key: team_experience
    question: "Does the team have prior Go experience?"
    type: choice
    options: ["Yes — production Go experience", "Some — tutorials and side projects", "None — Java-only team"]

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/migrations/java-to-go/2026"
suggested_citation: "Source: knowledgelib.io — AI Knowledge Library (verified 2026-05-29)"

# === RELATED UNITS ===
related_kos:
  depends_on:
    - id: "software/migrations/monolith-to-microservices/2026"
      label: "Monolith to Microservices"
  related_to:
    - id: "software/patterns/error-handling-strategies/2026"
      label: "Error Handling Strategies"
    - id: "software/patterns/concurrency-parallelism/2026"
      label: "Concurrency and Parallelism Patterns"
    - id: "software/patterns/dependency-injection/2026"
      label: "Dependency Injection"
  alternative_to:
    - id: "software/migrations/java-spring-to-kotlin/2026"
      label: "Java Spring to Kotlin Migration"
  often_confused_with:
    - id: "software/migrations/php-to-go/2026"
      label: "PHP to Go Migration"

# === SOURCES (10 authoritative sources) ===
# Types: official_docs, technical_blog, rfc_spec, academic_paper, community_resource, industry_report
# Reliability: high, moderate_high, moderate, moderate_low, low, authoritative
sources:
  - id: src1
    title: "Effective Go"
    author: Go Team
    url: https://go.dev/doc/effective_go
    type: official_docs
    published: 2024-01-01
    reliability: authoritative
  - id: src2
    title: "We switched from Java to Go and don't regret it"
    author: Glasskube
    url: https://glasskube.dev/blog/from-java-to-go/
    type: technical_blog
    published: 2024-09-15
    reliability: high
  - id: src3
    title: "Moving from Java to Go? What you need to know"
    author: Applied Go
    url: https://learn.appliedgo.net/blog/from-java-to-go
    type: community_resource
    published: 2024-03-10
    reliability: high
  - id: src4
    title: "Go vs. Java: 15 main differences"
    author: YourBasic
    url: https://yourbasic.org/golang/go-vs-java/
    type: community_resource
    published: 2023-06-01
    reliability: moderate_high
  - id: src5
    title: "Java to Go Migration: Why Teams Choose Golang for Speed & Scalability"
    author: Singula Team
    url: https://singula.team/blog/migration-from-java-to-go
    type: technical_blog
    published: 2024-08-20
    reliability: high
  - id: src6
    title: "Go by Example"
    author: Mark McGranaghan
    url: https://gobyexample.com/
    type: community_resource
    published: 2024-01-01
    reliability: high
  - id: src7
    title: "Return and handle an error"
    author: Go Team
    url: https://go.dev/doc/tutorial/handle-errors
    type: official_docs
    published: 2024-01-01
    reliability: authoritative
  - id: src8
    title: "Migrating Java Microservices to Go: A Comprehensive Guide"
    author: DZone
    url: https://dzone.com/articles/migrating-java-microservices-to-go-guide
    type: technical_blog
    published: 2025-01-15
    reliability: high
  - id: src9
    title: "Go 1.26 Release Notes"
    author: Go Team
    url: https://go.dev/doc/go1.26
    type: official_docs
    published: 2026-02-01
    reliability: authoritative
  - id: src10
    title: "Container-aware GOMAXPROCS"
    author: Go Team
    url: https://go.dev/blog/container-aware-gomaxprocs
    type: official_docs
    published: 2025-08-12
    reliability: authoritative
---

# How to Migrate a Java Application to Go

## How do I migrate a Java application to Go?

## TL;DR

- **Bottom line**: Migrate service-by-service starting with a low-risk pilot microservice -- replace Java class hierarchies with Go structs + interfaces, swap exceptions for explicit error returns, and use goroutines/channels instead of thread pools. Go produces a single static binary with sub-100ms startup vs. 5-10s for Spring Boot.
- **Key tool/command**: `go mod init myproject && go build -o myservice ./cmd/myservice`
- **Watch out for**: Translating Java inheritance hierarchies into Go -- Go has no classes or inheritance; use composition via struct embedding and implicit interfaces instead.
- **Works with**: Go 1.22+ (1.26 current as of May 2026, 1.25 supported), Java 11-25 (source), any build system (Maven/Gradle source, go modules target).

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- Go has no exceptions -- all error handling must use explicit return values; never use panic/recover for control flow.
- Go does not support class inheritance -- use struct embedding for composition and implicit interfaces for polymorphism; do not attempt to replicate Java's `extends` keyword.
- Never introduce circular package dependencies -- Go's compiler rejects them. Restructure using interfaces at the consumer side or flatten the package layout.
- Always propagate `context.Context` as the first parameter in functions that perform I/O -- Go has no implicit request-scoped state like Spring's `RequestContextHolder`.
- Go maps must be initialized before write (`make(map[K]V)` or literal syntax) -- `var m map[K]V` creates a nil map that panics on assignment.
- Run `go vet ./...` and `go test -race ./...` before every deployment -- Go's static analysis and race detector catch concurrency bugs that Java's type system does not prevent.

## Quick Reference

| Java Pattern | Go Equivalent | Example |
|---|---|---|
| `class Foo extends Bar` | Struct embedding | `type Foo struct { Bar }` |
| `class Foo implements Iface` | Implicit interface | Just implement the methods -- no `implements` keyword |
| `try { } catch (Exception e) { }` | Multiple return values | `val, err := doSomething(); if err != nil { return err }` |
| `@Autowired` / DI container | Constructor injection via params | `func NewService(repo Repo) *Service { return &Service{repo: repo} }` |
| `Thread` / `ExecutorService` | Goroutines + channels | `go process(item); results <- output` |
| `synchronized` / `ReentrantLock` | `sync.Mutex` or channels | `mu.Lock(); defer mu.Unlock()` |
| `Optional<T>` | Pointer or comma-ok idiom | `val, ok := m[key]` or `*T` (nil = absent) |
| `Stream.map().filter().collect()` | For loops with slices | `for _, v := range items { if v > 0 { out = append(out, v) } }` |
| `HashMap<K, V>` | Built-in `map[K]V` | `m := map[string]int{"a": 1}` |
| `ArrayList<T>` | Slice `[]T` | `s := []int{1, 2, 3}; s = append(s, 4)` |
| `interface Foo { void bar(); }` | `type Foo interface { Bar() }` | Uppercase = exported; no access modifiers |
| `public` / `private` / `protected` | Capitalization | `Exported` (public) vs `unexported` (package-private) |
| `@Override` annotation | No equivalent needed | Implicit interface satisfaction -- compiler checks at use site |
| `try-with-resources` / `finally` | `defer` | `defer file.Close()` -- runs when function returns |
| `package com.foo.bar` (deep nesting) | Flat packages | `package bar` -- one level, short names |

## Decision Tree

```
START
+-- Is this a monolithic Java application?
|   +-- YES --> Break into service boundaries first, then migrate one service at a time
|   +-- NO (already microservices) v
+-- Does the service have heavy Java framework dependencies (Spring, Hibernate)?
|   +-- YES --> Map framework features to Go stdlib + lightweight libraries (see Quick Reference)
|   +-- NO v
+-- Is the service CPU-bound or I/O-bound?
|   +-- CPU-BOUND --> Go excels here -- goroutines + GOMAXPROCS for parallelism
|   +-- I/O-BOUND --> Use goroutines + channels for concurrent I/O (replaces CompletableFuture)
+-- Does the service use complex ORM patterns (JPA/Hibernate)?
|   +-- YES --> Replace with sqlx or pgx + raw SQL (Go favors explicit queries over magic ORM)
|   +-- NO v
+-- Does the team have Go experience?
|   +-- NO --> Start with a non-critical service as a pilot; invest 2-4 weeks in Go training first [src8]
|   +-- YES v
+-- DEFAULT --> Rewrite service in idiomatic Go: structs for data, interfaces for behavior, error returns for control flow
```

## Decision Logic

Structured if/then rules an agent can apply once it has the user's answers to `inputs_needed`. Each rule resolves to a concrete recommendation.

### If the Java app is a single large monolith with shared in-process state
--> Do not lift-and-shift to one Go binary. First carve service boundaries inside the monolith, then migrate the lowest-coupling service to Go as a pilot. [src5, src8]

### If the codebase depends heavily on Spring Boot + JPA/Hibernate + Spring Security
--> Reconsider the migration ROI: map `@RestController` to `net/http` (Go 1.22+ routing), JPA to `pgx`/`sqlx` with hand-written SQL, and Spring DI to constructor injection — but budget for losing the ORM abstraction layer entirely. [src2, src3]

### If concurrency performance was the primary motivation and the workload is I/O-bound
--> Re-evaluate first: Java 25 virtual threads close most of the goroutine gap for I/O-bound work, so the infra savings may not justify a rewrite. Migrate only if startup time, memory footprint, or single-binary deployment also matter. [src4, src8]

### If the team has zero production Go experience
--> Run a 2-4 week Go training spike and migrate one non-critical service before committing to the full path; rushing leads to "Java-in-Go" (getter/setter structs, interface-for-everything, panic-as-exception). [src5, src8]

### If the service runs in a CPU-limited container (Kubernetes requests/limits)
--> Target Go 1.25+ so GOMAXPROCS auto-respects the Cgroup CPU quota; on older Go, add `go.uber.org/automaxprocs` or you will hit kernel CPU throttling and tail-latency spikes. [src10]

### If the goal is the smallest possible image and fastest cold start (serverless, scale-to-zero)
--> Go is the right target: build with `CGO_ENABLED=0` into a `FROM scratch` image (~10-20MB, sub-100ms start) vs. ~200-400MB JVM images with multi-second startup. [src2, src5]

### If the app relies on JVM-only ecosystem libraries (Kafka Streams, Spark, bytecode/reflection frameworks)
--> Keep those components on the JVM and migrate only the stateless request-handling services; Go has no equivalent runtime reflection or bytecode-manipulation power. [src1, src4]

## Step-by-Step Guide

### 1. Initialize Go module and project structure

Create the Go project with a standard layout. Go projects use a flat, simple directory structure compared to Java's deep package hierarchy. [src1]

```bash
mkdir myservice && cd myservice
go mod init github.com/yourorg/myservice

# Standard Go project layout
mkdir -p cmd/myservice internal/handler internal/service internal/repository
```

```
myservice/
  cmd/myservice/main.go    # Entrypoint (like public static void main)
  internal/handler/         # HTTP handlers (like @RestController)
  internal/service/         # Business logic (like @Service)
  internal/repository/      # Data access (like @Repository)
  go.mod                    # Dependencies (like pom.xml / build.gradle)
```

**Verify**: `go mod tidy` runs without errors.

### 2. Convert Java classes to Go structs and interfaces

Replace class hierarchies with composition. Define small, focused interfaces (1-3 methods) and let structs satisfy them implicitly. [src1, src3]

```go
// Java: public interface UserRepository { User findById(long id); }
// Go equivalent:
type UserRepository interface {
    FindByID(ctx context.Context, id int64) (*User, error)
}

// Java: public class User { private long id; private String name; }
// Go equivalent:
type User struct {
    ID   int64  `json:"id" db:"id"`
    Name string `json:"name" db:"name"`
}

// Java: public class PostgresUserRepo implements UserRepository { ... }
// Go equivalent (implicit interface satisfaction):
type PostgresUserRepo struct {
    db *sql.DB
}

func (r *PostgresUserRepo) FindByID(ctx context.Context, id int64) (*User, error) {
    var u User
    err := r.db.QueryRowContext(ctx, "SELECT id, name FROM users WHERE id = $1", id).
        Scan(&u.ID, &u.Name)
    if err != nil {
        return nil, fmt.Errorf("find user %d: %w", id, err)
    }
    return &u, nil
}
```

**Verify**: `go vet ./...` passes with no issues.

### 3. Replace exception handling with error returns

Convert try/catch blocks to Go's explicit error checking pattern. Wrap errors with context using `fmt.Errorf("context: %w", err)` for stack-trace-like debugging. [src1, src7]

```go
// Java:
// try {
//     User user = repo.findById(id);
//     if (user == null) throw new NotFoundException("user not found");
//     return userToDTO(user);
// } catch (SQLException e) {
//     throw new ServiceException("db error", e);
// }

// Go equivalent:
func (s *UserService) GetUser(ctx context.Context, id int64) (*UserDTO, error) {
    user, err := s.repo.FindByID(ctx, id)
    if err != nil {
        if errors.Is(err, sql.ErrNoRows) {
            return nil, fmt.Errorf("user %d not found: %w", id, ErrNotFound)
        }
        return nil, fmt.Errorf("get user %d: %w", id, err)
    }
    return toDTO(user), nil
}

// Define sentinel errors (like custom exception classes)
var ErrNotFound = errors.New("not found")
var ErrForbidden = errors.New("forbidden")
```

**Verify**: `go build ./...` compiles; error paths are tested with `if errors.Is(err, ErrNotFound)`.

### 4. Replace Spring DI with constructor injection

Go does not use annotation-based DI containers. Instead, wire dependencies explicitly through constructor functions. This eliminates runtime reflection and makes the dependency graph visible in `main()`. [src2, src3]

```go
// Java Spring:
// @Service
// public class UserService {
//     @Autowired private UserRepository repo;
//     @Autowired private EmailService email;
// }

// Go equivalent: explicit constructor injection
type UserService struct {
    repo  UserRepository  // interface, not concrete type
    email EmailSender     // interface for testability
}

func NewUserService(repo UserRepository, email EmailSender) *UserService {
    return &UserService{repo: repo, email: email}
}

// Wire everything in main() -- this IS your DI container
func main() {
    db := connectDB()
    repo := repository.NewPostgresUserRepo(db)
    emailer := email.NewSMTPSender(smtpConfig)
    userSvc := service.NewUserService(repo, emailer)
    handler := handler.NewUserHandler(userSvc)

    mux := http.NewServeMux()
    mux.HandleFunc("GET /users/{id}", handler.GetUser)
    log.Fatal(http.ListenAndServe(":8080", mux))
}
```

**Verify**: Application starts and all dependencies are injected. `go run ./cmd/myservice` starts the server.

### 5. Convert Java thread pools to goroutines and channels

Replace `ExecutorService`, `CompletableFuture`, and `synchronized` blocks with goroutines and channels. [src1, src4]

```go
// Java:
// ExecutorService pool = Executors.newFixedThreadPool(10);
// List<Future<Result>> futures = tasks.stream()
//     .map(t -> pool.submit(() -> process(t)))
//     .collect(Collectors.toList());

// Go equivalent: goroutines with bounded concurrency
func processAll(ctx context.Context, tasks []Task) ([]Result, error) {
    results := make([]Result, len(tasks))
    errCh := make(chan error, 1)
    sem := make(chan struct{}, 10) // max 10 concurrent goroutines

    var wg sync.WaitGroup
    for i, task := range tasks {
        wg.Add(1)
        go func(i int, t Task) {
            defer wg.Done()
            sem <- struct{}{}        // acquire semaphore
            defer func() { <-sem }() // release semaphore

            result, err := process(ctx, t)
            if err != nil {
                select {
                case errCh <- err:
                default:
                }
                return
            }
            results[i] = result
        }(i, task)
    }

    wg.Wait()
    close(errCh)
    if err := <-errCh; err != nil {
        return nil, err
    }
    return results, nil
}
```

**Verify**: `go test -race ./...` passes -- the race detector catches concurrent access bugs.

### 6. Set up HTTP server and middleware

Replace Spring Boot's embedded Tomcat with Go's `net/http` stdlib or a lightweight router (Chi, Echo). Middleware replaces Spring interceptors and filters. Go 1.22+ includes built-in pattern routing with path parameters, eliminating the need for third-party routers in many cases. [src1, src2]

```go
// Java Spring: @RestController + @GetMapping
// Go equivalent using stdlib (Go 1.22+ pattern routing):
func (h *UserHandler) GetUser(w http.ResponseWriter, r *http.Request) {
    id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
    if err != nil {
        http.Error(w, "invalid id", http.StatusBadRequest)
        return
    }

    user, err := h.svc.GetUser(r.Context(), id)
    if err != nil {
        if errors.Is(err, service.ErrNotFound) {
            http.Error(w, "not found", http.StatusNotFound)
            return
        }
        http.Error(w, "internal error", http.StatusInternalServerError)
        return
    }

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(user)
}

// Middleware (replaces Spring Filter/Interceptor)
func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        next.ServeHTTP(w, r)
        log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
    })
}
```

**Verify**: `curl http://localhost:8080/users/1` returns JSON. Server startup is <100ms.

### 7. Build and deploy the Go binary

Go compiles to a single static binary with no runtime dependencies. This replaces the JVM + JAR deployment model. Go 1.24+ automatically embeds version info from VCS tags into the binary. [src2, src5]

```bash
# Build for production (like mvn package -DskipTests)
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o myservice ./cmd/myservice

# Multi-stage Dockerfile (replaces JDK base image)
# FROM golang:1.24-alpine AS builder
# WORKDIR /app
# COPY go.mod go.sum ./
# RUN go mod download
# COPY . .
# RUN CGO_ENABLED=0 go build -o /myservice ./cmd/myservice
#
# FROM scratch               # <-- No OS, no runtime. ~10MB vs ~200MB+ for JVM
# COPY --from=builder /myservice /myservice
# ENTRYPOINT ["/myservice"]

# Compare image sizes:
# Java (Eclipse Temurin): ~200-400MB
# Go (from scratch):     ~10-20MB
```

**Verify**: `./myservice` runs without JVM installed. Docker image is <20MB. Startup is <100ms.

## Code Examples

### Go: HTTP service with repository pattern (replaces Spring Boot REST API)

> Full script: [go-http-service-with-repository-pattern-replaces-s.go](scripts/go-http-service-with-repository-pattern-replaces-s.go) (94 lines)

```go
// Input:  A Java Spring Boot @RestController + @Service + @Repository
// Output: Equivalent Go service with same API contract
package main
import (
    "context"
# ... (see full script)
```

### Go: Concurrent worker pool (replaces Java ExecutorService)

> Full script: [go-concurrent-worker-pool-replaces-java-executorse.go](scripts/go-concurrent-worker-pool-replaces-java-executorse.go) (67 lines)

```go
// Input:  Java ExecutorService with Callable tasks and Future results
// Output: Go worker pool with goroutines and channels
package main
import (
    "context"
# ... (see full script)
```

### Go: Interface-based testing (replaces Mockito)

> Full script: [go-interface-based-testing-replaces-mockito.go](scripts/go-interface-based-testing-replaces-mockito.go) (54 lines)

```go
// Input:  Java unit test with Mockito mocks
// Output: Go test using interface-based test doubles (no framework needed)
package service
import (
    "context"
# ... (see full script)
```

## Anti-Patterns

### Wrong: Translating Java class hierarchy to Go with embedding as inheritance

```go
// BAD -- treating embedding as inheritance (it's not)
type Animal struct {
    Name string
}
func (a *Animal) Speak() string { return "..." }

type Dog struct {
    Animal // This is NOT inheritance
}
func (d *Dog) Speak() string { return "Woof" }

// Bug: Animal.Speak() is still accessible and returns "..."
// No polymorphism: []Animal cannot hold Dog values
```

### Correct: Use interfaces for polymorphism

```go
// GOOD -- interfaces for polymorphic behavior
type Speaker interface {
    Speak() string
}

type Dog struct{ Name string }
func (d Dog) Speak() string { return "Woof" }

type Cat struct{ Name string }
func (c Cat) Speak() string { return "Meow" }

// Polymorphism via interface
func greetAll(speakers []Speaker) {
    for _, s := range speakers {
        fmt.Println(s.Speak())
    }
}
```

### Wrong: Using panic/recover as try/catch

```go
// BAD -- using panic for control flow (Java exception habit)
func findUser(id int64) *User {
    user, err := db.FindByID(id)
    if err != nil {
        panic(fmt.Sprintf("user not found: %d", id)) // Don't do this
    }
    return user
}

func handler(w http.ResponseWriter, r *http.Request) {
    defer func() {
        if r := recover(); r != nil {
            http.Error(w, "internal error", 500) // Catching panics like exceptions
        }
    }()
    user := findUser(42)
    json.NewEncoder(w).Encode(user)
}
```

### Correct: Return errors explicitly

```go
// GOOD -- explicit error returns (idiomatic Go)
func findUser(ctx context.Context, id int64) (*User, error) {
    user, err := db.FindByID(ctx, id)
    if err != nil {
        return nil, fmt.Errorf("find user %d: %w", id, err)
    }
    return user, nil
}

func handler(w http.ResponseWriter, r *http.Request) {
    user, err := findUser(r.Context(), 42)
    if err != nil {
        if errors.Is(err, ErrNotFound) {
            http.Error(w, "not found", 404)
            return
        }
        http.Error(w, "internal error", 500)
        return
    }
    json.NewEncoder(w).Encode(user)
}
```

### Wrong: Creating Java-style getter/setter methods

```go
// BAD -- Java-style boilerplate (not idiomatic Go)
type User struct {
    name  string
    email string
}

func (u *User) GetName() string     { return u.name }
func (u *User) SetName(n string)    { u.name = n }
func (u *User) GetEmail() string    { return u.email }
func (u *User) SetEmail(e string)   { u.email = e }
```

### Correct: Use exported fields directly

```go
// GOOD -- exported fields, no getters/setters needed
type User struct {
    Name  string `json:"name"`
    Email string `json:"email"`
}

// Only add methods when validation or side effects are needed:
func (u *User) SetEmail(email string) error {
    if !strings.Contains(email, "@") {
        return errors.New("invalid email")
    }
    u.Email = email
    return nil
}
```

### Wrong: Over-using interfaces (Java interface-for-everything habit)

```go
// BAD -- interface for a single concrete implementation
type UserServiceInterface interface {
    GetUser(ctx context.Context, id int64) (*User, error)
    CreateUser(ctx context.Context, u *User) error
    DeleteUser(ctx context.Context, id int64) error
}

type UserServiceImpl struct { /* fields */ }
// Unnecessary abstraction when only one implementation exists
```

### Correct: Define interfaces at the consumer, not the provider

```go
// GOOD -- define interfaces where they are used (consumer side)
// In the handler package:
type UserGetter interface {
    GetUser(ctx context.Context, id int64) (*User, error)
}

// The handler only depends on the methods it actually calls
type UserHandler struct {
    users UserGetter // Narrow interface, easy to test
}

// The concrete service satisfies this interface implicitly
// No need for a separate "UserServiceInterface"
```

### Wrong: Ignoring context.Context (Java has no equivalent)

```go
// BAD -- no context propagation (Java habit of ignoring cancellation)
func fetchData(url string) ([]byte, error) {
    resp, err := http.Get(url) // No timeout, no cancellation
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    return io.ReadAll(resp.Body)
}
```

### Correct: Thread context through all function calls

```go
// GOOD -- context for cancellation, timeouts, and request-scoped values
func fetchData(ctx context.Context, url string) ([]byte, error) {
    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        return nil, err
    }
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, fmt.Errorf("fetch %s: %w", url, err)
    }
    defer resp.Body.Close()
    return io.ReadAll(resp.Body)
}

// Caller sets timeout:
// ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
// defer cancel()
// data, err := fetchData(ctx, "https://api.example.com/data")
```

## Common Pitfalls

- **Nil pointer panics from uninitialized maps**: In Java, `new HashMap<>()` is safe. In Go, `var m map[string]int` creates a nil map that panics on write. Fix: Always initialize maps: `m := make(map[string]int)` or `m := map[string]int{}`. [src4]
- **Goroutine leaks from missing channel consumers**: Launching goroutines that write to unbuffered channels with no reader causes them to hang forever. Fix: Always ensure channels are read from, or use buffered channels. Use `context.Context` for cancellation. [src1]
- **Circular import errors from Java-style package structure**: Java allows circular package dependencies; Go does not. Fix: Restructure into a flat package layout or use interfaces to break cycles. [src3]
- **Ignoring `go vet` and `staticcheck` warnings**: Java developers rely on IDE warnings. Go has powerful static analysis tools that catch real bugs. Fix: Run `go vet ./...` and `staticcheck ./...` in CI. [src1]
- **Using `init()` functions for complex setup**: Java developers try to replicate `@PostConstruct` with Go's `init()`. This creates hidden dependencies and makes testing hard. Fix: Use explicit initialization in `main()` and pass dependencies through constructors. [src3]
- **Assuming Go generics work like Java's**: Go added generics in 1.18, but they are simpler. No wildcards (`? extends T`), no type erasure. Go 1.24 adds generic type aliases; Go 1.25 removes the core types concept. Fix: Use generics sparingly for data structures; prefer interfaces for behavioral abstraction. [src4]
- **Forgetting to close resources without defer**: Java's try-with-resources auto-closes. In Go, you must explicitly call `Close()`. Fix: Use `defer resource.Close()` immediately after successful open. Check the error from `Close()` on write operations. [src7]
- **Not using `context.Context` in APIs**: Java has no direct equivalent; Go passes context explicitly as the first parameter of every function that does I/O. Fix: Accept `ctx context.Context` as the first parameter in all service and repository methods. [src1]
- **Deploying Go in containers without adjusting GOMAXPROCS**: Before Go 1.25, the Go runtime ignored Cgroup CPU limits, setting GOMAXPROCS to the host's CPU count even in constrained containers — causing kernel CPU throttling (~100ms stalls) and tail-latency spikes. Fix: Upgrade to Go 1.25+ which sets `GOMAXPROCS = min(CPU_limit, cores)` and re-checks periodically; on older Go use `go.uber.org/automaxprocs`. [src8, src10]

## Diagnostic Commands

```bash
# Initialize a new Go project
go mod init github.com/yourorg/myservice

# Download dependencies (like mvn dependency:resolve)
go mod tidy

# Build and check for compilation errors
go build ./...

# Run all tests with race detector enabled
go test -race -v ./...

# Run static analysis (catches bugs go build misses)
go vet ./...

# Check test coverage (like JaCoCo)
go test -coverprofile=coverage.out ./... && go tool cover -html=coverage.out

# Format all code (like google-java-format but enforced)
gofmt -w .

# Profile CPU/memory (like JFR/VisualVM)
go test -bench=. -cpuprofile=cpu.out -memprofile=mem.out ./...
go tool pprof cpu.out

# Cross-compile for Linux (no JVM needed on target)
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o myservice ./cmd/myservice

# Track tool dependencies in go.mod (Go 1.24+)
go get -tool golang.org/x/tools/cmd/stringer
```

## Version History & Compatibility

| Version | Status | Breaking Changes | Migration Notes |
|---|---|---|---|
| Go 1.26 (2026-02) | Current | Green Tea GC default, `new()` accepts expressions, `errors.AsType` generic, `cmd/doc` removed, stricter `net/url` parsing | 10-40% GC overhead reduction (default); `go mod init` writes `go 1.25.0`; `go fix` is now a modernizer with source-level inliner |
| Go 1.25 (2025-08) | Supported | Container-aware GOMAXPROCS (respects Cgroup CPU limits), `testing/synctest`, experimental Green Tea GC | Drop `go.uber.org/automaxprocs` — the runtime now sets `GOMAXPROCS = min(CPU_limit, cores)` and re-checks periodically |
| Go 1.24 (2025-02) | Supported | Generic type aliases, Swiss Table maps, tool directives in go.mod, FIPS 140-3 crypto | 15-25% GC pause improvement; use `go get -tool` for tool deps |
| Go 1.23 (2024) | Supported | Iterator support (range-over-func), enhanced `net/http` | Use range-over-func for custom iterators |
| Go 1.22 (2024) | Supported | Enhanced `net/http` routing with path params | `mux.HandleFunc("GET /users/{id}", handler)` replaces Chi/Gorilla for simple routing |
| Go 1.21 (2023) | Supported | `log/slog` structured logging, `slices`/`maps` packages | Replace third-party logging (logrus, zap) for simple cases |
| Go 1.18 (2022) | Maintenance | Generics (`type constraints`), fuzzing | Use generics for data structures, not behavioral interfaces |
| Java 25 (2025) | LTS (source) | Virtual threads finalized, structured concurrency redesigned, scoped values | Virtual threads narrow Go's concurrency advantage for I/O-bound work; synchronized no longer pins carrier threads |
| Java 21 (2023) | LTS (source) | Virtual threads (preview finalized), pattern matching, record patterns | Virtual threads reduce Go's concurrency advantage for I/O-bound work |
| Java 17 (2021) | LTS (source) | Sealed classes, records | Records map well to Go structs |

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Microservices with high concurrency needs | Heavy enterprise integration (ESB, JMS, BPMN) | Stay with Java + Spring Boot |
| You need fast startup and low memory (serverless, K8s) | Team has deep Java/Spring expertise, no Go experience | Invest in Java optimization (GraalVM native) |
| Building CLI tools, infrastructure, or DevOps tooling | Complex ORM-heavy CRUD applications | Java + JPA/Hibernate or consider Kotlin |
| You want single-binary deployment with no runtime deps | Project depends on JVM-only libraries (Kafka Streams, Spark) | Keep those components in Java |
| Team is willing to learn Go idioms (not just syntax) | Tight deadline -- rewriting under pressure leads to Java-in-Go code | Incremental migration or stay |
| Containerized microservices where image size matters (~10MB vs ~300MB) | Application heavily uses Java reflection, bytecode manipulation, or annotation processing | Stay with JVM; Go has no equivalent runtime reflection power |

## Important Caveats

- Go has no exceptions -- all error handling is explicit via return values. This increases verbosity but makes error paths visible. Do not fight this pattern; embrace `if err != nil`.
- Go interfaces are satisfied implicitly. You never write `implements`. This is powerful but can be confusing for Java developers who expect explicit contracts.
- Go has no method overloading and no default parameter values. Use functional options pattern (`WithTimeout(5*time.Second)`) or config structs instead.
- Go modules replaced GOPATH in 2019. Always use `go mod init` for new projects. Never set GOPATH for new development.
- Memory management differs: Go has a garbage collector but no JVM tuning knobs. If you need predictable latency, profile with `pprof` and reduce allocations.
- Java's `null` maps roughly to Go's nil, but Go's zero values are type-specific (0 for int, "" for string, nil for pointers/slices/maps). A nil slice is valid and usable (length 0), but a nil map panics on write.
- Java 25's virtual threads and structured concurrency significantly narrow Go's concurrency advantage for I/O-bound workloads. Evaluate whether the migration is still justified if concurrency performance was the primary motivation. [src4, src8]
- Go 1.24's Swiss Table maps and Go 1.26's Green Tea garbage collector (default, 10-40% GC overhead reduction) keep Go competitive for latency-sensitive services. [src9]
- Container deployments: Go 1.25+ makes GOMAXPROCS container-aware by default, so a Go service no longer over-schedules threads against a Cgroup CPU limit. On Go <1.25 you must add `go.uber.org/automaxprocs`. [src10]
- The Dockerfile in step 7 references `golang:1.24-alpine`; bump the builder image to the current `golang:1.26-alpine` (or your pinned minor) to inherit the default Green Tea GC and container-aware GOMAXPROCS.

## Related Units

- [Monolith to Microservices](/software/migrations/monolith-to-microservices/2026)
- [Error Handling Strategies](/software/patterns/error-handling-strategies/2026)
- [Concurrency and Parallelism Patterns](/software/patterns/concurrency-parallelism/2026)
- [Dependency Injection](/software/patterns/dependency-injection/2026)
- [Java Spring to Kotlin Migration](/software/migrations/java-spring-to-kotlin/2026)
- [PHP to Go Migration](/software/migrations/php-to-go/2026)
