Golang new error: errors.New, fmt.Errorf, and custom errors

Golang new error and golang create error with errors.New and fmt.Errorf; errors new golang patterns, sentinels, errors.Is, %w wrapping, and custom types implementing error.

Published

Updated

Read time 4 min read

Reviewed byDeepak Prasad

Golang new error: errors.New, fmt.Errorf, and custom errors

To create a new error in Go with a fixed text message, use errors.New from the standard library errors package—the same API people look up as golang new error, golang errors.new, errors.new golang, or go errors new. When the message should include formatted values, use fmt.Errorf; when you need one stable value to compare with errors.Is, declare a package-level sentinel such as var ErrFoo = errors.New("...") and return that variable. For returning and handling errors in callers, see returning errors in Go.

Tested with Go 1.24 on Linux.


Create a golang new error with errors.New

func New(text string) error returns an error whose Error() string is exactly text. It is the usual answer when someone searches golang error new or new error golang and wants a fixed message with no formatting.

go
package main

import (
	"errors"
	"fmt"
)

func main() {
	err := errors.New("this is a new error")
	if err != nil {
		fmt.Println(err)
	}
}
Output

You should see the message printed on one line (the same string you passed to errors.New).

Return a golang new error from your own function

Validate input and return nil when there is no failure:

go
package main

import (
	"errors"
	"fmt"
	"strings"
)

func checkStartsWithS(str string) error {
	if !strings.HasPrefix(str, "S") && !strings.HasPrefix(str, "s") {
		return errors.New("invalid string: must start with S")
	}
	return nil
}

func main() {
	for _, s := range []string{"Hello", "Sorry"} {
		if err := checkStartsWithS(s); err != nil {
			fmt.Println(err)
		} else {
			fmt.Println("valid string")
		}
	}
}
text
invalid string: must start with S
valid string

Each call to errors.New is a different value

For debugging and tests it matters: two separate calls to errors.New with the same string still produce different values, so == is false unless both sides are the exact same variable.

go
package main

import (
	"errors"
	"fmt"
)

func main() {
	a := errors.New("x")
	b := errors.New("x")
	fmt.Println(a == b)
}
text
false

If you need a single shared sentinel error, declare it once at package scope: var ErrInvalid = errors.New("invalid") and return that same variable.


fmt.Errorf for messages with context (and %w wrapping)

fmt.Errorf is how you golang create error messages that include numbers, names, or other values (another common phrasing is golang create new error with context). It still returns a value of type error.

go
package main

import "fmt"

func main() {
	const name, id = "Daniel", 17
	ids := map[int]string{1: "Anna", 2: "Bob", 5: "Harry", 7: "Chris", 8: "Cody", 18: "Ron"}
	if _, ok := ids[id]; !ok {
		err := fmt.Errorf("user %q (id %d) not found", name, id)
		fmt.Println(err.Error())
	}
}
Output

You should see user "Daniel" (id 17) not found.

When you wrap another error, use %w so errors.Is and errors.Unwrap work:

go
package main

import (
	"errors"
	"fmt"
)

var ErrNotFound = errors.New("not found")

func lookup(id int) error {
	if id != 1 {
		return fmt.Errorf("user %d: %w", id, ErrNotFound)
	}
	return nil
}

func main() {
	err := lookup(2)
	fmt.Println(errors.Is(err, ErrNotFound))
}
text
true

Custom types and the built-in error interface

Go’s built-in error interface is only Error() string. Any named type with that method satisfies error; you do not redeclare the interface in your package (doing so shadows the name error and confuses readers).

A small custom type can carry extra data for logging or errors.As:

go
package main

import (
	"errors"
	"fmt"
)

type NegativeInputError struct {
	Value int
}

func (e NegativeInputError) Error() string {
	return fmt.Sprintf("negative input: %d", e.Value)
}

func sqrtNonNeg(x int) (float64, error) {
	if x < 0 {
		return 0, NegativeInputError{Value: x}
	}
	return float64(x), nil
}

func main() {
	_, err := sqrtNonNeg(-3)
	var neg NegativeInputError
	if errors.As(err, &neg) {
		fmt.Println("caught", neg.Value)
	}
}

For more background on interfaces in general, see interfaces in Go.


Summary

Creating errors in Go starts with errors.New for a plain fixed string, package-level var Err = errors.New(...) when you need a stable sentinel, and fmt.Errorf when the message should include formatted context or wrap another error with %w for errors.Is. Separate calls to errors.New with identical text are still different values for ==. Custom types with an Error method implement the same error interface when you need richer inspection with errors.As.


References


Frequently Asked Questions

1. What is the difference between errors.New and fmt.Errorf in Go?

errors.New takes a plain string and returns an error with that message. fmt.Errorf builds a string from a format (like fmt.Printf) and can wrap another error with the %w verb for errors.Is and errors.Unwrap.

2. Does errors.New with the same text return the same error value?

No. Each call to errors.New returns a distinct error, so == between two errors.New("x") results is false. Use a package-level var ErrFoo = errors.New(...) if you need a stable sentinel.

3. When should I use %w in fmt.Errorf?

Use %w when the new message should wrap an underlying error so callers can use errors.Is and errors.As. Use %v if you only want the text in the message chain without unwrap semantics.
Tuan Nguyen

Data Scientist

Proficient in Golang, Python, Java, MongoDB, Selenium, Spring Boot, Kubernetes, Scrapy, API development, Docker, Data Scraping, PrimeFaces, Linux, Data Structures, and Data Mining. With expertise …