Error handling and defer in Go (Concurrency and synchronization) | Patterns, idioms, and best practices in Go

Error handling in Go is significantly different from the familiar Java approach with exceptions. Instead of try/catch, Go uses returning an error as a separate value, and `defer` helps safely release resources regardless of whether an error occurred or not.

1. Error handling idioms

Error handling idioms - Explicit error checking ⚠️. errors are returned and checked immediately after the call

In Go, it is customary to return an error as the second value of a function and check it immediately. This is a simple but powerful idiom.


// Go: error checking
func readFile(path string) ([]byte, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, err // return error up
    }
    return data, nil
}

func main() {
    content, err := readFile("file.txt")
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    fmt.Println(string(content))
}

// Java: error checking via exception
try {
    byte[] content = Files.readAllBytes(Paths.get("file.txt"));
    System.out.println(new String(content));
} catch (IOException e) {
    System.out.println("Error: " + e.getMessage());
}
In Go, error handling is explicit and local. This allows you to immediately see where a problem may arise, without hidden exceptions.

2. defer for cleanup

Error handling idioms - Explicit error checking ⚠️. errors are returned and checked immediately after the call

`defer` allows postponing the execution of a function until exiting the current function. Very convenient for releasing resources: closing files, connections, mutexes, etc.


// Go: defer for closing a file
func processFile(path string) error {
    file, err := os.Open(path)
    if err != nil {
        return err
    }
    defer file.Close() // guaranteed closure on any exit from the function

    // reading the file
    content, err := io.ReadAll(file)
    if err != nil {
        return err
    }
    fmt.Println(string(content))
    return nil
}

// Java: try-with-resources
try (FileInputStream file = new FileInputStream("file.txt")) {
    byte[] content = file.readAllBytes();
    System.out.println(new String(content));
} catch (IOException e) {
    System.out.println("Error: " + e.getMessage());
}
defer guarantees that resources will be released even if an error occurs in the function. This makes the code cleaner and safer.

3. Common Patterns and Tips

  • Check for errors immediately after calling functions: if err != nil.
  • Use defer to release any resources: files, connections, locks.
  • You can defer multiple calls — they execute in reverse order (stack).
  • Don't use panic for ordinary errors — only for truly critical situations.
  • For Java developers: defer ≈ try-with-resources, explicit error checking ≈ catch/throw, but without exceptions.
The combination of explicit error checking and defer allows writing reliable code that is safe in a multithreaded or parallel context.

Output

In Go, error handling and resource management via defer form the basis of safe and stable code. Unlike Java, where errors are handled through exceptions and try-with-resources, Go emphasizes clarity: each function returns an error that must be handled, and resources are released automatically via defer.

The main advantages of this approach:

  • Explicit error checking makes it easier to understand the program flow and reduces the chance of missing a critical situation.
  • defer guarantees resource release even in the case of errors or early exit from the function.
  • Fewer hidden effects compared to exceptions — the code is predictable and readable.
  • Suitable for parallel and concurrent code, where errors should be handled locally, and resources should close regardless of the order of completion of goroutines.

For a Java developer, this is useful as a contrast: Go requires more discipline in error checking, but in return offers a simple, reliable, and straightforward model of resource management and error handling.

Comparison of Go vs Java on Error Handling and defer

Concept Go Java Comment
Error handling Return error as value (error), explicit check if err != nil Exceptions try/catch, automatic propagation up the stack Go makes errors visible locally; Java hides errors until catch
Resource release defer executes on function exit, guaranteed closing of files, connections, mutex try-with-resources or finally, more verbose In Go, release is tied to function, easier to read and prevents resource leaks
Error handling in concurrent code Errors are local, each goroutine handles its own errors Exceptions can "bubble up" between threads, requires separate handling Go simplifies error management in parallel threads
Code complexity Explicit, linear, fewer hidden effects Hidden effects through exceptions, requires try/catch/finally, sometimes harder to read Go is a predictable flow, easier to analyze and test
Advantages Simplicity, reliability, safety for concurrency Automatic resource management through try-with-resources, familiar to Java developers Go is suitable for production code with high concurrency, fewer hidden pitfalls

🌐 На русском
Total Likes:0

Оставить комментарий

My social media channel
By sending an email, you agree to the terms of the privacy policy

Useful Articles:

Memory / Runtime / Allocator - Go vs Java
Memory management, pointers, and profiling are fundamental aspects of efficient code. Let s consider three key concepts: slice backing array, pointer, and profiling (pprof / trace), and compare Go wit...
Error handling and defer in Go (Concurrency and synchronization) | Patterns, idioms, and best practices in Go
Error handling in Go is significantly different from the familiar Java approach with exceptions. Instead of try/catch, Go uses returning an error as a separate value, and `defer` helps safely release ...
Loops in Java: for, while, do while, continue and break statements
Hello! This is Vitaly Lesnykh. Today we will continue the course “Java Basics for Beginners” and discuss one of the most important topics in programming — loops. A loop is the repetition of code exe...

New Articles:

Zero Allocation in Java: what it is and why it matters
Zero Allocation — is an approach to writing code in which no unnecessary objects are created in heap memory during runtime. The main idea: fewer objects → less GC → higher stability and performance. ...
Stream vs For in Java: how to write the fastest code possible
In Java, performance is often determined not by the "beauty of the code," but by how it interacts with memory, the JIT compiler, and CPU cache. Let s analyze why the usual for is often faster than Str...
Compiler, Build, and Tooling in Go and Java: how assembly, initialization, analysis, and diagnostics are organized in two ecosystems
This article is dedicated to a general overview of how the compiler, build, and tooling practices are arranged in Go, and how to better understand them through comparison with Java. We will not delve ...
Fullscreen image