How It Works Features Pricing Blog
Log In Start Free Trial
Gin · Go

Fix runtime error: invalid memory address or nil pointer dereference in Gin

This error occurs when Go code dereferences a nil pointer, typically by accessing a field or calling a method on a nil value returned from a function. In Gin handlers, this often happens when database queries return nil results or when request binding fails silently. Fix it by checking for nil before dereferencing pointers.

Reading the Stack Trace

2026/04/10 14:22:31 [Recovery] panic recovered: GET /api/users/999 HTTP/1.1 runtime error: invalid memory address or nil pointer dereference /usr/local/go/src/runtime/panic.go:261 (0x44b5c3) gopanic: runtime.gopanic(e) /usr/local/go/src/runtime/signal_unix.go:874 (0x44b593) sigpanic: runtime.sigpanic() /app/handlers/user.go:18 (0x8a2f4d) GetUser: user.Name /app/vendor/github.com/gin-gonic/gin/context.go:174 (0x89d3a1) (*Context).Next: c.handlers[c.index](c) /app/vendor/github.com/gin-gonic/gin/recovery.go:101 (0x89e8c4) CustomRecoveryWithWriter.func1: c.Next() /app/vendor/github.com/gin-gonic/gin/context.go:174 (0x89d3a1) (*Context).Next: c.handlers[c.index](c) /app/vendor/github.com/gin-gonic/gin/gin.go:620 (0x89f3b2) (*Engine).handleHTTPRequest: serveError(c, 404, default404Body)

Here's what each line means:

Common Causes

1. Database query returns nil without check

A database lookup returns a nil pointer when no record matches, and the handler accesses a field without checking for nil first.

func GetUser(c *gin.Context) {
	id := c.Param("id")
	user, _ := db.FindUserByID(id)
	c.JSON(200, gin.H{"name": user.Name}) // user is nil if not found
}

2. Ignoring error return from function

The function returns both a pointer and an error, but the error is ignored with _, allowing nil pointer usage.

func GetUser(c *gin.Context) {
	user, _ := db.FindUserByID(c.Param("id"))
	// _ discards the "not found" error
	c.JSON(200, gin.H{"name": user.Name})
}

3. Uninitialized struct pointer

A struct pointer is declared but never assigned, and code attempts to access its fields.

func GetUser(c *gin.Context) {
	var user *User // nil by default
	c.JSON(200, gin.H{"name": user.Name}) // dereferencing nil
}

The Fix

Always check both the error return and the nil pointer before using the result. Return early with appropriate HTTP status codes (500 for errors, 404 for not found) so the nil pointer is never dereferenced.

Before (broken)
func GetUser(c *gin.Context) {
	id := c.Param("id")
	user, _ := db.FindUserByID(id)
	c.JSON(200, gin.H{"name": user.Name})
}
After (fixed)
func GetUser(c *gin.Context) {
	id := c.Param("id")
	user, err := db.FindUserByID(id)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to query user"})
		return
	}
	if user == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
		return
	}
	c.JSON(http.StatusOK, gin.H{"name": user.Name})
}

Testing the Fix

package handlers

import (
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/gin-gonic/gin"
)

func setupRouter() *gin.Engine {
	gin.SetMode(gin.TestMode)
	r := gin.Default()
	r.GET("/api/users/:id", GetUser)
	return r
}

func TestGetUser_ExistingUser(t *testing.T) {
	router := setupRouter()
	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/api/users/1", nil)
	router.ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("expected status 200, got %d", w.Code)
	}

	var body map[string]string
	json.Unmarshal(w.Body.Bytes(), &body)
	if body["name"] == "" {
		t.Error("expected name to be non-empty")
	}
}

func TestGetUser_NonExistentUser(t *testing.T) {
	router := setupRouter()
	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/api/users/99999", nil)
	router.ServeHTTP(w, req)

	if w.Code != http.StatusNotFound {
		t.Errorf("expected status 404, got %d", w.Code)
	}
}

func TestGetUser_DoesNotPanic(t *testing.T) {
	router := setupRouter()
	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/api/users/invalid", nil)

	defer func() {
		if r := recover(); r != nil {
			t.Errorf("handler panicked: %v", r)
		}
	}()

	router.ServeHTTP(w, req)
}

Run your tests:

go test ./...

Pushing Through CI/CD

git checkout -b fix/nil-pointer-dereference,git add handlers/user.go handlers/user_test.go,git commit -m "fix: check for nil pointer before accessing user fields",git push origin fix/nil-pointer-dereference

Your CI config should look something like this:

name: CI
on:
  pull_request:
    branches: [main]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version: '1.22'
      - run: go mod download
      - run: go vet ./...
      - run: go test -race -coverprofile=coverage.out ./...
      - run: go build ./...

The Full Manual Process: 18 Steps

Here's every step you just went through to fix this one bug:

  1. Notice the error alert or see it in your monitoring tool
  2. Open the error dashboard and read the stack trace
  3. Identify the file and line number from the stack trace
  4. Open your IDE and navigate to the file
  5. Read the surrounding code to understand context
  6. Reproduce the error locally
  7. Identify the root cause
  8. Write the fix
  9. Run the test suite locally
  10. Fix any failing tests
  11. Write new tests covering the edge case
  12. Run the full test suite again
  13. Create a new git branch
  14. Commit and push your changes
  15. Open a pull request
  16. Wait for code review
  17. Merge and deploy to production
  18. Monitor production to confirm the error is resolved

Total time: 30-60 minutes. For one bug.

Or Let bugstack Fix It in Under 2 minutes

Every step above? bugstack does it automatically.

Step 1: Install the SDK

go get github.com/bugstack/sdk

Step 2: Initialize

import "github.com/bugstack/sdk"

func init() {
  bugstack.Init(os.Getenv("BUGSTACK_API_KEY"))
}

Step 3: There is no step 3.

bugstack handles everything from here:

  1. Captures the stack trace and request context
  2. Pulls the relevant source files from your GitHub repo
  3. Analyzes the error and understands the code context
  4. Generates a minimal, verified fix
  5. Runs your existing test suite
  6. Pushes through your CI/CD pipeline
  7. Deploys to production (or opens a PR for review)

Time from error to fix deployed: Under 2 minutes.

Human involvement: zero.

Try bugstack Free →

No credit card. 5-minute setup. Cancel anytime.

Deploying the Fix (Manual Path)

  1. Run go test ./... locally to confirm nil pointer checks work.
  2. Run go vet ./... to catch other potential issues.
  3. Open a pull request with the nil check fix.
  4. Have a teammate review and approve the PR.
  5. Merge to main and verify the endpoint returns proper errors in staging.

Frequently Asked Questions

BugStack runs go vet and go test with race detection, verifies nil checks are present for all pointer dereferences, and confirms no panics occur for edge case inputs.

All fixes are submitted as pull requests with CI validation. Go's type system and test suite catch issues before your team reviews and merges.

Go's type system cannot statically prove whether a pointer is nil at runtime. The compiler allows nil pointers because they are valid zero values for pointer types.

Yes, always keep Gin's recovery middleware enabled as a safety net. But you should still handle nil checks explicitly rather than relying on panic recovery.