Go vs Java: simplicitat enfront de l''ecosistema enterprise

Comparació real entre Go i Java per a backend, microserveis, rendiment i mantenibilitat. Amb criteri, sense guerra de llenguatges.

Cover for Go vs Java: simplicitat enfront de l''ecosistema enterprise

Porto anys treballant amb Java i Spring Boot en entorns enterprise. Projectes amb desenes de mòduls, pipelines de CI que triguen quinze minuts, serveis que necessiten 512 MB de RAM només per arrancar i servir un endpoint de health check. Java funciona, no dic que no. Però quan vaig començar a escriure serveis en Go, la sensació va ser semblant a treure’m una motxilla de vint quilos que no sabia que portava. No tot és millor a Go --- hi ha coses on Java segueix sent imbatible --- però la diferència en certs escenaris és tan gran que mereix una comparació honesta.

Aquest article no és un “Go bo, Java dolent”. És el que he vist usant tots dos en producció, amb els seus avantatges reals i les seves friccions reals.


Dues filosofies oposades

Java va néixer als 90 amb la promesa de “write once, run anywhere” i una aposta forta per l’orientació a objectes. Amb el temps, l’ecosistema Java es va convertir en sinònim d’enterprise: frameworks enormes, patrons de disseny per capes, configuracions XML que van mutar en anotacions, i una cultura on abstraure és gairebé sempre la resposta. No dic que això sigui dolent --- hi ha una raó per la qual mig món corporatiu funciona amb Java --- però sí té conseqüències.

Go va néixer el 2009 dins de Google amb un objectiu molt diferent: resoldre problemes d’enginyeria de sistemes a escala, amb un llenguatge simple que qualsevol persona de l’equip pogués llegir i entendre. No hi ha herència, no hi ha excepcions, no hi ha genèrics (bé, ara sí, però limitats), no hi ha màgia. La filosofia és: menys features, menys ambigüitat, menys sorpreses.

A Java, la pregunta habitual és “quin patró aplico?”. A Go, és “quina és la forma més directa de resoldre això?”.

Això no és només una diferència estètica, i crec que molta gent ho subestima. Afecta com estructures projectes, com incorpores gent nova, com depures problemes a les 3 de la matinada i quant codi has de mantenir.

Si véns de Java i estàs considerant Go, et recomano començar per una visió general de què és Go i per què existeix abans de continuar amb la comparació.


Estructura de projecte: Maven/Gradle vs go mod

Un projecte típic de Spring Boot amb Maven o Gradle té una estructura que ja coneixes si has tocat Java enterprise:

my-service/
├── pom.xml (or build.gradle.kts)
├── src/
│   ├── main/
│   │   ├── java/com/empresa/servei/
│   │   │   ├── controller/
│   │   │   ├── service/
│   │   │   ├── repository/
│   │   │   ├── model/
│   │   │   ├── dto/
│   │   │   ├── config/
│   │   │   ├── exception/
│   │   │   └── Application.java
│   │   └── resources/
│   │       ├── application.yml
│   │       └── db/migration/
│   └── test/
│       └── java/com/empresa/servei/
└── docker/

Un projecte equivalent en Go:

my-service/
├── go.mod
├── go.sum
├── main.go
├── handler/
│   └── user.go
├── service/
│   └── user.go
├── repository/
│   └── user.go
├── model/
│   └── user.go
└── main_test.go

La diferència salta a la vista. A Java, l’estructura està dictada per convencions del framework (Spring), de l’eina de build (Maven/Gradle) i de la JVM (el classpath). A Go, l’estructura és teva. No hi ha convenció obligatòria més enllà de tenir un go.mod i que el paquet main tingui una funció main(). Això pot ser alliberador o caòtic, depenent de la disciplina de l’equip. I sent honestos, he vist projectes Go tan desorganitzats com qualsevol monòlit Java mal plantejat.

Gestió de dependències

Maven i Gradle són eines potents, però tenen una corba d’aprenentatge considerable. Resoldre conflictes de dependències transitives a Maven és un art fosc que ningú gaudeix. Gradle amb Kotlin DSL millora les coses, però segueix sent un sistema de build complex que de vegades sents que necessita el seu propi equip de manteniment.

A Go:

go mod init github.com/usuari/el-meu-servei
go get github.com/gin-gonic/gin

Ja està. El fitxer go.mod declara les teves dependències, go.sum les verifica, i go mod tidy neteja el que no uses. No hi ha plugins, no hi ha BOMs, no hi ha dependencyManagement. Si vols aprofundir en com organitzar un projecte Go de debò, tinc un article sobre arquitectura neta a Go que entra en detall.


Serveis backend: Spring Boot vs net/http i Gin

Però deixem l’estructura de projecte i anem al que importa de debò: escriure serveis. Aquí és on la diferència es nota. Anem amb un exemple concret: un endpoint REST que retorna un usuari per ID.

Spring Boot (Java)

@RestController
@RequestMapping("/api/users")
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/{id}")
    public ResponseEntity<UserDto> getUser(@PathVariable Long id) {
        return userService.findById(id)
            .map(ResponseEntity::ok)
            .orElse(ResponseEntity.notFound().build());
    }
}

@Service
public class UserService {

    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public Optional<UserDto> findById(Long id) {
        return userRepository.findById(id)
            .map(this::toDto);
    }

    private UserDto toDto(User user) {
        return new UserDto(user.getId(), user.getName(), user.getEmail());
    }
}

public interface UserRepository extends JpaRepository<User, Long> {}

Perquè això funcioni necessites: Spring Boot Starter Web, Spring Data JPA, una base de dades configurada a application.yml, anotacions d’entitat JPA a User, i una classe Application amb @SpringBootApplication. El framework fa molta màgia per tu, però és màgia.

Go amb Gin

package main

import (
    "net/http"
    "strconv"

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

type User struct {
    ID    int64  `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

func main() {
    r := gin.Default()
    r.GET("/api/users/:id", getUser)
    r.Run(":8080")
}

func getUser(c *gin.Context) {
    id, err := strconv.ParseInt(c.Param("id"), 10, 64)
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
        return
    }

    user, err := findUserByID(id)
    if err != nil {
        c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
        return
    }

    c.JSON(http.StatusOK, user)
}

A Go, el que veus és el que hi ha. No hi ha injecció de dependències automàtica, no hi ha proxies, no hi ha classpath scanning. El flux és explícit: entra una request, parseges paràmetres, crides la teva lògica, retornes una resposta. Si alguna cosa falla, ho gestiones allà mateix. És més tediós? Sens dubte. Però quan alguna cosa es trenca a les 3 de la matinada, saber exactament on mirar té un valor que no apareix als benchmarks.

A Spring Boot, sovint necessites entendre tres capes d’abstracció per depurar un error. A Go, el stack trace et porta directament a la línia del problema.

Sense framework també funciona

Una cosa que a Java seria impensable --- escriure un servei HTTP sense framework --- a Go és perfectament viable:

package main

import (
    "encoding/json"
    "net/http"
)

func main() {
    http.HandleFunc("/api/health", func(w http.ResponseWriter, r *http.Request) {
        json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
    })
    http.ListenAndServe(":8080", nil)
}

Això compila, s’executa i serveix JSON. Sense dependències externes. El paquet net/http de la biblioteca estàndard de Go és prou bo per a molts serveis reals. Intenta fer el mateix en Java pur, sense Spring, sense Javalin, sense res: acabes reinventant mig framework.


Microserveis: on Go brilla amb força

Aquest és probablement l’escenari on la diferència entre Go i Java es fa més evident. I la pregunta interessant no és només de rendiment, sinó d’operacions.

Arrencada i consum de recursos

MètricaSpring Boot (Java 21)Go
Temps d’arrencada2-8 segons< 100 ms
Ús de RAM en repòs200-500 MB10-30 MB
Mida d’imatge Docker200-400 MB (amb JRE)10-20 MB (scratch/alpine)
Artefacte de desplegamentJAR + JVMBinari estàtic

Quan tens 3 serveis, aquestes diferències són anecdòtiques. Quan en tens 30 o 50, es converteixen en factura de cloud, en temps de desplegament, en velocitat d’escalat.

El Dockerfile ho diu tot

Java (Spring Boot):

FROM eclipse-temurin:21-jre-alpine
COPY target/my-service.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

Go:

FROM golang:1.22 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o server .

FROM scratch
COPY --from=builder /app/server /server
EXPOSE 8080
ENTRYPOINT ["/server"]

El binari de Go és autocontingut. No necessita runtime, no necessita JVM, no necessita sistema operatiu complet. Pots usar scratch com a imatge base --- literalment un contenidor buit amb el teu binari dins. Això té implicacions de seguretat (menys superfície d’atac) i d’operacions (menys coses que poden trencar-se).

Per a una visió més completa de Go en entorns cloud native i microserveis, tinc un article dedicat sobre microserveis amb Go que entra en més detall.


Rendiment i ús de recursos

La conversa sobre rendiment entre Java i Go té matisos que molts benchmarks ignoren. I crec que aquesta és la part que més convé separar del soroll habitual.

JVM: potent però amb preu

La JVM és una meravella d’enginyeria. El compilador JIT optimitza codi en calent, el garbage collector ha millorat enormement (ZGC, Shenandoah), i per a càrregues de treball long-running amb patrons predictibles, Java pot ser extremadament ràpid.

Però aquest rendiment té un cost:

  • Warmup: La JVM necessita temps per compilar el bytecode a codi natiu. Les primeres milers de requests d’un servei acabat d’arrencar són més lentes.
  • Memòria base: El propi runtime de la JVM, les classes carregades, el metaspace… tot suma abans que el teu codi faci res.
  • GC pauses: Tot i que els GC moderns són molt millors, segueixen existint i segueixen afectant la latència p99.

GraalVM Native Image intenta resoldre això compilant Java a binari natiu, i la idea és prometedora. Però en la pràctica afegeix complexitat, temps de compilació llargs i restriccions en reflection i proxies dinàmics --- exactament el que Spring Boot usa intensivament. És una d’aquelles solucions que t’entusiasma en la demo i després et complica la vida en producció.

Go: predictible i lleuger

Go compila a codi natiu directament. No hi ha warmup, no hi ha JIT, no hi ha bytecode intermedi. El rendiment que veus en la primera request és essencialment el mateix que veuràs en la milionèsima.

// Un servidor HTTP en Go arrenca i està llest en mil·lisegons
// No hi ha "cold start" més enllà del que trigui el teu codi d'inicialització
func main() {
    db := connectDB()
    defer db.Close()

    router := setupRoutes(db)
    log.Println("Server ready on :8080")
    http.ListenAndServe(":8080", router)
}

El garbage collector de Go és simple comparat amb els de la JVM: prioritza latència baixa sobre throughput màxim. Per a serveis que necessiten latència predictible, això és un avantatge real.

AspecteJava (JVM)Go
Throughput en estat estableExcel·lent (després del warmup)Molt bo
Latència p99Variable (GC pauses)Predictible
Cold startLent (2-8s)Instantani (< 100ms)
Ús de memòriaAltBaix
CompilacióRàpida (bytecode)Molt ràpida (natiu)

No és que Go sigui “més ràpid” que Java en tots els escenaris. És que Go és més predictible i més barat d’operar, especialment quan tens molts serveis petits.


Ecosistema: gegant enterprise vs biblioteca estàndard enfocada

I aquí arribem al punt on he de ser just amb Java. Perquè aquí és on Java guanya sense discussió. L’ecosistema Java és probablement el més gran i madur del món del backend.

El que Java et dóna

  • Spring Framework: Injecció de dependències, AOP, transaccions, security, batch, integration, cloud… hi ha un starter per a gairebé tot.
  • JPA/Hibernate: ORM madur amb anys d’optimització.
  • Biblioteques per a tot: Apache Commons, Guava, Jackson, MapStruct, Lombok…
  • Eines d’observabilitat: Micrometer, Spring Actuator, integració nativa amb Prometheus, Grafana, etc.
  • Suport empresarial: Red Hat, VMware (Broadcom), Oracle… hi ha empreses que et venen suport i SLAs.

El que Go et dóna

  • Biblioteca estàndard potent: net/http, encoding/json, database/sql, testing, crypto… per a molts serveis no necessites res més.
  • Biblioteques enfocades: Gin, Echo (routers), sqlx (SQL), pgx (PostgreSQL), zap (logging), cobra (CLIs).
  • Menys opcions, menys paràlisi: A Java, per fer HTTP tens RestTemplate, WebClient, Feign, OkHttp, Apache HttpClient… A Go, tens http.Client de la stdlib. Funciona bé. Següent problema.

La diferència filosòfica és clara: Java et dóna un ecosistema on pots trobar una solució prefabricada per a gairebé qualsevol problema. Go et dóna eines bàsiques sòlides i espera que construeixis el que necessitis. Cap dels dos enfocaments és universalment millor, però sí canvien radicalment com es sent el dia a dia d’un projecte.

Quan importa l’ecosistema?

Si estàs construint un sistema amb autenticació OAuth2, integració amb Active Directory, transaccions distribuïdes, batch processing, i reporting --- l’ecosistema de Spring t’estalvia mesos de feina. Aquelles són solucions madures, provades, amb documentació extensa.

Si estàs construint un servei que rep requests, processa dades i respon JSON, la biblioteca estàndard de Go més una o dues dependències et donen tot el que necessites amb menys complexitat.


Gestió d’errors: excepcions vs errors explícits

Aquesta és una de les diferències que més impacta quan véns de Java. I és una de les que més divideixen opinions. Intentaré ser just amb tots dos enfocaments, tot i que reconec que la meva opinió ha anat canviant amb el temps.

Java: excepcions

public User findById(Long id) {
    return userRepository.findById(id)
        .orElseThrow(() -> new UserNotFoundException("User not found: " + id));
}

// En algun lloc del controller o un @ControllerAdvice
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(UserNotFoundException ex) {
    return ResponseEntity.status(404).body(new ErrorResponse(ex.getMessage()));
}

El model d’excepcions et permet separar el flux normal de la gestió d’errors. En teoria, sona bé. El problema és que les excepcions són invisibles en la signatura del mètode (excepte les checked exceptions, que ningú usa ja). Pots cridar un mètode sense saber que pot llançar cinc excepcions diferents. I això, quan ho descobreixes en producció, fa mal.

Go: errors com a valors

func findUserByID(id int64) (*User, error) {
    user, err := db.QueryRow("SELECT id, name, email FROM users WHERE id = $1", id)
    if err != nil {
        if errors.Is(err, sql.ErrNoRows) {
            return nil, fmt.Errorf("user not found: %d", id)
        }
        return nil, fmt.Errorf("querying user %d: %w", id, err)
    }
    return user, nil
}

A Go, els errors són valors que es retornen com a part del resultat d’una funció. No hi ha excepcions (bé, hi ha panic, però usar-lo per control de flux està mal vist). El resultat és que cada crida a una funció que pot fallar t’obliga a decidir què fer amb l’error.

Sí, el famós if err != nil es repeteix molt. És verbós. Però té un avantatge que aprens a valorar amb el temps: mai hi ha errors ocults. Quan llegeixes codi Go, veus exactament on pot fallar cada operació i què es fa al respecte.

// Això és Go idiomàtic: cada error es gestiona en el punt on ocorre
file, err := os.Open("config.json")
if err != nil {
    return fmt.Errorf("opening config: %w", err)
}
defer file.Close()

data, err := io.ReadAll(file)
if err != nil {
    return fmt.Errorf("reading config: %w", err)
}

var config Config
if err := json.Unmarshal(data, &config); err != nil {
    return fmt.Errorf("parsing config: %w", err)
}

La gestió d’errors a Go és tediosa d’escriure però fàcil de llegir. A Java, és fàcil d’escriure però perillosa d’ignorar.


Testing: JUnit/Mockito vs testing estàndard

Java amb JUnit i Mockito

@ExtendWith(MockitoExtension.class)
class UserServiceTest {

    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private UserService userService;

    @Test
    void shouldReturnUserWhenExists() {
        User user = new User(1L, "Roger", "roger@oshy.tech");
        when(userRepository.findById(1L)).thenReturn(Optional.of(user));

        Optional<UserDto> result = userService.findById(1L);

        assertTrue(result.isPresent());
        assertEquals("Roger", result.get().getName());
        verify(userRepository).findById(1L);
    }

    @Test
    void shouldReturnEmptyWhenNotExists() {
        when(userRepository.findById(99L)).thenReturn(Optional.empty());

        Optional<UserDto> result = userService.findById(99L);

        assertFalse(result.isPresent());
    }
}

JUnit 5 és un framework de testing madur i potent. Mockito facilita crear mocks de dependències. Però necessites dependències externes, anotacions per configurar el context, i la verbositat de Java es nota també aquí.

Go amb testing estàndard

package service

import (
    "testing"
)

type mockUserRepo struct {
    users map[int64]*User
}

func (m *mockUserRepo) FindByID(id int64) (*User, error) {
    user, ok := m.users[id]
    if !ok {
        return nil, fmt.Errorf("user not found: %d", id)
    }
    return user, nil
}

func TestFindUserByID_Exists(t *testing.T) {
    repo := &mockUserRepo{
        users: map[int64]*User{
            1: {ID: 1, Name: "Roger", Email: "roger@oshy.tech"},
        },
    }
    svc := NewUserService(repo)

    user, err := svc.FindByID(1)

    if err != nil {
        t.Fatalf("unexpected error: %v", err)
    }
    if user.Name != "Roger" {
        t.Errorf("expected name Roger, got %s", user.Name)
    }
}

func TestFindUserByID_NotExists(t *testing.T) {
    repo := &mockUserRepo{users: map[int64]*User{}}
    svc := NewUserService(repo)

    _, err := svc.FindByID(99)

    if err == nil {
        t.Fatal("expected error, got nil")
    }
}

Go inclou el paquet testing a la biblioteca estàndard. No necessites frameworks externs. Els mocks són structs que implementen interfícies --- no hi ha màgia de reflection ni generació de proxies. Executes tests amb go test ./... i ja.

AspecteJava (JUnit + Mockito)Go (testing)
Framework necessariJUnit 5 + Mockito + extensionsCap (stdlib)
MockingMockito (reflection/proxies)Interfícies + structs manuals
Execuciómvn test / gradle testgo test ./...
BenchmarksJMH (configuració complexa)testing.B (integrat)
CoveragePlugins addicionalsgo test -cover
Velocitat d’execucióSegons (càrrega JVM + Spring context)Mil·lisegons

La diferència en velocitat d’execució de tests és dramàtica, i no ho dic a la lleugera. Un test unitari a Go s’executa en mil·lisegons. Un test de Spring Boot que aixeca el context de l’aplicació pot trigar 10-30 segons només en arrencar. Sembla poc, però aquells segons acumulats canvien la teva forma de treballar: amb feedback immediat, testes més. Amb feedback lent, testes el just.


Concurrència: goroutines vs threads

No puc comparar Go i Java sense parlar de concurrència, perquè és una de les majors fortaleses de Go. Tot i que la situació està canviant amb Java 21, i això mereix reconèixer-se.

Java: threads i el problema històric

Java ha millorat enormement amb Virtual Threads (Project Loom, disponible des de Java 21). Però durant anys, la concurrència a Java significava ExecutorService, CompletableFuture, synchronized, i molt de cura amb l’estat compartit.

ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
List<Future<Result>> futures = urls.stream()
    .map(url -> executor.submit(() -> fetchUrl(url)))
    .toList();

for (Future<Result> future : futures) {
    Result result = future.get(); // pot llançar excepcions
    process(result);
}

Els Virtual Threads són un pas enorme en la direcció correcta. Però són relativament nous, i moltes biblioteques i frameworks encara s’estan adaptant.

Go: goroutines i channels des del primer dia

results := make(chan Result, len(urls))

for _, url := range urls {
    go func(u string) {
        result, err := fetchURL(u)
        if err != nil {
            results <- Result{Error: err}
            return
        }
        results <- result
    }(url)
}

for range urls {
    result := <-results
    if result.Error != nil {
        log.Printf("error: %v", result.Error)
        continue
    }
    process(result)
}

Les goroutines són part del llenguatge, no del framework. Els channels són primitives del llenguatge per a comunicació entre goroutines. El runtime de Go multiplexa milers de goroutines sobre uns pocs threads del sistema operatiu. Això no és un add-on --- és com Go funciona per defecte.

Go no va inventar la concurrència, però la va fer accessible. El que a Java requeria conèixer ExecutorService, CompletableFuture, i reactive streams, a Go és una goroutine i un channel.


Quan triar Java

Java segueix sent la millor opció en molts escenaris. Seria deshonest no reconèixer-ho, i crec que aquest és el punt on més articles “Go vs Java” fallen: presenten Java com un dinosaure, quan en realitat segueix evolucionant i resolent problemes reals:

  • Dominis complexos amb molta lògica de negoci: Si el teu servei té regles de negoci elaborades, fluxos transaccionals complexos, i un model de domini ric, Java amb Spring et dóna eines madures per gestionar aquella complexitat.
  • Equips grans amb experiència en JVM: Si el teu equip té 10 desenvolupadors Java i zero experiència en Go, migrar té un cost real.
  • Integració enterprise: SAP, Oracle, sistemes legacy, missatgeria JMS, SOAP… l’ecosistema Java té connectors per a tot.
  • Projectes amb Spring ja en producció: Reescriure un monòlit Spring Boot en Go “perquè sí” és una idea terrible. Itera sobre el que funciona.
  • Android: Tot i que Kotlin domina, l’ecosistema Android és JVM.

El factor equip

Aquest punt és crucial i sovint s’ignora en les comparatives tècniques. I potser és el més important de tot aquest article. Si la teva empresa té 50 desenvolupadors Java, un pipeline de CI/CD optimitzat per a JVM, biblioteques internes en Java, i anys de coneixement acumulat, la productivitat d’aquell equip en Java serà major que en Go durant molt de temps. La tecnologia no existeix en el buit, i la millor decisió tècnica que ignora l’equip sol ser una mala decisió.


Quan triar Go

Dit tot l’anterior, Go brilla especialment en aquests escenaris:

  • Microserveis petits i enfocats: Serveis que fan una cosa, la fan bé, i necessiten ser lleugers i ràpids de desplegar.
  • CLIs i eines d’infraestructura: Docker, Kubernetes, Terraform, Hugo… estan escrits en Go per una raó. Un binari estàtic que distribueixes sense dependències és imbatible.
  • Serveis amb alta concurrència: Proxies, API gateways, workers que processen milers de connexions simultànies.
  • Equips que valoren la simplicitat: Si vols que qualsevol persona de l’equip pugui entendre qualsevol part del codi sense conèixer frameworks complexos.
  • Cloud native i contenidors: Imatges Docker mínimes, arrencada instantània, baix consum de memòria. La teva factura de cloud t’ho agrairà.
  • Projectes nous sense deute tècnic Java: Si comences de zero i el domini no requereix l’ecosistema enterprise de Java, Go et permet avançar ràpid amb menys complexitat.

La taula final

CriteriJavaGo
Corba d’aprenentatgeMitjana-alta (llenguatge + frameworks)Baixa (llenguatge simple, poques abstraccions)
EcosistemaEnorme, madur, enterpriseEnfocat, stdlib potent
Rendiment (throughput)Excel·lent després del warmupMolt bo, constant
Ús de recursosAlt (JVM)Baix
ConcurrènciaVirtual Threads (recent)Goroutines (natiu des de sempre)
Gestió d’errorsExcepcionsValors explícits
TestingJUnit + Mockito (extern)testing (stdlib)
DeployJAR + JVMBinari estàtic
Temps de compilacióRàpidMolt ràpid
Suport IDEExcel·lent (IntelliJ)Bo (GoLand, VS Code)
Comunitat enterpriseDominantCreixent
MicroserveisPossible però pesatIdeal
Monòlits complexosIdealPossible però incòmode

Conclusió

No hi ha un guanyador universal entre Go i Java. I crec que qui et digui el contrari o no ha treballat amb tots dos en producció o simplifica massa. Però sí hi ha contextos on cada un és clarament superior.

Si estàs en un entorn enterprise amb equips Java, Spring Boot funcionant en producció, i dominis de negoci complexos, Java segueix sent una elecció sòlida. No necessites canviar per canviar.

Si estàs construint serveis nous, infraestructura, eines de línia de comandes, o qualsevol cosa on la simplicitat, el rendiment predictible i el cost operatiu baix importin més que la mida de l’ecosistema, Go mereix una oportunitat seriosa.

La meva experiència personal: després d’anys amb Spring Boot, escriure un servei en Go es sent com conduir un cotxe manual després d’haver conduit sempre un d’automàtic amb cent botons al tauler. Tens més control, menys intermediaris, i la sensació que entens exactament el que està passant en cada moment. No és per a tothom ni per a tot projecte, però quan encaixa, encaixa de debò.

La millor tecnologia és la que el teu equip pot mantenir, operar i evolucionar amb confiança. De vegades és Java. De vegades és Go. I de vegades és tots dos en el mateix sistema.

Si vols començar a explorar Go amb una base pràctica, fes un cop d’ull a la guia per aprendre Go des de zero. I si ja tens una mica d’experiència, l’article sobre arquitectura neta a Go t’ajudarà a estructurar projectes reals.

OshyTech

Enginyeria backend i de dades orientada a sistemes escalables, automatització i IA.

Navegació

Copyright 2026 OshyTech. Tots els drets reservats