diff --git a/Makefile b/Makefile
index bb70b91bb9..c868ef4463 100644
--- a/Makefile
+++ b/Makefile
@@ -36,7 +36,8 @@ XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
 GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1
 GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1
 ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1
-GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.17.1
+GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.19.0
+GOPLS_MODERNIZE_PACKAGE ?= golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@v0.19.0
 
 DOCKER_IMAGE ?= gitea/gitea
 DOCKER_TAG ?= latest
@@ -230,7 +231,7 @@ clean: ## delete backend and integration files
 		tests/e2e/reports/ tests/e2e/test-artifacts/ tests/e2e/test-snapshots/
 
 .PHONY: fmt
-fmt: ## format the Go code
+fmt: ## format the Go and template code
 	@GOFUMPT_PACKAGE=$(GOFUMPT_PACKAGE) $(GO) run build/code-batch-process.go gitea-fmt -w '{file-list}'
 	$(eval TEMPLATES := $(shell find templates -type f -name '*.tmpl'))
 	@# strip whitespace after '{{' or '(' and before '}}' or ')' unless there is only
@@ -249,6 +250,19 @@ fmt-check: fmt
 	  exit 1; \
 	fi
 
+.PHONY: fix
+fix: ## apply automated fixes to Go code
+	$(GO) run $(GOPLS_MODERNIZE_PACKAGE) -fix ./...
+
+.PHONY: fix-check
+fix-check: fix
+	@diff=$$(git diff --color=always $(GO_SOURCES)); \
+	if [ -n "$$diff" ]; then \
+	  echo "Please run 'make fix' and commit the result:"; \
+	  printf "%s" "$${diff}"; \
+	  exit 1; \
+	fi
+
 .PHONY: $(TAGS_EVIDENCE)
 $(TAGS_EVIDENCE):
 	@mkdir -p $(MAKE_EVIDENCE_DIR)
@@ -288,7 +302,7 @@ checks: checks-frontend checks-backend ## run various consistency checks
 checks-frontend: lockfile-check svg-check ## check frontend files
 
 .PHONY: checks-backend
-checks-backend: tidy-check swagger-check fmt-check swagger-validate security-check ## check backend files
+checks-backend: tidy-check swagger-check fmt-check fix-check swagger-validate security-check ## check backend files
 
 .PHONY: lint
 lint: lint-frontend lint-backend lint-spell ## lint everything
@@ -809,6 +823,7 @@ deps-tools: ## install tool dependencies
 	$(GO) install $(GOVULNCHECK_PACKAGE) & \
 	$(GO) install $(ACTIONLINT_PACKAGE) & \
 	$(GO) install $(GOPLS_PACKAGE) & \
+	$(GO) install $(GOPLS_MODERNIZE_PACKAGE) & \
 	wait
 
 node_modules: package-lock.json
diff --git a/models/migrations/v1_22/v294_test.go b/models/migrations/v1_22/v294_test.go
index a1d702cb77..c3de332650 100644
--- a/models/migrations/v1_22/v294_test.go
+++ b/models/migrations/v1_22/v294_test.go
@@ -4,7 +4,6 @@
 package v1_22 //nolint
 
 import (
-	"slices"
 	"testing"
 
 	"code.gitea.io/gitea/models/migrations/base"
@@ -44,7 +43,7 @@ func Test_AddUniqueIndexForProjectIssue(t *testing.T) {
 	for _, index := range tables[0].Indexes {
 		if index.Type == schemas.UniqueType {
 			found = true
-			slices.Equal(index.Cols, []string{"project_id", "issue_id"})
+			assert.ElementsMatch(t, index.Cols, []string{"project_id", "issue_id"})
 			break
 		}
 	}
diff --git a/modules/public/public.go b/modules/public/public.go
index 9bc04f3f7e..a7eace1538 100644
--- a/modules/public/public.go
+++ b/modules/public/public.go
@@ -110,5 +110,4 @@ func servePublicAsset(w http.ResponseWriter, req *http.Request, fi os.FileInfo,
 		}
 	}
 	http.ServeContent(w, req, fi.Name(), modtime, content)
-	return
 }
diff --git a/routers/private/hook_verification_test.go b/routers/private/hook_verification_test.go
index f6c2e1087f..8653e34daa 100644
--- a/routers/private/hook_verification_test.go
+++ b/routers/private/hook_verification_test.go
@@ -18,7 +18,9 @@ func TestVerifyCommits(t *testing.T) {
 	unittest.PrepareTestEnv(t)
 
 	gitRepo, err := git.OpenRepository(t.Context(), testReposDir+"repo1_hook_verification")
-	defer gitRepo.Close()
+	if err != nil {
+		defer gitRepo.Close()
+	}
 	assert.NoError(t, err)
 
 	objectFormat, err := gitRepo.GetObjectFormat()
diff --git a/services/packages/arch/vercmp.go b/services/packages/arch/vercmp.go
index 6dcd0df419..d44aa530f0 100644
--- a/services/packages/arch/vercmp.go
+++ b/services/packages/arch/vercmp.go
@@ -35,7 +35,7 @@ func parseEVR(evr string) (epoch, version, release string) {
 func compareSegments(a, b []string) int {
 	lenA, lenB := len(a), len(b)
 	l := min(lenA, lenB)
-	for i := 0; i < l; i++ {
+	for i := range l {
 		if r := compare(a[i], b[i]); r != 0 {
 			return r
 		}
diff --git a/tools/lint-go-gopls.sh b/tools/lint-go-gopls.sh
index a222ea14d7..2cd26ca6fe 100755
--- a/tools/lint-go-gopls.sh
+++ b/tools/lint-go-gopls.sh
@@ -11,7 +11,7 @@ IGNORE_PATTERNS=(
 # current absolute path, indicating a error was found. This is necessary
 # because the tool does not set non-zero exit code when errors are found.
 # ref: https://github.com/golang/go/issues/67078
-ERROR_LINES=$("$GO" run "$GOPLS_PACKAGE" check "$@" 2>/dev/null | grep -E "^$PWD" | grep -vFf <(printf '%s\n' "${IGNORE_PATTERNS[@]}"));
+ERROR_LINES=$("$GO" run "$GOPLS_PACKAGE" check -severity=warning "$@" 2>/dev/null | grep -E "^$PWD" | grep -vFf <(printf '%s\n' "${IGNORE_PATTERNS[@]}"));
 NUM_ERRORS=$(echo -n "$ERROR_LINES" | wc -l)
 
 if [ "$NUM_ERRORS" -eq "0" ]; then