docs/migrate/from-spring-boot/page.md
{% answer %}
Spring Boot developers tend to feel at home in GoFr — both frameworks share an opinionated, batteries-included philosophy. Spring's controllers map to GoFr handlers; Spring's auto-configuration maps to GoFr's defaults; Spring's Actuator endpoints map to GoFr's built-in /.well-known/health and /metrics.
{% /answer %}
{% callout title="Migrating with an AI assistant?" %} Hand https://gofr.dev/AGENTS.md to your coding assistant (Claude Code, Cursor, Codex, Aider). It contains the framework conventions, routing/binding/datasource patterns, and per-framework cheat-sheets so the assistant can translate handlers without you re-explaining GoFr. {% /callout %}
| Spring Boot | GoFr |
|---|---|
@RestController + @RequestMapping | app.GET("/path", handler) |
@PathVariable | c.PathParam("id") |
@RequestParam | c.Param("q") |
@RequestBody | c.Bind(&struct) |
@Autowired field injection | Struct fields populated via constructor or via GoFr's container |
application.yaml | .env + GoFr Configs |
Spring Data JPA | Plain SQL via c.SQL (or sqlc / gorm if you want ORM-like) |
@Scheduled cron | app.AddCronJob(...) |
@KafkaListener | app.Subscribe("topic", handler) |
| Spring Actuator | Built-in /.well-known/health, /metrics |
| Micrometer | Built-in Prometheus metrics |
| Spring Cloud Sleuth | Built-in OpenTelemetry tracing |
| Resilience4j circuit breaker | Built-in via app.AddHTTPService |
Spring Boot:
@RestController
class HelloController {
@GetMapping("/hello")
public Map<String, String> hello() {
return Map.of("message", "Hello, world");
}
}
GoFr:
package main
import "gofr.dev/pkg/gofr"
func main() {
app := gofr.New()
app.GET("/hello", func(c *gofr.Context) (any, error) {
return "Hello, world", nil
})
app.Run()
}
Spring Boot:
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: root
server:
port: 8080
GoFr (configs/.env):
HTTP_PORT=8080
DB_HOST=localhost
DB_PORT=3306
DB_NAME=mydb
DB_USER=root
12-factor / environment-variable configuration is GoFr's default; per-environment files are a natural fit for Kubernetes ConfigMaps and Secrets.
Go does not have class-based DI like Spring. The conventions are:
Context.application-prod.yaml / application-staging.yaml profile system. GoFr loads configs/.env and then overlays configs/.<APP_ENV>.env on top — so APP_ENV=production reads configs/.env then configs/.production.env. Note the dot prefix and .env suffix on the override file (not .env.production).@PostConstruct / @PreDestroy with OnStart and graceful shutdown in main.@Autowired field injection becomes constructor parameters, or Wire / Fx if you want a generated graph. See Spring DI patterns and their Go equivalents.A medium Spring Boot service (50-100 endpoints, JPA entities, Kafka listeners) typically takes 1–2 engineering weeks for a Java team. The biggest time sink is decomposing JPA-heavy data access patterns into explicit Go SQL.
{% faq %}
{% faq-item question="What about Spring's @Transactional?" %}
Go has explicit transaction handling — tx, _ := c.SQL.Begin(); defer tx.Rollback(); ...; tx.Commit(). There is no annotation-driven transaction boundary; the boundary is wherever your code says it is.
{% /faq-item %}
{% faq-item question="Can I keep using Kafka with the same topics?" %} Yes. GoFr's Pub/Sub subscriber connects to your existing Kafka brokers and topics. {% /faq-item %}
{% /faq %}