Lately I’ve been working with two different technology stacks almost in parallel, in both cases we were using them to develop REST services.

During this time I’ve come up with some conclusions and opinions I’d like to share.

A disclaimer, few months ago, I had several years of experience with Java and 0 days of professional experience with Golang.

Actual project examples

Few months ago I created an API to extract and structure COVID-19 data from ECDC website. I developed it in Spring Boot (REST).

Few months later I had the luck of work on my first professional project in Go and I decided to create a port of the API to extract COVID-19 data in Go, just for learning and for fun.

Now we have two REST services, almost functionally identical, but developed in two different tech stacks, so we can easily compare some relevant aspects of both.

Table 1. Source code for the 2 REST services implementations
Java + Spring Boot (REST) Go + Gin framework

I actually created that COVID-19 data REST API to be the data source for the COVID19-Stats App, a PWA built with Svelte, but that’s another topic.

The Ecosystems

If you want to create a REST service just in plain Java you will have extra work to do, in Golang a little bit less. That’s why we use framework, because they’ve already solved many common problems for us.

For this comparison I am going to use Spring Boot (REST) for Java and Gin framework for Go, but in both languages there are a lot of production ready nice options.


Go - Without framework

Go uses the concept of HTTP multiplexer or router. You can specify routes using patterns and link those routes to handlers. The router will decide which handler has to execute the request based on the path received.

router.go file
package main

import (

func main() {
	router := http.NewServeMux()
	router.Handle("/redirect", http.RedirectHandler("", 307))
	router.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello world!"))
	http.ListenAndServe(":3000", router)

Source code is already quite simple, but there might more complex routing use cases.

Go - Gin Framework

Happily there are frameworks that help us to keep our base code simple, for example when we need to extract path parameters, which is quite common use case in REST APIs, we can use a routing library, I’ve used Gorilla Mux and Gin framework and I liked more Gin framework.

import (

func main() {

	// ...

	r := gin.Default()
	r.GET("/countries", router.Countries)
	r.GET("/countries/:code", router.Country)
	r.GET("/countries/:code/dates", router.CountryDates)
	r.GET("/countries/:code/dates/:date", router.CountryDate)
And this is a handler example, the router.Countries one
func (r *routerImpl) Countries(c *gin.Context) {
	c.JSON(200, r.countries())

Java +

The Spring Boot (REST) is based on the concept of Controller, it is implemented using annotations on the class and methods.

@RestController (1)
@RequestMapping("/countries") (2)
public class CountriesController {

	// Some source code is not shown, you can find the complete example in the repository

	@GetMapping("/{country}/dates/{isoDateStr}")  (3)
	public DateStatsDto getDateByCountry(@Size(min = 2, max = 2) @PathVariable String country, @Size(min = 10, max = 20) @PathVariable String isoDateStr) throws NotFoundException {
return new DateStatsDto(service.getDate(country, DateUtils.convert(isoDateStr)));
  1. Declare the class as Controller so it is registered in Spring Boot (REST)

  2. Controller base path definition

  3. Handler definition for a nested path under the main controller path. Spring Boot (REST) makes easy to extract path variables defined in the route, you can directly use them as method arguments.


Go - Gin Framework

Gin framework uses an external validation package validator, besides that it is fully integrated with Gin framework.

type User struct {
	Name  string `validate:"required"` (1)
	Email string `validate:"required,email"`

err := validate.Struct(user) (2)
validationErrors := err.(validator.ValidationErrors) (3)
  1. The validation system uses Go tags, it is not the same as Java annotations, but in the validation case, it works in pretty same way as annotations.

  2. Executes the validation explicitly

  3. Extracts validation errors

Java +

You can enable the validation in the controller level, then in the handlers you can also specify the type of validation. Let’s explain it using the previous example:

@Validated (1)
public class CountriesController {

	// Some source code is not shown, you can find the complete example in the repository

	public DateStatsDto getDateByCountry(
@Size(min = 2, max = 2) @PathVariable String country, (2)
@Size(min = 10, max = 20) @PathVariable String isoDateStr) throws NotFoundException {
return new DateStatsDto(service.getDate(country, DateUtils.convert(isoDateStr)));
  1. Declare the class as Controller so it is registered in Spring Boot (REST)

  2. @Size validates that the input argument country has 2 characters

The validation system is more powerful than you can see in this code snippet, for example adding @Valid annotation opens the door to complex types validation.

Filtering and Middleware

Different approaches, pretty much the same end result.

I will elaborate this topic in following days.

Dependency injection / IoC

Spring IoC

Spring IoC is the most complete and powerful systems I’ve ever used for IoC, actually, the first time I used Spring professionally was just to deal with IoC. It supports XML configuration files or Java annotations, I like annotations more, here a simple example from Spring IoC documentation:

Spring IoC example
public class JpaMovieFinder implements MovieFinder { (1)
    // implementation elided for clarity


public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired (2)
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;

    // ...
  1. JpaMovieFinder is instantiated by Spring IoC

  2. With @Autowired annotation Spring IoC knows that has to inject movieFinder argument. It should be a class implementing MovieFinder


Neither Go nor Gin framework has any IoC solution, but you can still apply Dependency Injection technique to decouple your components and improve the testability of your system.

Dependency injection simple example in Go
package main

import "fmt"

// Greeter interface to greet the caller
type Greeter interface {

type greeterHello struct{}

func (g *greeterHello) greet() { (3)

type greeterHi struct{}

func (g *greeterHi) greet() { (4)

// App Application representation
type App struct {
	greeters []Greeter (1)

func (app *App) startup() {
	for _, v := range app.greeters {

func main() {
	greeters := []Greeter{ (2)

	app := &App{greeters}


<1> `App` accepts an array of `Greeter`
<2> During `App` instantiation we pass different implementations of `Greeter`
<3> Greeter implementation that prints *Hello!*
<4> Greeter implementation that prints *Hi!*

It is more verbose, but there is an advantage, there is nothing hidden, everything is explicit and you have full control of instantiation order.

As soon as you use Dependency Injection, I don’t have any strong opinion about using IoC system or doing Dependency Injection manually.


Unit tests

For unit tests there are no big differences.

Go comes with a standard library for testing and benchmarking.

For Java there are many well-known unit testing frameworks, but Spring already has quite big support for unit testing.

Integration tests


There are no support for Integration Tests in Go, you will have to implement everything by yourself, although it is not difficult, here you can find a simple example.


To write integration tests for REST services, MockMvc is really convenient.

MockMvc code snippet from
	private MockMvc mockMvc; (1)

	void getCountries() throws Exception {
this.mockMvc.perform(get("/countries")) (2)
		.andDo(print()).andExpect(status().isOk()) (3)
		.andExpect(jsonPath("$.*", hasSize(144)))
		.andExpect(jsonPath("$.ES.deathsNumber", comparesEqualTo(309)))
		.andExpect(jsonPath("$.ES.countryCode", comparesEqualTo("ES")))
		.andExpect(jsonPath("$.ES.countryName", comparesEqualTo("Spain")))
		.andExpect(jsonPath("$.ES.path", comparesEqualTo("/countries/ES")))
		.andExpect(jsonPath("$.VC.countryName", comparesEqualTo("Saint Vincent and the Grenadines")))
		.andDo(document("countries/list", preprocessResponse(prettyPrint(), new CropPreprocessor())));
  1. The Spring test runner injects the MockMvc object.

  2. We use MockMvc to call to the endpoint we have created.

  3. Then we validate the endpoint response: status code and body.


Besides the languages specific differences, the main difference is the performance. The CPU consumption in Go is smaller, but about the memory the difference is really significant, the order of 30 times smaller fingerprint.


Here I’ve found a surprising difference, just by checking the memory consumption in my laptop.

Memory consumption


Following the TechEmpower benchmarks:

Following the The Benchmarker results:


If I were you, I’d choose Go if:

  • If you value the explicit over implicit, keep in mind that there is a cost, you will most likely have to write more lines of code.

  • If you value the simplicity, Go has a quite reduced set of keywords, which reduces the learning curve and simplifies the code reviews.

  • If RAM memory usage is critical for your project, actually I’d just keep away from Spring Boot (REST).

  • If the project you are going to work on is a distributed system, specially if it is based on HTTP.