Java vs Kotlin en projectes reals

Guia pràctica de Java vs Kotlin basada en projectes reals: sintaxi, rendiment, bones pràctiques i com decidir què fer servir en cada cas.

Cover for Java vs Kotlin en projectes reals

Quan algú compara Java vs Kotlin, gairebé sempre troba taules de features i benchmarks sintètics. Però en projectes reals el que pesa de debò és:

  • Quin llenguatge et permet entregar més ràpid.
  • Quin et dona menys ensurts a producció.
  • Com afecta l’equip i el manteniment de la base de codi.

En el meu cas, vinc de molts anys treballant amb Java en projectes de tota mena, i des de fa uns quants anys també mantinc i desenvolupo codi en Kotlin en paral·lel. Això vol dir conviure cada dia amb repos híbrids, mòduls heretats en Java i noves funcionalitats ja escrites en Kotlin. El que llegiràs aquí surt d’aquest contrast constant.


Panorama general: per què Java vs Kotlin no és un debat teòric

Més que triar un guanyador absolut entre Java vs Kotlin, la pregunta pràctica sol ser:

  • Què mantinc en Java perquè funciona i és estable?
  • On em compensa introduir Kotlin per guanyar productivitat?
  • Com gestiono una base de codi híbrida sense tornar boig l’equip?

La clau és que Kotlin viu en el mateix ecosistema JVM que Java, s’integra amb les seves llibreries i frameworks, i pots barrejar tots dos llenguatges dins del mateix projecte. Això et permet pensar en termes d’estratègia i no de “reescriptura total”.


Sintaxi Kotlin vs Java en el dia a dia

Parlar de sintaxi Kotlin vs Java no és un tema estètic. Afecta:

  • El temps que trigues a escriure una feature.
  • La claredat dels PR.
  • La facilitat per refactoritzar.

Data classes vs POJOs verbosos

Exemple típic de model simple:

Java

public class User {
    private final Long id;
    private String name;
    private String email;

    public User(Long id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    @Override
    public boolean equals(Object o) {
        // equals típic
    }

    @Override
    public int hashCode() {
        // hashCode típic
    }

    @Override
    public String toString() {
        return "User{id=" + id + ", name='" + name + "', email='" + email + "'}";
    }
}

Kotlin

data class User(
    val id: Long,
    var name: String,
    var email: String
)

En bases de codi grans, aquesta diferència es multiplica per centenars de classes. Després d’anys mantenint tots dos llenguatges, es nota que:

  • Kotlin redueix moltíssim el codi repetitiu.
  • Els diffs de PR són més petits i més fàcils de revisar.
  • Refactoritzar models és més senzill i menys propens a errors.

Null safety: dels NPE “sorpresa” als errors en compilació

Una de les diferències més importants en la sintaxi Kotlin vs Java és com es tracta el null.

Java: NullPointerException a qualsevol lloc

public String getUserEmail(User user) {
    return user.getProfile().getEmail().toLowerCase();
}

Si user, profile o email són null, el bug apareix en temps d’execució. Pots evitar-ho amb if imbricats o Optional, però no és obligatori.

Kotlin: tipus anul·lables i operadors segurs

fun getUserEmail(user: User?): String? {
    return user
        ?.profile
        ?.email
        ?.lowercase()
}
  • User? obliga a pensar si l’usuari pot ser null.
  • L’operador ?. encadena accessos segurs.
  • Si una cosa és opcional, el tipus ho deixa clar.

Amb uns quants anys usant Kotlin al costat de Java, es nota com molts errors passen de runtime a compile time, una cosa clau en projectes on molts desenvolupadors toquen el mateix codi.


Funcions d’extensió vs utilitats estàtiques

En Java és habitual acabar amb classes tipus *Utils plenes de mètodes estàtics.

public class PriceUtils {

    public static BigDecimal applyVat(BigDecimal basePrice, BigDecimal vatPercent) {
        if (basePrice == null || vatPercent == null) {
            return BigDecimal.ZERO;
        }
        BigDecimal multiplier = vatPercent
                .divide(BigDecimal.valueOf(100))
                .add(BigDecimal.ONE);
        return basePrice.multiply(multiplier);
    }
}

Ús:

BigDecimal finalPrice = PriceUtils.applyVat(basePrice, vatPercent);

Kotlin: funcions d’extensió

fun BigDecimal.applyVat(vatPercent: BigDecimal?): BigDecimal {
    if (vatPercent == null) return this
    val multiplier = vatPercent
        .divide(BigDecimal(100))
        .plus(BigDecimal.ONE)
    return this.multiply(multiplier)
}

Ús:

val finalPrice = basePrice.applyVat(vatPercent)

Això converteix l’API interna en una cosa més propera al domini: llegeixes el codi i sembla gairebé una frase. Després de treballar amb tots dos estils, es nota que Kotlin facilita crear mini-DSLs que fan que la lògica de negoci s’entengui millor.

Rendiment Kotlin vs Java en la pràctica

Quan es parla de rendiment Kotlin vs Java, hi ha dos escenaris diferents:

  1. Rendiment en compilació.
  2. Rendiment en execució.

Compilació: builds ràpids vs features potents

En projectes grans, especialment amb molts mòduls i anotacions, el que es veu en el dia a dia és:

  • Java sol compilar una mica més ràpid de manera consistent.
  • Kotlin pot penalitzar quan:
    • Hi ha molt ús de data class.
    • Es fan servir intensament coroutines i genèrics.
    • Entra en joc KAPT i processadors d’anotacions.

No és dramàtic, però si vens de builds molt ràpids en Java es nota quan comences a introduir Kotlin sense cuidar l’estructura.

Un enfocament que m’ha funcionat:

  • Mòduls “core” molt estables: es poden mantenir en Java si la compilació és un coll d’ampolla.
  • Mòduls de lògica de negoci, més canviants: aquí Kotlin compensa per la seva productivitat.

Rendiment en execució: la JVM mana

En termes de CPU i memòria, en la majoria d’aplicacions empresarials no s’observa una diferència significativa entre codi equivalent en Java i en Kotlin:

  • Tots dos acaben generant bytecode JVM similar.
  • El coll d’ampolla real sol estar en:
    • Accés a base de dades.
    • Crides a altres serveis.
    • Disseny de l’arquitectura.

En escenaris de rendiment crític, l’estratègia que millor m’ha funcionat ha estat:

  1. Escriure la lògica normalment en Kotlin, per productivitat.
  2. Fer profiling real en entorn de càrrega.
  3. Optimitzar només els hotspots, fins i tot fent servir estils més “Java clàssics” quan calgui.

Concurrència: threads i futures vs coroutines

Una de les diferències pràctiques més notables en Java vs Kotlin és com modeles treball asíncron.

Java: ExecutorService i CompletableFuture

ExecutorService executor = Executors.newFixedThreadPool(10);

public CompletableFuture<OrderSummary> buildOrderSummary(Long orderId) {
    return CompletableFuture.supplyAsync(() -> loadOrder(orderId), executor)
        .thenCombineAsync(
            CompletableFuture.supplyAsync(() -> loadOrderLines(orderId), executor),
            (order, lines) -> new OrderSummary(order, lines),
            executor
        );
}
  1. És potent, però la composició de futures es torna verbosa.
  2. La gestió d’excepcions s’ha de cuidar molt.
suspend fun buildOrderSummary(orderId: Long): OrderSummary = coroutineScope {
    val orderDeferred = async { loadOrder(orderId) }
    val linesDeferred = async { loadOrderLines(orderId) }

    OrderSummary(
        order = orderDeferred.await(),
        lines = linesDeferred.await()
    )
}
  • El codi sembla gairebé síncron.
  • suspend i coroutineScope fan explícit el flux.
  • Compondre treballs concurrents és més llegible.

Quan portes anys mantenint serveis en Java i després introdueixes Kotlin amb coroutines, la sensació és que passes de barallar-te amb l’API de concurrència a expressar la lògica del que realment necessites fer.

Bones pràctiques Java i Kotlin en bases de codi mixtes

En projectes reals, l’escenari més habitual no és “100% Java” vs “100% Kotlin”, sinó una base de codi híbrida. Aquí entren en joc les bones pràctiques Java i Kotlin.

Separar per estabilitat i domini

Una cosa que ajuda molt és organitzar mòduls en funció de:

  • Estabilitat del codi:
    • Nucli molt estable: es pot quedar en Java sense problema.
    • Capes de negoci molt canviants: Kotlin aporta molta agilitat.
  • Proximitat al domini:
    • Codi que modela conceptes de negoci: Kotlin hi encaixa especialment bé (data class, sealed class, DSLs, etc.).

Estratègia de migració gradual

En lloc de reescriure-ho tot, una estratègia que he aplicat en diversos projectes és:

  1. Mantenir el codi existent en Java.
  2. Escriure funcionalitat nova en Kotlin quan sigui possible.
  3. Migrar a Kotlin només les classes Java on el manteniment és dolorós:
    • Molta lògica de negoci.
    • Molts bugs recurrents.
    • Refactors freqüents.

Així s’evita el xoc d’una reescriptura total i l’equip s’acostuma a Kotlin a poc a poc.

Testing en Kotlin per a projectes majoritàriament Java

Un truc molt pràctic per introduir Kotlin sense risc és començar pels tests:

  • És una àrea on la sintaxi concisa de Kotlin brilla:
    • Builders per a dades de prova.
    • DSLs “given/when/then”.
    • Extensions per a asserts.

Exemple de test en Kotlin sobre codi Java

class OrderServiceTest {

    private val repository = InMemoryOrderRepository()
    private val service = OrderService(repository)

    @Test
    fun `should create order with lines`() {
        val request = orderRequest {
            customerId = 123L
            line {
                productId = 1L
                quantity = 2
            }
            line {
                productId = 2L
                quantity = 1
            }
        }

        val order = service.createOrder(request)

        assertThat(order.lines).hasSize(2)
        assertThat(order.customerId).isEqualTo(123L)
    }
}

fun orderRequest(block: OrderRequestBuilder.() -> Unit): OrderRequest =
    OrderRequestBuilder().apply(block).build()

class OrderRequestBuilder {
    var customerId: Long = 0
    private val lines = mutableListOf<OrderLineRequest>()

    fun line(block: OrderLineRequest.() -> Unit) {
        lines += OrderLineRequest().apply(block)
    }

    fun build() = OrderRequest(customerId, lines)
}

Aquest tipus de DSL per a tests és molt més natural en Kotlin que en Java, i serveix com a porta d’entrada perquè l’equip es familiaritzi amb el llenguatge.

Casos típics: Android, backend i llibreries internes

Android: Kotlin com a estàndard de facto

En Android, el debat Java vs Kotlin està pràcticament decantat:

  • La majoria d’exemples, documentació i llibreries modernes ja se centren en Kotlin.
  • L’ecosistema de coroutines i Flow encaixa molt bé amb la gestió d’estat i UI moderna.

Si avui comencés una app Android des de zero, no en tindria cap dubte: la base estaria en Kotlin i Java quedaria reservat per integrar SDKs antics o llibreries molt legacy.

Backend: Spring, Ktor i companyia

En backend el panorama és més equilibrat:

  • Si tens una plataforma enorme basada en Java i Spring de fa anys, no hi ha pressa per migrar-ho tot.
  • Però Kotlin encaixa molt bé en:
    • Serveis nous basats en Spring Boot.
    • Serveis lleugers amb Ktor o frameworks similars.
    • Capa de domini i DTOs.

Exemple de controlador REST

Java (Spring)

@RestController
@RequestMapping("/api/orders")
public class OrderController {

    private final OrderService service;

    public OrderController(OrderService service) {
        this.service = service;
    }

    @GetMapping("/{id}")
    public ResponseEntity<OrderDto> getOrder(@PathVariable Long id) {
        return service.findById(id)
            .map(order -> ResponseEntity.ok(toDto(order)))
            .orElse(ResponseEntity.notFound().build());
    }

    // mapper toDto(...)
}

Kotlin (Spring)

@RestController
@RequestMapping("/api/orders")
class OrderController(
    private val service: OrderService
) {

    @GetMapping("/{id}")
    fun getOrder(@PathVariable id: Long): ResponseEntity<OrderDto> =
        service.findById(id)
            ?.let { ResponseEntity.ok(it.toDto()) }
            ?: ResponseEntity.notFound().build()
}

fun Order.toDto() = OrderDto(
    id = id,
    customerId = customerId,
    total = total,
    lines = lines.map { it.toDto() }
)

fun OrderLine.toDto() = OrderLineDto(
    productId = productId,
    quantity = quantity,
    price = price
)

És el mateix patró, però amb menys càrrega cognitiva i un ús intensiu d’extensions.

Llibreries internes i SDKs

En llibreries de molt baix nivell o SDKs interns que es consumeixen des de molts projectes Java, moltes vegades compensa:

  • Continuar en Java per compatibilitat i predictibilitat.
  • Fer servir Kotlin en el codi de negoci que consumeix aquestes llibreries.

Checklist pràctic: Java, Kotlin o tots dos?

A l’hora de decidir entre Java vs Kotlin en un projecte real, aquest checklist ajuda:

  • Tens una base de codi Java gran i estable?
    • Sí: comença amb Kotlin en mòduls nous i tests, no ho reescriguis tot.
    • No: Kotlin és una opció molt seriosa com a llenguatge principal.
  • Com és l’equip?
    • Perfil molt Java clàssic, poc temps de formació: híbrid suau, introduint Kotlin a poc a poc.
    • Perfil mixt, ganes d’actualitzar-se: aposta més forta per Kotlin.
  • Què pesa més ara mateix: velocitat d’entrega o estabilitat extrema?
    • Entrega ràpida de noves features: Kotlin sol oferir millor productivitat.
    • Estabilitat en sistemes crítics molt madurs: Java continua sent l’opció conservadora.
  • Quin tipus de projecte és?
    • Android: Kotlin gairebé per defecte.
    • Backend empresarial: l’híbrid sol ser molt raonable.
    • Llibreries internes consumides per molts projectes: Java pot continuar sent la columna vertebral.

Llavors… amb quin em quedo?

Després de mantenir durant anys projectes en Java i altres en Kotlin, i molts híbrids, la conclusió més honesta és que el debat Java vs Kotlin poques vegades es resol amb un “tot o res”.

  • Java continua sent robust, estable, àmpliament entès i amb un tooling excel·lent.
  • Kotlin aporta una sintaxi més expressiva, null safety, coroutines i, en general, una experiència de desenvolupament més agradable.

La bona notícia és que poden conviure sense problema en el mateix projecte. La decisió pràctica passa per:

  • Fer servir Java on ja tens una base sòlida i estable.
  • Introduir Kotlin on el canvi constant i la llegibilitat de la lògica de negoci són clau.
  • Dissenyar una estratègia gradual, pensant en les persones de l’equip tant com en la tecnologia.

Articles relacionats

OshyTech

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

Navegació

Copyright 2026 OshyTech. Tots els drets reservats