A linguagem Go permite a criação de erros customizados possam ser definidos pelo usuário de acordo com as necessidades da aplicação. Para tal, precisamos importar o package errors:

Sintaxe
import "errors"

Agora, podemos declarar nossos novo tipo de erro como mostrado no exemplo básico abaixo:

Ex 1:
func nome_erro() error {
  return errors.New("mensagem")
}

Quando utilizamos o método ou função New, criamos um erro em que o único contexto do erro contém uma mensagem de texto.

Sintaxe
func New(text string) error
Ex 2:
func MeuErro() error{
     return errors.New("minha mensagem de erro")
}

Como declaramos nosso erro como funções, o que já foi estudado sobre o assunto é válido para o uso com erros customizados.

Com o básico que foi discutido até agora, um erro customizado pode ser exibido como mostrado no exemplo básico abaixo:

Ex 3:
package main
import "fmt"
import "errors"

//erro customizado
func MeuErro() error{
     return errors.New("minha mensagem de erro")
}

//função que retorna error
func Funcao() (error) {
    return MeuErro()
}

func main() {
    erro := Funcao()
    fmt.Print("Erro:", erro)
}
Saída
Erro: minha mensagem de erro

Podemos utilizar o método Errorf do package fmt para imprimir em uma string os erros ocorridos em cascata ou aninhados. Assim, podemos simular um stacktrace com as mensagens de erros.

Sintaxe
func Errorf(format string, a ...any) error
Ex 4:
package main
import "fmt"
import "errors"

//erro customizado 
func ArgumentoInvalido1() error{
     return errors.New("Argumento inválido1")
}

//erro customizado 
func ArgumentoInvalido2() error{
     return errors.New("Argumento inválido2")
}

//1o erro lançado
func funcao1()(error) {
    return ArgumentoInvalido1() 
}

//2o erro lançado
func funcao2()(error) {
    erro := funcao1();
    return fmt.Errorf("%w\n%w", erro, ArgumentoInvalido2()) //concatenando erros
}

func main() {
    erro :=funcao2();
    fmt.Print("erro:\n",erro);
}
Saída
erro:
Argumento inválido1
Argumento inválido2

Como nossas funções precisam retornar tanto o valor esperado quanto nosso erro customizado, internamente em nossas funções devemos realizar as devidas validações para retorno de dados válidos e retorno de erro de forma adequada.

Sintaxe: erro customizado
func nome_erro() error {

  return errors.New("mensagem erro")

}
Sintaxe: validação de erro
func nome_funcao(<parametros>) (tipo_retorno, error){
   
   if(condicao){

     return resultado_valido, nil  //OK valor válido e nil como erro

   }

  //outras validações

  return valor_retorno, nome_erro() //ERRO valor qualquer e error como nome_erro
}

No exemplo abaixo, criamos o erro ArgumentoInvalido para que possamos exibí-lo quando o argumento de nossa função não é o esperado ou válido, retornando nosso erro customizado.

Ex 5:
package main
import "fmt"
import "errors"

//erro customizado
func ArgumentoInvalido() error{
     return errors.New("Argumento inválido")
}

//função que valida argumento retornando resultado e erro
func Dobro(p int) (int,error) {

    if(p != 0){ //se argumento válido
        return p*2, nil
    }
    return 0, ArgumentoInvalido() //argumentos inválido
}

func main() {
    _,erro := Dobro(0); //_ descarta

    if(erro != nil){ //validação de erro
        fmt.Print("Erro:", erro)
    }
}
Saída
Erro:Argumento inválido

O tipo errors contém o método Is. Com esse método, podemos comparar um erro capturado e nosso erro customizado como uma alternativa ao uso de validação com nil visto acima.

Sintaxe
func Is(err, target error) bool
onde:
err: erro capturado
target: erro cusmtomizado para comparação

No exemplo abaixo, declaramos uma variável ErroArgInvalido como erro global e uma funçao para lançar esse erro. Na main, utilizamos função errors.Is para mostra a verificação do erro customizado.

Ex 5:
package main
import "fmt"
import "errors"

//erro customizado global
var ErroArgInvalido = errors.New("Argumento inválido")
//outros erros

func Dobro(p int) (int,error) {
    if(p != 0){
        return p*2, nil
    }
    return 0, ErroArgInvalido //retorno do erro 
}

func main() {
    _,erro := Dobro(0);

    if(errors.Is(erro, ErroArgInvalido)){ //validação com erros.Is
        fmt.Print("ArgumentoInvalido")
    }else{
        fmt.Print("OK")
    }
}
Saída
ArgumentoInvalido

Em alguns momentos, precisamos adicionar mais dados sobre os nossos erros customizados. Para isso, utilizamos o tipo struct com o qual podemos criar nossos erros mais complexos:

Sintaxe
type nome_erro struct {
   //membros
}

Para a declaração de um novo tipo de erro, precisamos implementar o método Error da interface error que é mostrada abaixo:

Sintaxe
type error interface {
   Error() string
}

Definimos, por exemplo, as informações codigo e msg para armazenar o código do erro e uma mensagem de erro respectivamente.

Ex 6:
func (e MeuErro) Error() string {
   return fmt.Sprintf("codigo: %d msg %w", e.codigo, e.msg)
}

Agora, podemos declarar uma função que irá realizar as validações internas e retornar nosso erro customizado do tipo struct. Abaixo, o código final contendo os trechos de código mostrados nesta parte:

Ex 7:
package main
import "fmt"
//import "errors"

type MeuErro struct{
codigo int
msg string
}

func (e MeuErro) Error() string {
   return fmt.Sprintf("codigo: %d\nmsg: %s", e.codigo, e.msg)
}

func funcao(p int) (int, error) {
   if p < 0 {
       return 0, MeuErro {codigo: 0, msg:"valor < 0 "}
   }
   return p*2, nil
}

func main() {
    _,erro :=funcao(-1);
    fmt.Print("erro:\n",erro.Error());
}
Saída
erro:
codigo: 0
msg: valor < 0

Utilizando ponteiro e referência, podemos retornar a struct que representa nosso erro customizado e acessar suas informações como parte do tratamento para exibição. A função errors.As do package errors é utilizado para validação.

Modificamos o nosso método Error do exemplo anterior para o mostrado abaixo:

Ex 8:
//metodo da interface error
func (e MeuErro) Error() string {
   return e.msg
}

Na função que retorna o nosso erro, adicionamos o retorno de uma referência para a struct de erro:

Ex 9:
//função p/ retorno de erro
func funcao(p int) (int, error) {
   if p < 0 {
       return 0, &MeuErro {codigo: 0, msg:"valor < 0 "} //referência p/ erro
   }
   return p*2, nil
}

Na main, alteramos o tratamento adicionando o método errors.As para validação do nosso erro customizado lançado e exibimos suas informações.

Sintaxe: errors.As
func As(err error, target any) bool
Ex 10:
func main() {
    _,erro :=funcao(-1);
    var mErro *MeuErro; //ponteiro
    
    if errors.As(erro, &mErro) {
       fmt.Printf("msg:%s\ncodigo:%d",mErro.msg,mErro.codigo)
    }
}

Abaixo, o código final contendo os trechos de código mostrados nesta parte:

Ex 11:
package main
import "fmt"
import "errors"

//novo tipo
type MeuErro struct{
codigo int
msg string
}

//método da interface error
func (e MeuErro) Error() string {
   return e.msg
}

//função p/ retorno de erro
func funcao(p int) (int, error) {

   if p < 0 {
       return 0, &MeuErro {codigo: 0, msg:"valor < 0 "}
   }

   return p*2, nil
}

func main() {
    _,erro :=funcao(-1);

    var mErro *MeuErro; //ponteiro
    
    if errors.As(erro, &mErro) { //referência
       fmt.Printf("msg:%s\ncodigo:%d",mErro.msg,mErro.codigo)
    }else{
       fmt.Printf("OK, sem bugs" )
    }
}
Saída
erro:
codigo: 0
msg: valor < 0
  1. 03/07/2026 - revisão 2 - Ajustes: pontuais
  2. 09/09/2025 - revisão 1 - Ajustes: pontuais e em sintaxes
  3. 07/04/2025 - versão inicial