---
title: "GN003 – Always Handle ShouldBindJSON / ShouldBind Errors"
impact: high
impactDescription: "Ignoring ShouldBindJSON errors means malformed or missing fields are silently treated as zero values, causing corrupted data writes and bypassed validation."
tags: [go, gin, validation, correctness]
---

# GN003 – Always Handle `ShouldBindJSON` / `ShouldBind` Errors

## Rule

Every call to `c.ShouldBindJSON`, `c.ShouldBind`, `c.ShouldBindQuery`, or `c.ShouldBindUri` must be followed by an error check. If the error is non-nil, abort immediately with a `400 Bad Request` response.

## Why

`ShouldBind*` populates the target struct but returns an error for malformed JSON, missing required fields (enforced by `binding:"required"` tags), or type mismatches. Ignoring the error means the handler continues with a partially or incorrectly populated struct, silently writing bad data.

## Wrong

```go
func (h *UserHandler) Create(c *gin.Context) {
    var req CreateUserRequest
    c.ShouldBindJSON(&req)   // ❌ error ignored — continues with zero-value struct fields

    user, err := h.service.CreateUser(c.Request.Context(), req)
    // ...
}

// Worse: using BindJSON (panics on error in some versions, logs but doesn't stop)
func (h *UserHandler) Update(c *gin.Context) {
    var req UpdateUserRequest
    c.BindJSON(&req)   // ❌ BindJSON writes 400 header but handler still continues
    h.service.UpdateUser(c.Request.Context(), req)
}
```

## Correct

```go
type CreateUserRequest struct {
    Name  string `json:"name"  binding:"required,min=2,max=100"`
    Email string `json:"email" binding:"required,email"`
    Age   int    `json:"age"   binding:"required,min=18"`
}

func (h *UserHandler) Create(c *gin.Context) {
    var req CreateUserRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
            "error":   "invalid request",
            "details": err.Error(),
        })
        return
    }

    user, err := h.service.CreateUser(c.Request.Context(), req)
    if err != nil {
        c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": "creation failed"})
        return
    }
    c.JSON(http.StatusCreated, user)
}
```

## Notes

- Prefer `ShouldBind*` over `Bind*` — the `Bind*` family writes a `400` header but doesn't abort the handler, making code flow confusing.
- Use struct tags: `binding:"required"`, `binding:"email"`, `binding:"min=1,max=255"` to shift validation into the binding layer.
- For detailed error messages, type-assert the error to `validator.ValidationErrors` and format field names cleanly.
- See GN008 for struct validation tag conventions.
