Translate

Mostrando las entradas con la etiqueta Java. Mostrar todas las entradas
Mostrando las entradas con la etiqueta Java. Mostrar todas las entradas

jueves, 25 de junio de 2026

@MockitoSpyBean en Spring Framework


Si trabajás con pruebas en Spring Boot, seguramente utilizaste @SpyBean para observar el comportamiento de un bean real y verificar sus interacciones.

Sin embargo, a partir de las versiones más recientes de Spring, @SpyBean fue reemplazado por @MockitoSpyBean, una nueva anotación que forma parte del soporte oficial de Mockito en Spring Framework.

¿Por qué aparece @MockitoSpyBean?

Durante años Spring Boot ofreció:
  • @MockBean
  • @SpyBean

para integrar Mockito con el contexto de Spring.

Con la evolución del framework, estas anotaciones fueron reemplazadas por:
  • @MockitoBean
  • @MockitoSpyBean

que proporcionan una integración más consistente y alineada con Mockito.

Un Spy es una instancia real envuelta por Mockito.

Esto significa que:
  • Los métodos reales se ejecutan normalmente.
  • Podemos verificar invocaciones.
  • Podemos modificar comportamientos específicos cuando sea necesario.

Por eso un Spy resulta ideal cuando queremos observar el comportamiento de un bean sin reemplazarlo completamente.

Supongamos el siguiente servicio:


@Service
public class EmailService {

    public void send(String to, String message) {
        System.out.println("Sending email to " + to);
    }
}


Y un servicio que lo utiliza:

@Service
public class UserService {

    private final EmailService emailService;

    public UserService(EmailService emailService) {
        this.emailService = emailService;
    }

    public void register(String email) {
        emailService.send(email, "Welcome!");
    }
}

Utilizando @MockitoSpyBean


@SpringBootTest
class UserServiceTest {

    @Autowired
    private UserService userService;

    @MockitoSpyBean
    private EmailService emailService;

    @Test
    void shouldSendWelcomeEmail() {

        userService.register("john@example.com");

        verify(emailService)
                .send("john@example.com", "Welcome!");
    }
}

La prueba utiliza el bean real de Spring, pero Mockito registra las interacciones permitiéndonos verificar llamadas y argumentos.

Aunque el Spy ejecuta la implementación real, podemos modificar métodos específicos:

doNothing()
    .when(emailService)
    .send(anyString(), anyString());


De esta manera evitamos ejecutar la lógica real mientras seguimos verificando las invocaciones.

En la mayoría de los proyectos Spring Boot basta con incluir:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

Si usas gradle:

testImplementation 'org.springframework.boot:spring-boot-starter-test'

Esta dependencia incorpora Mockito y las utilidades de testing de Spring.

@MockitoBean vs @MockitoSpyBean ¿Cuándo conviene usarlo?

@MockitoSpyBean resulta útil cuando:
  • Queremos verificar interacciones sobre un bean real.
  • Necesitamos conservar parte de la lógica original.
  • Queremos evitar crear mocks complejos.
  • Estamos realizando pruebas de integración con Spring.

Los Spies son poderosos, pero también pueden volver las pruebas más sensibles a cambios internos de implementación.

Como regla general:
  • Utilizá @MockitoBean para aislar dependencias.
  • Utilizá @MockitoSpyBean cuando necesites observar el comportamiento real de un bean.

@MockitoSpyBean representa la evolución natural de @SpyBean y es la alternativa recomendada en las versiones actuales de Spring.

Permite combinar la ejecución real de un componente con todas las capacidades de verificación de Mockito, logrando pruebas más expresivas y fáciles de diagnosticar cuando algo falla.

Y si no entienden la imagen es Mr Bean de espía ;) 

lunes, 22 de junio de 2026

ANTLR: El primer paso para construir un lenguaje de programación


Cuando usamos Java, Kotlin o Scala, normalmente escribimos código sin pensar demasiado en lo que ocurre detrás de escena.


int result = 10 + 20;


Sin embargo, antes de que la JVM pueda ejecutar ese programa, alguien tuvo que diseñar un compilador capaz de entender ese texto y transformarlo en bytecode.

En esta serie vamos a construir nuestro propio lenguaje para la JVM. No pretendemos competir con Kotlin o Scala, pero sí recorrer muchos de los mismos conceptos que utilizan estos lenguajes internamente.


Y el primer paso es aprender una herramienta fundamental: ANTLR.


¿Qué es ANTLR?

ANTLR (ANother Tool for Language Recognition) es una herramienta que permite generar analizadores léxicos y sintácticos a partir de una gramática.

En lugar de escribir manualmente todo el código necesario para interpretar un lenguaje, simplemente describimos las reglas del mismo y ANTLR genera automáticamente gran parte de la infraestructura necesaria.


Por ejemplo, podemos definir una regla como:


expression

    : expression '+' expression

    | NUMBER

    ;


A partir de ella ANTLR podrá reconocer expresiones como:

1 + 2 + 3


Un compilador suele dividirse en varias etapas:


Código Fuente

      │

      ▼

 Lexer

      │

      ▼

 Tokens

      │

      ▼

 Parser

      │

      ▼

 AST

      │

      ▼

 Análisis Semántico

      │

      ▼

 Generación de Código


ANTLR participa principalmente en las dos primeras:

  • Lexer
  • Parser


¿Qué hace el Lexer?

El Lexer toma una secuencia de caracteres y la divide en tokens.


Por ejemplo:

let age = 42


se transforma en:

LET

IDENTIFIER(age)

ASSIGN

NUMBER(42)


El Lexer no entiende el significado del programa. Solo identifica patrones.


¿Qué hace el Parser?

El Parser toma los tokens generados por el Lexer y verifica que respeten las reglas definidas por la gramática.


Por ejemplo:

LET IDENTIFIER ASSIGN NUMBER


podría convertirse en:

Assignment

 ├── Variable(age)

 └── Number(42)


Esta estructura representa la forma del programa y servirá como base para las siguientes etapas.


¿Qué es una gramática?

Una gramática define qué construcciones son válidas dentro de un lenguaje.


Por ejemplo:

variableDeclaration

    : 'let' IDENTIFIER '=' expression

    ;


Esta regla indica que una declaración válida debe contener:

  • La palabra reservada let
  • Un identificador
  • El símbolo =
  • Una expresión


¿ANTLR genera compiladores?

No.


ANTLR resuelve principalmente:

  • Análisis léxico
  • Parsing


Todavía debemos implementar:

  • Árboles sintácticos abstractos (AST)
  • Tablas de símbolos
  • Sistema de tipos
  • Generación de bytecode JVM


Por eso ANTLR es una herramienta muy importante, pero representa solo una parte del compilador completo.


viernes, 19 de junio de 2026

Java 26: LazyConstant


Una de las novedades de Java 26 es LazyConstant, una API experimental que permite definir constantes inicializadas bajo demanda (*lazy initialization*) de forma segura y eficiente.


Supongamos que tenemos un objeto costoso de crear:


public class Config {

    private static final ExpensiveObject INSTANCE =

            new ExpensiveObject();

}


El objeto se crea cuando la clase es cargada, aunque nunca llegue a utilizarse.

Una solución clásica es utilizar el patrón Initialization-on-demand holder:


public class Config {


    private static class Holder {

        static final ExpensiveObject INSTANCE =

                new ExpensiveObject();

    }


    public static ExpensiveObject instance() {

        return Holder.INSTANCE;

    }

}


Funciona, pero es un patrón poco evidente y algo verboso.

Con Java 26 podemos declarar un valor que será calculado únicamente la primera vez que se necesite:


private static final LazyConstant<ExpensiveObject> INSTANCE =

        LazyConstant.of(ExpensiveObject::new);


y obtenerlo mediante:


ExpensiveObject obj = INSTANCE.get();


La instancia se crea una sola vez y es segura para múltiples hilos.


Veamos un ejemplo:


class DatabaseConnection {


    private static final LazyConstant<ConnectionPool> POOL =

            LazyConstant.of(ConnectionPool::new);


    static ConnectionPool pool() {

        return POOL.get();

    }

}


La conexión se abrirá únicamente cuando alguien invoque:


DatabaseConnection.pool();


¿Qué ventajas tiene?

  • Inicialización diferida (lazy initialization).
  • Thread-safe.
  • Evita implementar patrones complejos.
  • Más legible que usar synchronized, volatile o double-checked locking.
  • Puede reemplazar muchos usos de AtomicReference.


LazyConstant está pensado para recursos costosos o poco utilizados, donde tiene sentido retrasar su creación.

Durante años, Java obligó a utilizar patrones como el Holder idiom o double-checked locking para conseguir inicialización perezosa segura. Java 26 introduce LazyConstant para convertir ese patrón en una característica del lenguaje y la biblioteca estándar, haciendo el código más simple, legible y menos propenso a errores.


miércoles, 17 de junio de 2026

¿Por qué Clojure compila diferente? Ventajas y desventajas de ser un "Hosted Language" en la JVM


Cuando pensamos en lenguajes para la JVM, solemos meter en la misma bolsa a Java, Scala, Kotlin y Clojure. Después de todo, todos terminan ejecutándose sobre la misma máquina virtual.


Pero hay una diferencia fundamental en cómo están construidos.


Scala y Kotlin generan bytecode JVM de manera bastante directa. Clojure, en cambio, adopta una filosofía distinta: es un hosted language, es decir, un lenguaje "hospedado" sobre la plataforma Java.


¿Y qué significa eso? ¿Tiene ventajas? ¿Tiene costos?

Lenguajes como Java, Scala o Kotlin siguen aproximadamente este esquema:


Código fuente

      ↓

Compilador

      ↓

Bytecode JVM (.class)

      ↓

JVM


Muchas características del lenguaje se traducen directamente en clases, métodos y bytecode especializado.


Por ejemplo, una case class de Scala:

case class Person(name: String)

genera automáticamente métodos como:

  • equals()
  • hashCode()
  • copy()
  • toString()


Todo esto queda representado directamente en bytecode.


Clojure también produce bytecode JVM, pero gran parte del comportamiento del lenguaje vive en su runtime:


Código Clojure

      ↓

Compilador

      ↓

Bytecode + Runtime de Clojure

      ↓

JVM


Por eso Rich Hickey describe a Clojure como un hosted language.


La plataforma Java no es solamente un destino de compilación: es el ecosistema sobre el cual está construido el lenguaje.


Ventaja: Aprovecha toda la plataforma Java


Desde Clojure podemos usar directamente cualquier clase Java:


(import java.time.LocalDate)

(LocalDate/now)


No hay puentes especiales ni adaptadores.

Clojure reutiliza:

  • la JVM;
  • el recolector de basura;
  • las bibliotecas Java;
  • los hilos;
  • las excepciones;
  • las herramientas de profiling;
  • todo el ecosistema existente.


En vez de reinventar la rueda, se apoya en ella.


Ventaja: Un compilador relativamente pequeño


Muchas características importantes de Clojure viven en bibliotecas y no en el compilador:

  • secuencias perezosas;
  • colecciones persistentes;
  • STM;
  • transducers.


Esto hace que el compilador sea considerablemente más sencillo que los de Scala o Kotlin.


Ventaja: Desarrollo interactivo y REPL


Una de las mayores fortalezas de Clojure es su modelo de desarrollo interactivo.

Podemos definir una función:


(defn cuadrado [x]

  (* x x))


y cargarla inmediatamente en la REPL, sin recompilar todo el proyecto.


Este enfoque favorece:

  • feedback rápido;
  • hot reloading;
  • experimentación;
  • desarrollo incremental.


Ventaja: Evolución mediante librerías


Muchas características avanzadas de Clojure se agregaron sin modificar demasiado el lenguaje:

  • transducers;
  • core.async;
  • spec;
  • STM.


Al depender más del runtime y menos del compilador, la evolución del ecosistema suele ser más flexible.


Las desventajas

Menos optimizaciones

Scala y Kotlin generan bytecode más especializado.

En Clojure, muchas operaciones pasan por componentes del runtime como:

  • IFn;
  • PersistentVector;
  • PersistentMap;
  • RT.


Esto introduce niveles adicionales de indirección y, en ciertos casos, puede afectar el rendimiento.


Más reflexión

Si escribimos:

(defn largo [s]

  (.length s))


el compilador puede recurrir a reflexión.

Podemos ayudarlo con type hints:


(defn largo [^String s]

  (.length s))


pero esto requiere intervención del programador.


Menos comprobaciones en tiempo de compilación


Scala y Kotlin verifican muchas cosas antes de ejecutar:

  • tipos;
  • nulabilidad;
  • exhaustividad del pattern matching;
  • restricciones genéricas.


Clojure, al ser dinámico, detecta muchos errores recién en tiempo de ejecución.


Interoperabilidad asimétrica

Desde Clojure usar Java es muy fácil.

Pero desde Java consumir código Clojure no es tan natural:


IFn plus = Clojure.var("myns", "plus");


Mientras que una clase escrita en Scala o Kotlin suele verse como una clase Java convencional.


Dos filosofías distintas

No es que una aproximación sea mejor que la otra.

Scala y Kotlin intentan enriquecer la JVM mediante compiladores sofisticados y bytecode especializado.

Clojure adopta otra filosofía: construir un lenguaje pequeño y dinámico que reutilice al máximo la plataforma existente.

Al final, la diferencia más interesante no es a qué compilan, porque todos terminan ejecutándose en la JVM.

La verdadera diferencia es: ¿Cuánto del lenguaje vive en el compilador y cuánto vive en el runtime?

Y en esa decisión de diseño está gran parte de la personalidad de Clojure.


lunes, 15 de junio de 2026

JDBI en Java: acceso a datos simple


Cuando hablamos de acceso a bases de datos en Java, solemos pensar en JDBC puro o en frameworks pesados como Hibernate.

Pero existe una opción intermedia muy interesante: Jdbi.


JDBI busca mantener la simplicidad y el control de SQL, pero eliminando gran parte del código repetitivo de JDBC.


¿Qué es JDBI?

JDBI es una librería que se construye sobre JDBC y agrega:

  • Mapeo automático de objetos
  • Queries más limpias
  • Manejo simplificado de parámetros
  • Integración con transacciones
  • APIs modernas y funcionales


La idea es poder escribir SQL real sin tener que pelear con ResultSet, PreparedStatement y toneladas de try/catch.


Dependencia Maven:

<dependency>

    <groupId>org.jdbi</groupId>

    <artifactId>jdbi3-core</artifactId>

    <version>3.49.6</version>

</dependency>


Si usamos una base de datos específica, agregamos el driver correspondiente.


Por ejemplo para MySQL:

<dependency>

    <groupId>com.mysql</groupId>

    <artifactId>mysql-connector-j</artifactId>

</dependency>


Crear una conexión:

Jdbi jdbi = Jdbi.create(

    "jdbc:mysql://localhost:3306/test",

    "root",

    "password"

);


Ejecutar una consulta


Supongamos esta tabla:

CREATE TABLE users (

    id BIGINT PRIMARY KEY,

    name VARCHAR(100),

    age INT

);


Y esta clase:


public class User {

    private Long id;

    private String name;

    private Integer age;


    // getters y setters

}


Ahora podemos consultar así:


List<User> users = jdbi.withHandle(handle ->

    handle.createQuery("SELECT * FROM users")

          .mapToBean(User.class)

          .list()

);


Fijate que desaparece todo el manejo manual de ResultSet.


Insertar datos:


jdbi.useHandle(handle ->

    handle.createUpdate("""

        INSERT INTO users(id, name, age)

        VALUES(:id, :name, :age)

    """)

    .bind("id", 1L)

    .bind("name", "Emanuel")

    .bind("age", 30)

    .execute()

);


Parámetros nombrados

Una de las mejores cosas de JDBI es evitar los clásicos:


statement.setString(1, ...);

statement.setInt(2, ...);


En su lugar:

.bind("name", "Juan")


Mucho más legible.


JDBI también permite definir interfaces tipo DAO.


public interface UserDao {


    @SqlQuery("SELECT * FROM users WHERE id = :id")

    User findById(@Bind("id") Long id);


    @SqlUpdate("""

        INSERT INTO users(id, name, age)

        VALUES(:id, :name, :age)

    """)

    void insert(

        @Bind("id") Long id,

        @Bind("name") String name,

        @Bind("age") Integer age

    );

}


Y luego:


UserDao dao = jdbi.onDemand(UserDao.class);

User user = dao.findById(1L);


¿Por qué mucha gente lo elige?

Porque queda en un punto muy cómodo. No es tan automático como JPA y permite hacer optimizaciones fácilmente, pero no es tan trabajoso como JDBC. 


JDBI se integra muy bien con Spring Framework.

Dependencia:

<dependency>

    <groupId>org.jdbi</groupId>

    <artifactId>jdbi3-spring5</artifactId>

    <version>3.49.6</version>

</dependency>


Configuración:


@Configuration

public class JdbiConfig {


    @Bean

    public Jdbi jdbi(DataSource dataSource) {

        Jdbi jdbi = Jdbi.create(dataSource);

        jdbi.installPlugin(new SqlObjectPlugin());

        return jdbi;

    }

}


Crear el DAO como Bean


@Configuration

public class DaoConfig {


    @Bean

    public UserDao userDao(Jdbi jdbi) {

        return jdbi.onDemand(UserDao.class);

    }

}


Usarlo desde un Service


@Service

public class UserService {

    private final UserDao userDao;

    public UserService(UserDao userDao) {

        this.userDao = userDao;

    }


    public User getUser(Long id) {

        return userDao.findById(id);

    }

}


¿Cuándo conviene usar JDBI?

JDBI suele ser una muy buena opción cuando:

  • querés escribir SQL manualmente
  • necesitás buena performance
  • no querés la complejidad de Hibernate
  • trabajás con consultas complejas
  • querés algo más moderno que JDBC puro


Especialmente en microservicios o aplicaciones donde el modelo relacional es importante, JDBI puede resultar mucho más simple y transparente que un ORM completo.


Dejo link: https://jdbi.org


domingo, 14 de junio de 2026

Mundial de Lenguajes de Programación



Le pregunte a la IA que onda si hacemos partidos de diferentes tecnologías con diferentes lenguajes, y este fue el resultado : 



Partido 1: Aplicaciones Web

Java vs C#


🇯🇵 Java sale con Spring Boot.

🇺🇸 C# responde con ASP.NET Core.


Ambos tienen experiencia internacional, planteles maduros y una enorme cantidad de títulos.

Resultado: Empate 1-1.


> Si tenés que hacer una API corporativa, cualquiera de los dos te lleva a octavos.


 🏟️ Partido 2: Concurrencia

Elixir vs Go


🇧🇷 Elixir apuesta por la posesión y el juego asociado con actores y supervisores.

🇩🇪 Go juega más directo con goroutines y channels.


Elixir domina el mediocampo cuando aparecen millones de procesos concurrentes.

Resultado: Elixir 2 - Go 1.

La BEAM sigue siendo una de las ligas más difíciles de enfrentar.


 🏟️ Partido 3: Aplicaciones Standalone

C++ vs Rust


🇮🇹 C++ es el veterano con ocho mundiales encima.

🇳🇱 Rust llega con una generación joven y una defensa impecable.


Rust reduce errores, pero C++ todavía tiene una experiencia enorme.

Resultado: C++ 1 - Rust 1.

La leyenda sigue vigente, pero la promesa pide pista.


 🏟️ Partido 4: Cloud

Java vs Node.js


🇦🇷 Java viene con GraalVM y Spring Boot.

🇪🇸 Node.js juega rápido y con poco peso.


En microservicios y aplicaciones cloud modernas, ambos se sienten cómodos.

Resultado: Empate.

El VAR determina que la elección depende más del equipo que del lenguaje.


🏟️ Partido 5: Inteligencia Artificial

Python vs el resto del mundo


🇫🇷 Python entra a la cancha.

Resultado: Python 5 - 0.

Hubo partido durante cinco minutos.


 🏟️ Partido 6: Programación Funcional

Scala vs Haskell

🇺🇾 Scala combina técnica y pragmatismo.

🇭🇷 Haskell juega un fútbol hermoso que pocos entienden.


Resultado: Scala 2 - Haskell 1.

La posesión fue 90% para Haskell, pero Scala convirtió las que tuvo.


🏟️ Partido 7: Frontend

TypeScript vs JavaScript


🇵🇹 JavaScript es el capitán histórico.

🇵🇹 TypeScript es el mismo equipo, pero con preparador físico.

Resultado: TypeScript 3 - JavaScript 1.

 Los errores en tiempo de compilación fueron la figura del partido.


 🏟️ Partido 8: Bases de Datos

PostgreSQL vs MySQL


🇨🇴 PostgreSQL juega con mucha técnica.

🇲🇽 MySQL apuesta por la experiencia.


Resultado: PostgreSQL 2 - MySQL 1.

PostgreSQL ganó por posesión y calidad de juego.


Y listo, no hice mucho, ni dice mucho pero es divertido :P 

FFM API en Java: Accediendo a Memoria Nativa y Código Nativo sin JNI


La FFM API (Foreign Function & Memory API) es una de las incorporaciones más interesantes de Java moderno.


Su objetivo es permitir que una aplicación Java pueda:

  • Llamar funciones escritas en C.
  • Acceder a memoria fuera del heap de la JVM.
  • Reemplazar gran parte del uso de JNI.
  • Interactuar con librerías nativas de forma más segura y eficiente.


La FFM API forma parte del proyecto Project Panama.

Pero demos un paso para atras,  ¿Por qué existía JNI?


Durante años, si una aplicación Java necesitaba:

  • acceder a una librería en C,
  • usar código del sistema operativo,
  • trabajar con drivers,
  • o ejecutar operaciones de bajo nivel,

la única opción era usar JNI (Java Native Interface)


El problema es que JNI:

  • es complejo,
  • verboso,
  • inseguro,
  • difícil de debuggear,
  • y propenso a crashes de la JVM.


Además, obliga a escribir:

  • código Java,
  • código C,
  • headers,
  • compilación nativa,
  • glue code.


Puff si no habre puteado con JNI.


¿Qué propone la FFM API?

La FFM API permite hacer esto:

printf("Hola desde C!\n");


...directamente desde Java.


Y además:

  • manejar memoria nativa,
  • mapear estructuras,
  • trabajar con punteros,
  • invocar funciones dinámicamente.


Todo usando una API moderna.


La FFM API tiene dos pilares:

1. Foreign Function


Permite invocar funciones nativas.


Por ejemplo:

  • printf
  • strlen
  • malloc
  • funciones de librerías C


2. Foreign Memory

Permite manejar memoria fuera del heap de Java.


Esto es importante porque:

  • la JVM normalmente controla toda la memoria,
  • pero muchas librerías nativas usan memoria propia.


La FFM API permite trabajar con esa memoria de manera segura.


Primer ejemplo: llamar a printf


import java.lang.foreign.*;

import java.lang.invoke.MethodHandle;

import static java.lang.foreign.ValueLayout.*;


public class Main {


    public static void main(String[] args) throws Throwable {

        Linker linker = Linker.nativeLinker();

        SymbolLookup stdlib = linker.defaultLookup();


        MemorySegment printfAddress =

                stdlib.find("printf")

                      .orElseThrow();


        FunctionDescriptor printfSignature =

                FunctionDescriptor.of(JAVA_INT, ADDRESS);


        MethodHandle printf = linker.downcallHandle(

                printfAddress,

                printfSignature

        );


        try (Arena arena = Arena.ofConfined()) {

            MemorySegment text =

                    arena.allocateFrom("Hola desde C!\n");


            printf.invoke(text);

        }

    }

}


¿Qué está pasando acá?


Linker linker = Linker.nativeLinker();

Obtiene un linker capaz de interactuar con funciones nativas.


SymbolLookup stdlib = linker.defaultLookup();

Busca símbolos exportados por librerías nativas.


stdlib.find("printf")

Obtiene la dirección de memoria de la función.

Muy parecido a: void* ptr = dlsym(...)


FunctionDescriptor.of(JAVA_INT, ADDRESS)

Describe la firma de la función.

En este caso: int printf(char*)


linker.downcallHandle(...)

Crea un MethodHandle que permite invocar la función nativa.


Manejo de memoria con Arena

Uno de los conceptos más importantes es:

Un Arena administra memoria nativa.


try (Arena arena = Arena.ofConfined()) {


}


Cuando el bloque termina:

  • la memoria se libera automáticamente.
  • Esto evita muchísimos memory leaks.


La memoria nativa se representa con: MemorySegment


Es básicamente:

  • un bloque de memoria,
  • con límites,
  • seguro,
  • y controlado.


Y para reservar memoria hacemos: 

MemorySegment segment = arena.allocate(4);

Por ejemplo escribir y leer un entero: 

segment.set(JAVA_INT, 0, 42);

int value = segment.get(JAVA_INT, 0);


Supongamos esta estructura:

struct Point {

    int x;

    int y;

};


Podemos modelarla en Java.

StructLayout POINT = MemoryLayout.structLayout(

        JAVA_INT.withName("x"),

        JAVA_INT.withName("y")

);


Y para reservar memoria hacemos:

MemorySegment point = arena.allocate(POINT);


Escribir los campos:


point.set(JAVA_INT, 0, 10);

point.set(JAVA_INT, 4, 20);


¿Qué ventajas tiene?

Menos complejidad


No hace falta:

  • generar headers,
  • compilar JNI,
  • usar glue code.


¿Puede FFM API reemplaza completamente JNI?

Todavía no en todos los casos.

Hay escenarios avanzados donde JNI sigue siendo necesario.


Pero para muchísimos casos:

  • bindings simples,
  • acceso a librerías,
  • llamadas nativas,
  • estructuras,


la FFM API es mucho mejor.



miércoles, 10 de junio de 2026

Java Streams vs LINQ


A simple vista, Java y C# parecen ofrecer lo mismo:


users.stream()

     .filter(u -> u.getAge() > 50)

     .toList();


users.Where(u => u.Age > 50).ToList();


  • Sintaxis similar
  • Misma intención


Pero internamente… son dos mundos completamente distintos.

  • En Java, una lambda es código ejecutable
  • En LINQ, puede ser datos (un AST)


Y eso cambia todo.


En Java, esto:

Predicate<User> p = u -> u.getAge() > 50;


Se compila a bytecode usando invokedynamic

Es equivalente a:


class Lambda implements Predicate<User> {

    public boolean test(User u) {

        return u.getAge() > 50;

    }

}


No hay forma de saber:

  • qué propiedad se accede (age)
  • qué operador se usa (>)
  • qué constante (50)

Solo podés ejecutarla, no analizarla

Por lo tanto, siempre ocurre en memoria. Nunca puede transformarse en sql o en nada


En .NET LINQ, esto:

users.Where(u => u.Age > 50)


Puede ser interpretado como:

Expression<Func<User, bool>>


Esto NO es una función

Es un árbol de expresión (AST)


¿Cómo se ve ese AST?

Conceptualmente:

GreaterThan

 ├── MemberAccess (Age)

 └── Constant (50)


O en código:

BinaryExpression(

    left: MemberExpression("Age"),

    operator: GreaterThan,

    right: Constant(50)

)


Ahora sí podés:

  • traducir a SQL
  • optimizar
  • serializar
  • ejecutar en distintos motores


¿Por qué Java eligió este camino?

Java priorizó:

  • simplicidad
  • performance
  • compatibilidad


Las lambdas fueron diseñadas como:  “syntactic sugar” sobre interfaces funcionales

No como estructuras analizables.


 ¿Se podría implementar LINQ en Java?

Sí… pero requeriría:

  • lambdas introspectables
  • un AST estándar en el lenguaje
  • cambios en el compilador


Streams y LINQ no son equivalentes.

  • Streams son pipelines de ejecución
  • LINQ es un lenguaje de queries embebido

domingo, 7 de junio de 2026

¿Se puede tener Java Linq?


Sí, podría… pero no “gratis” ni sin romper varias cosas del lenguaje como hoy está diseñado.

La clave es entender qué significa realmente “expresión” al estilo LINQ.


Primero: ¿qué es una “expresión” en LINQ?


En .NET LINQ no estás pasando una función común.

x => x.Age > 50


Eso puede ser dos cosas distintas en C#:


Func<User, bool>                 // código ejecutable

Expression<Func<User, bool>>    // árbol de expresión (AST)


En el segundo caso, el compilador no genera bytecode directo, sino una estructura tipo:


GreaterThan

 ├── MemberAccess (Age)

 └── Constant (50)


En Java eso NO existe

x -> x.getAge() > 50


Siempre es: Predicate<User>


Y eso es:

  • bytecode
  • opaco
  • no analizable


Entonces… ¿por qué Java no puede simplemente agregarlo?

Puede… pero implicaría cambios profundos 


1. Cambiar cómo funcionan las lambdas

Hoy en Java:

Predicate<User> p = x -> x.getAge() > 50;



 Se compila usando invokedynamic

Es básicamente una función


Para soportar expresiones, Java necesitaría algo como:

Expression<User, Boolean> expr = x -> x.getAge() > 50;


Eso implica que el compilador:

  • capture la estructura del código
  • no solo lo ejecute


2. Falta un AST estándar en el lenguaje

Java no tiene algo como:

expr.getBody(); // árbol de nodos


Tendrías que definir:

  • nodos (BinaryExpression, MethodCall, etc.)
  • un modelo estándar
  • APIs para recorrerlo

Básicamente: meter un mini compilador dentro del lenguaje


3. Problema de ambigüedad (esto es clave)


¿Qué pasa con esto?

x -> x.getAge() > calcularEdadMinima()


¿Eso es SQL?

 ❌ No siempre

 ❌ Puede depender de lógica Java

 ❌ Puede tener efectos secundarios


LINQ tiene reglas estrictas para esto… Java no.


4. Compatibilidad hacia atrás (el verdadero monstruo)

Java es ultra conservador.

Si agregás expresiones:

  • ¿cómo diferenciás `Predicate` vs `Expression`?
  •  ¿rompés código existente?
  • ¿cómo inferís tipos?


C# lo resolvió desde el diseño… Java llegó tarde (lambdas en Java 8).


5.  Filosofía del lenguaje

Java prioriza:

  • simplicidad
  • previsibilidad
  • menos “magia”


LINQ introduce:

  • traducción implícita
  • múltiples targets (SQL, XML, memoria)
  • comportamiento distinto según contexto


Eso no encaja del todo con el ADN de Java


Entonces… ¿es imposible?

No. Para nada.


De hecho, Java ya hace algo parecido, pero explícito:

  • JPA Criteria API
  • Querydsl
  • jOOQ


Todos construyen un AST… pero a mano


La posta (la diferencia real)

No es que Java “no pueda”.


Es que hoy:

  • Las lambdas en Java son comportamiento
  • En LINQ son datos


 Conclusión

Java podría implementar algo tipo LINQ si:

  • introduce un tipo `Expression`
  • cambia el compilador
  • define un AST estándar
  • acepta más complejidad en el lenguaje


Pero eso implicaría:

cambiar una de las decisiones fundamentales de cómo Java entiende las lambdas



viernes, 5 de junio de 2026

¿Por qué Java Streams no pueden ser LINQ?


Imaginá esto:

repoPersona

    .filter(p -> p.getEdad() > 50)

    .toList();



Y que mágicamente se transforme en:

SELECT * FROM persona WHERE edad > 50;


Sería hermoso.

Pero… ¿no son lazy los streams?

Sí, los streams en Java son lazy:


users.stream()

     .filter(u -> u.getAge() > 50)

     .toList();


No se ejecuta hasta el final


Peeeero...

Lazy no es lo mismo que interpretable


En Java, esto:

u -> u.getAge() > 50


Se compila a bytecode

No queda como estructura accesible


Es decir:

  • No podés analizarla
  • No podés transformarla
  • No podés convertirla en SQL


 ¿Por qué en C# sí se puede?


En .NET LINQ pasa algo distinto:

repo.Where(x => x.Age > 50)


Eso no es una función normal

Es: Expression<Func<T, bool>>


El código es datos (un AST)


Por esta razón necesitamos de cosas como Specification de jpa para tipar nuestras querys :(


Java Streams no pueden ser LINQ porque:  Java no puede “leer” lambdas como datos


Y eso obliga a usar soluciones como Specifications.


miércoles, 3 de junio de 2026

Jakarta Persistence 4.0: El mayor salto de JPA en años



Durante mucho tiempo, cuando hablábamos de persistencia en Java, hablábamos de JPA. Con la migración de Java EE a Jakarta EE, JPA pasó a llamarse Jakarta Persistence, pero durante varias versiones los cambios fueron relativamente modestos.

Con Jakarta Persistence 4.0 eso cambia.

Esta nueva versión incorpora características largamente esperadas, mejora la seguridad de tipos y prepara el terreno para trabajar junto a Jakarta Data. Veamos qué trae de nuevo.


¿Qué es Jakarta Persistence?

Jakarta Persistence es la especificación estándar para el mapeo objeto-relacional (ORM) en Java.

Permite representar tablas como objetos Java y realizar operaciones CRUD sin escribir SQL para cada interacción.


@Entity

public class Cliente {


    @Id

    private Long id;

    private String nombre;

}


Implementaciones populares incluyen:

  • Hibernate ORM
  • EclipseLink


Novedades más importantes de Jakarta Persistence 4.0:

1. EntityAgent: trabajar sin Persistence Context

Una de las novedades más llamativas es la incorporación de EntityAgent.

Hasta ahora, casi todas las operaciones pasaban por un EntityManager y un Persistence Context.


entityManager.persist(cliente);


Con EntityAgent se pueden realizar operaciones sobre entidades desacopladas del contexto de persistencia, simplificando ciertos escenarios y reduciendo overhead. 


Conceptualmente:

entityAgent.insert(cliente);


Esto acerca la API a modelos más ligeros y modernos.


2. Carga masiva por ID

Un problema clásico:


for(Long id : ids) {

    entityManager.find(Cliente.class, id);

}


Esto puede generar el famoso problema N+1.

Jakarta Persistence 4.0 incorpora soporte para obtener múltiples entidades por identificador en una sola operación. 


La ventaja:

  • Menos viajes a la base de datos.
  • Mejor rendimiento.
  • Código más simple.


3. Consultas más seguras con Static Query

Una crítica frecuente a JPA era que las consultas JPQL eran simples Strings.


@Query("select c from Cliente c")


Si el nombre de una propiedad cambiaba, el error aparecía recién en tiempo de ejecución.

Persistence 4.0 incorpora una API de Static Queries que permite mayor verificación en compilación. 


Beneficios:

  • Más seguridad de tipos.
  • Menos errores en producción.
  • Mejor soporte para herramientas y refactorización.


4. Result Set Mapping programático

Hasta ahora era habitual definir mapeos complejos mediante anotaciones.


@SqlResultSetMapping(...)


La nueva versión agrega una API programática para definir estos mappings. 


Ventajas:

  • Más flexible.
  • Más reutilizable.
  • Menos anotaciones gigantes.


5. Nuevas capacidades para Entity Graphs

Los Entity Graphs fueron introducidos para controlar qué relaciones se cargan.


@EntityGraph(attributePaths = {

    "pedidos",

    "direccion"

})


Persistence 4.0 mejora significativamente esta funcionalidad:

Nuevas anotaciones.

Mejor integración con operaciones existentes.

Uso de Entity Graphs en refresh(). 


6. Soporte para entidades Read-Only

En muchos escenarios solo queremos leer datos.

Antes:


Cliente cliente =

    entityManager.find(Cliente.class, id);


La entidad quedaba administrada por el contexto.

Persistence 4.0 agrega soporte explícito para carga en modo solo lectura. 


Beneficios:

  • Menor consumo de memoria.
  • Menor costo de dirty checking.
  • Mejor rendimiento.


7. @PreMerge

Hasta ahora existían callbacks como:


@PrePersist

@PostPersist

@PreUpdate

@PostUpdate



Ahora aparece:

@PreMerge

public void beforeMerge() {

    System.out.println("Fusionando entidad");

}


Esto permite interceptar el proceso de merge antes de que ocurra. 


8. Excluir campos del Optimistic Locking

Hasta ahora cualquier modificación podía afectar el control de versiones.

Persistence 4.0 incorpora una anotación para excluir ciertos atributos del mecanismo de optimistic locking. 


Ideal para:

  • Campos calculados.
  • Contadores.
  • Metadatos auxiliares.


9. select new implícito

Actualmente:


select new ClienteDTO(

    c.id,

    c.nombre

)

from Cliente c


Persistence 4.0 simplifica este escenario permitiendo inferir automáticamente el DTO cuando se especifica el tipo de resultado. 


10. Preparándose para Jakarta Data

Quizás el cambio más importante no sea una característica puntual.

Muchas de las nuevas APIs fueron diseñadas para trabajar mejor con Jakarta Data

La idea es ofrecer una experiencia similar a:

  • Spring Data
  • Micronaut Data
  • Quarkus Panache


pero de forma estandarizada dentro del ecosistema Jakarta EE. 


¿Vale la pena actualizar?

  • Si estás usando:
  • Hibernate moderno
  • Jakarta EE 11 o futuro Jakarta EE 12
  • Nuevos proyectos


la respuesta es sí.


Las mejoras apuntan a problemas reales que los desarrolladores vienen sufriendo desde hace años:

  • Más type safety
  • Menos Strings mágicos
  • Mejor rendimiento
  • Mejor integración con Jakarta Data
  • APIs más modernas


Jakarta Persistence 4.0 representa probablemente la evolución más importante de JPA desde la llegada de los Entity Graphs y los Stored Procedures.

La incorporación de EntityAgent, Static Queries, Result Set Mapping programático, mejoras en Entity Graphs y nuevas capacidades de carga hacen que la especificación se vea mucho más moderna y preparada para competir con las soluciones de persistencia actuales. 

Para quienes pensaban que JPA había quedado estancado, Jakarta Persistence 4.0 demuestra exactamente lo contrario.


Dejo link: 

https://jakarta.ee/specifications/persistence/4.0/



lunes, 1 de junio de 2026

Problema: Java consumiendo mucho CPU (sin GUI ni puertos)


Si no podés usar VisualVM ni JMX remoto, la estrategia correcta es:

CLI + correlación de datos


1. Encontrar el proceso

jps -l

o directamente:

ps aux | grep java

guardate el PID


2. Confirmar que el problema es CPU

top -p <pid>

o mejor:

top -H -p <pid>

Esto muestra threads individuales


Acá está la clave:

  • Vas a ver un thread con CPU alto (ej: 200%)
  • Anotá el TID (thread id)


3. Convertir TID a HEX (clave para Java)


Java muestra threads en hexadecimal, Linux en decimal.

printf "%x\n" <tid>


Ejemplo:

12345 → 3039


4.Sacar thread dump

jstack <pid> > dump.txt


5. Buscar el thread problemático


Dentro del dump:

grep -i 3039 dump.txt


Ese es el thread que está consumiendo CPU

¿Qué vas a encontrar? Generalmente algo así:

"id=123 nid=0x3039 runnable"


Y abajo el stack:

at com.miapp.CalculoPesado.procesar(CalculoPesado.java:42)

at com.miapp.Service.loop(Service.java:88)


¡encontraste el problema!


¿Dónde entra jstat en todo esto?

Acá es donde sumás contexto.


Ejecutá:

jstat -gcutil <pid> 1000


 Qué mirar para CPU alto


Caso 1: GC excesivo

  • YGC sube MUY rápido
  • GCT crece constantemente


CPU alto por Garbage Collector


Caso 2: Full GC frecuentes

  • FGC aumenta
  • FGCT alto


pausas pesadas → CPU + latencia


Caso 3: GC normal

  • GC estable
  • memoria OK


Entonces el problema es código, no GC

domingo, 31 de mayo de 2026

jstat en Java: cómo entender el Garbage Collector desde la consola


Cuando hablamos de monitoreo en Java, muchos van directo a VisualVM.

Pero en entornos reales (servidores, contenedores, CI), necesitás herramientas sin GUI. Ahí aparece jstat.

Una herramienta liviana, incluida en la JVM, que te permite ver qué está pasando con la memoria y el Garbage Collector en tiempo real.

jstat (Java Virtual Machine Statistics Monitoring Tool) es una utilidad de consola que permite consultar métricas internas de la JVM.

  • Uso de memoria
  • Actividad del GC
  • Cantidad de recolecciones
  • Tiempos acumulados


Primero necesitás el PID del proceso:


jps -l


Después ejecutás:


jstat -gc <pid> 1000


Esto imprime estadísticas cada 1 segundo.


 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU     YGC   YGCT   FGC    FGCT     GCT

1024.0 1024.0  0.0   512.0   8192.0   4096.0   16384.0   8000.0   5120.0 4800.0 1024.0  900.0     10    0.25    2     0.50     0.75


Ahora lo importante: entender esto 👇

Generación joven (Young Gen)


S0C / S1C → tamaño de Survivor 0 y 1

S0U / S1U → uso actual

EC → tamaño de Eden

EU → uso de Eden

Acá viven los objetos nuevos.


Generación vieja (Old Gen)

OC → capacidad

OU → uso


👉 Si esto crece mucho y no baja → posible problema de memoria.


Metaspace

MC / MU → capacidad y uso

CCSC / CCSU → Compressed Class Space


👉 Relacionado con clases cargadas.


YGC → cantidad de GC jóvenes

YGCT → tiempo total en GC joven

FGC → cantidad de Full GC

FGCT → tiempo total en Full GC

GCT → tiempo total de GC


Veamos un caso saludable

  • Eden sube y baja constantemente
  • YGC aumenta progresivamente
  • Old Gen estable


👉 Todo OK.


Posible memory leak

  • OU (Old Gen) sube y nunca baja
  • Full GC frecuentes


👉 Algo está reteniendo objetos.


🟠 Problema de GC

  • YGC muy alto en poco tiempo
  • GCT creciendo rápido


👉 La app está gastando demasiado tiempo en GC.


⚙️ Otros modos útiles

-gcutil (más simple y legible)


jstat -gcutil <pid> 1000


Salida en porcentajes:

 S0     S1     E      O      M     CCS    YGC   YGCT   FGC   FGCT     GCT

 0.00  50.00  60.00  48.00  93.75 87.89   10    0.25    2     0.50     0.75


👉 Ideal para monitoreo rápido.


Ejecutar N veces

jstat -gc <pid> 1000 10


👉 Ejecuta 10 veces y termina.


jstat es una herramienta simple pero extremadamente poderosa.


✔ Ideal para servidores sin GUI

✔ Perfecta para debugging en vivo

✔ Fundamental para entender el GC

lunes, 25 de mayo de 2026

¿Cómo monitorear la JVM desde la consola?


Cuando trabajamos con Java, una de las herramientas más conocidas para monitoreo es VisualVM.

Nos permite analizar memoria, CPU, hilos, GC y mucho más… todo desde una interfaz gráfica muy cómoda.

Pero hay un problema: ¿Qué pasa cuando estás en un servidor sin entorno gráfico?

Ahí es donde VisualVM ya no alcanza.


Java ya trae todo lo necesario… solo que más low level.


1. jps – listar procesos Java


jps -l


Salida típica:

1234 com.miapp.Main

5678 org.apache.catalina.startup.Bootstrap


Es el primer paso: obtener el PID.


2. jstat – métricas del GC


jstat -gc 1234 1000


Muestra:

  • uso de memoria
  • comportamiento del Garbage Collector
  • generaciones (Eden, Survivor, Old)


Ideal para detectar problemas de memoria en vivo.


3. jstack – dump de hilos


jstack 1234


Te da:

  • todos los threads
  • estados (RUNNABLE, BLOCKED, etc.)
  • posibles deadlocks


Clave para debugging de concurrencia.


4. jmap – análisis de memoria


jmap -heap 1234


O histogramas:


jmap -histo 1234


Podés ver qué objetos están ocupando memoria.


5. jcmd – el reemplazo moderno

Esta es la más importante.


jcmd 1234 help


Ejemplos:

jcmd 1234 GC.heap_info

jcmd 1234 Thread.print

jcmd 1234 GC.run


Es como un “super comando” que reemplaza a varios anteriores.


Veamos un ejemplo: 

En entornos reales se suele hacer esto:


1. Servidor (Linux sin GUI)

  • Aplicación Java corriendo
  • JMX habilitado


2. Diagnóstico:

  • CLI (jcmd, jstat, etc.)
  • o conexión remota desde VisualVM


Este enfoque es estándar en sistemas productivos.


VisualVM no es “la herramienta”, es solo un cliente visual.

Las verdaderas herramientas son:

  • APIs internas de la JVM
  • JMX
  • comandos como jcmd


VisualVM simplemente las consume y las muestra bonito.


martes, 5 de mayo de 2026

VisualVM vs .NET Tooling


¿Por qué en Java hay “una herramienta” y en .NET hay muchas?

Cuando venís del mundo Java, es muy común preguntarte: ¿Dónde está el VisualVM de .NET?


La respuesta corta es: no existe uno solo.

La respuesta interesante es: no existe por diseño.


En el ecosistema Java, herramientas como VisualVM se volvieron estándar porque la JVM expone un modelo bastante unificado:

  • JMX (Java Management Extensions)
  • Heap dumps
  • Thread dumps
  • GC metrics


Todo eso se puede consumir desde una sola herramienta.

¿Qué ofrece VisualVM?

  • Profiling de CPU
  • Análisis de memoria (heap)
  • Monitoreo de threads
  • Plugins

Resultado: una experiencia “tipo panel de control” donde ves todo.


En .NET moderno, el enfoque es distinto. En lugar de una herramienta central, tenés un ecosistema de herramientas especializadas.


Visual Studio (Profiler integrado)


Es lo más parecido a VisualVM:

  • CPU profiling
  • Memory profiling
  • Timeline
  • UI completa


Es la opción más “todo en uno”, pero:

❌ Es pesada

❌ No siempre usable en producción


CLI tools (el corazón real de .NET)


.NET adopta un enfoque más tipo Unix:

  • dotnet-counters → métricas en vivo
  • dotnet-trace → trazas de ejecución
  • dotnet-dump → dumps
  • dotnet-gcdump → GC
  • dotnet-stack → threads


Cada herramienta hace una cosa… pero la hace muy bien.

Esto permite:

  • Usarlas en producción
  • Automatizarlas
  • Integrarlas con pipelines


PerfView

  • Muy potente
  • Usado internamente por Microsoft
  • Basado en ETW (Event Tracing for Windows)


❌ Curva de aprendizaje alta

❌ UX poco amigable


Herramientas comerciales

  • dotTrace
  • ANTS Profiler
  • .NET Memory Profiler


Estas sí se sienten más como VisualVM:

✔️ UI amigable

✔️ Experiencia integrada

❌ Son pagas



sábado, 2 de mayo de 2026

Records vs Project Valhalla en Java


Java viene evolucionando fuerte en dos direcciones:

  • Java Records → escribir menos código
  • Project Valhalla → ejecutar más rápido


A primera vista parecen competir… pero en realidad se complementan.

  • Usá Records → claridad, simplicidad, APIs
  • Usá Valhalla (Value Types) → rendimiento, memoria
  • Usá ambos → lo ideal en sistemas complejos


Records: el rey de la simplicidad

record User(String name, int age) {}


✔ Inmutables

✔ Menos boilerplate

✔ equals, hashCode, toString automáticos


¿Cuándo usar Records?

DTOs / APIs


record ProductDTO(String name, double price) {}


Perfecto para:

  • REST APIs
  • serialización JSON
  • comunicación entre capas


Modelos simples

record Money(double amount, String currency) {}


Cuando te importa más la legibilidad que el rendimiento


Lógica de negocio

  • servicios
  • respuestas intermedias


El overhead de objetos no suele ser un problema


Limitación clave, un record:

  • vive en el heap
  • tiene identidad
  • usa referencias


Valhalla: el rey del rendimiento

Ejemplo conceptual:


value class Point {

    int x, y;

}



✔ Sin identidad

✔ Sin overhead de objeto

✔ Datos contiguos en memoria


¿Cuándo usar Value Types?

Grandes volúmenes de datos


value class Point {

    int x, y;

}


List<Point>


Mucho más eficiente que objetos tradicionales


Simulaciones

  • físicas
  • partículas
  • coordenadas


Miles o millones de instancias


Alto rendimiento

  • trading
  • analytics
  • cálculos intensivos


Menos GC = mejor performance


¿Qué cambia realmente?

Sin Valhalla:

List<Point> // lista de referencias


Con Valhalla:

[x1, y1, x2, y2, x3, y3]


Datos planos → mejor cache → más velocidad


Cuándo NO usar Valhalla

  • Entidades (`User`, `Order`)
  • Objetos con identidad
  • Integración con frameworks clásicos


Si necesitás identidad → no es value type

Lo mejor: combinarlos

Acá está la clave real 👇


Caso 1: Record + Value Type


value class Point {

    int x, y;

}


record Circle(Point center, double radius) {}


Tenés:

API clara (record)

datos eficientes (value)


Caso 2: separación por capas


// API

record PointDTO(int x, int y) {}


// Core

value class Point {

    int x, y;

}


Separás:

  • interfaz → legible
  • core → performante


Caso 3: colecciones grandes


value class Price {

    double amount;

}


List<Price>


Sin boxing → sin referencias → 🔥


Futuro: value records

Se viene algo así:

value record Point(int x, int y) {}


Combina:

  • simplicidad (record)
  • eficiencia (value type)


Conclusión, no es una pelea, es una estrategia:

  • Records → hacen feliz al desarrollador
  • Valhalla → hace feliz a la JVM



Java vs C#: Records y Value Types vs Records y Structs


Durante años, C# llevó ventaja en expresividad y control de memoria.

Pero con Java Records y Project Valhalla, Java está alcanzando… y en algunos aspectos, intentando superarlo.


👉 La pregunta es:

 ¿Java está copiando a C#… o lo está mejorando?


Empecemos por records: empate técnico

En C#

record Person(string Name);


En Java

record Person(String name) {}


✔ Inmutables

✔ Comparación por valor

✔ Menos boilerplate


Conclusión: Empate total — ambos lenguajes convergieron al mismo concepto


Value Types vs Structs

En C#: struct


struct Point {

    public int X;

    public int Y;

}


✔ Tipo por valor

✔ Generalmente en stack

✔ Muy eficiente


Problemas:

Boxing (se convierte en objeto)

Copias implícitas

Comportamiento a veces confuso


 En Java: Value Types (Valhalla)


value class Point {

    int x, y;

}


✔ Sin identidad

✔ Sin overhead de objeto

✔ Puede ser “flattened”


Y lo más importante: No cambia su naturaleza según el contexto


Diferencia clave

C# → tipo híbrido (a veces valor, a veces objeto)

Java → tipo consistente (siempre value type)


Punto para Java (a nivel diseño)


Memoria y layout

  • Stack vs Heap
  • Depende del contexto
  • Puede generar copias innecesarias


 Java (Valhalla)

  • Flattening automático
  • Mejor locality
  • Optimización por la JVM


Ejemplo mental:

En java esto: 

List<Point>


Podría ser:

[x1, y1, x2, y2, x3, y3]


Mucho más eficiente que referencias



Generics (el golpe fuerte de Java)


C#

List<int> 


Soportado desde hace años


Java (hoy)

List<Integer>  (boxing)


Java (con Valhalla)

List<int> ✔

List<Point> ✔


Sin boxing y sin referencias


Este es uno de los mayores avances de Java


Complejidad del modelo mental


En C#

  • class vs struct
  • boxing/unboxing
  • copias implícitas
  • Puede ser confuso


En Java

  • class vs value class
  • sin identidad vs con identidad
  • Más simple conceptualmente


Ecosistema y madurez

C#

✔ Ya está en producción

✔ Frameworks adaptados

✔ Casos reales


 Java (Valhalla)

⚠️ En desarrollo

⚠️ APIs en evolución

⚠️ Falta adopción


Hoy no podés usarlo en producción estándar



¿Quién lo hizo mejor?


Hoy: C#

Porque:

  • ya funciona
  • es estable
  • está probado


 A futuro: Java (posiblemente)

Porque:

  • evita errores históricos (boxing, copias)
  • diseño más consistente
  • mejor integración con la JVM



Proyecto Valhalla en Java: Tipos de Valor y el Futuro del Rendimiento


Cuando hablamos de la evolución de Java, uno de los proyectos más interesantes (y menos comprendidos) es Project Valhalla.


Si alguna vez sentiste que Java es “pesado” por culpa de los objetos… este proyecto apunta directamente a eso.

Project Valhalla es una iniciativa dentro de OpenJDK cuyo objetivo principal es introducir:

Tipos de valor (Value Types / Inline Classes)

Esto permite representar datos sin el costo de los objetos tradicionales.

En Java, casi todo es un objeto… y eso tiene consecuencias:


Integer x = 10;


Esto implica:

  • Heap allocation
  • Indirección (punteros)
  • Presión sobre el GC
  • Mala localización en memoria (cache-unfriendly)


Incluso algo simple como una lista de enteros:


List<Integer>


Es en realidad:

List -> referencias -> objetos Integer


¿Qué propone Valhalla?


Valhalla introduce un nuevo tipo de clases: Clases inline (value classes)


Ejemplo conceptual:


value class Point {

    int x;

    int y;

}


Esto permite:

  • Sin identidad (no == por referencia)
  • Sin overhead de objeto
  • Layout compacto en memoria


¿Por qué esto es importante?


Mejora de rendimiento

  • Menos uso de heap
  • Mejor uso de CPU cache
  • Menos GC


Estructuras más eficientes


Ejemplo:

List<Point>


Con Valhalla podría ser:

[ x1, y1, x2, y2, x3, y3 ]


👉 En lugar de:

[ ref -> Point, ref -> Point, ref -> Point ]


Conceptos clave de Valhalla


Value Objects

  • No tienen identidad, la identidad esta dada por su propio valor
  • Son inmutables (conceptualmente)
  • Se comparan por contenido


Flattening

El JVM puede “aplanar” estructuras:


class Line {

    Point p1;

    Point p2;

}


En memoria:

[x1, y1, x2, y2]


Specialized Generics (futuro)

Uno de los grandes problemas actuales:

List<int> ❌

List<Integer> ✅ (pero lento)


Valhalla busca permitir:

List<int> ✅


Sin boxing


Cambios importantes a tener en cuenta

  • No todo será automáticamente value type
  • Cambia el modelo mental (menos identidad, más datos)
  • Impacta librerías y frameworks


Project Valhalla aún está en desarrollo activo.

No es parte estable de Java todavía, pero ya hay:

  • Prototipos en OpenJDK
  • Features en incubación


Veamos un ejemplo: 


 Antes (Java actual)


class Complex {

    double re;

    double im;

}


  • Objeto en heap
  • Referencias


Con Valhalla (ideal)


value class Complex {

    double re;

    double im;

}


  • Datos contiguos
  • Sin overhead




Project Valhalla busca algo muy ambicioso:

Combinar lo mejor de OOP con la eficiencia de lenguajes de bajo nivel


En otras palabras:

  • Mantener la simplicidad de Java
  • Reducir el costo de abstracción


En C# tenemos el struct que es muy similar a lo que busca Valhalla.


martes, 21 de abril de 2026

Records vs Clases vs Lombok vs Kotlin vs Scala


¿Cuál es la mejor forma de modelar datos? Desde los Struct de c++ nos venimos preguntando esto. Vamos a ver algunas opciones modernas que nos provee la plataforma java. 

Cuando trabajamos con objetos que representan datos (DTOs, Value Objects, etc.), distintos lenguajes ofrecen soluciones para evitar el boilerplate.


En este post comparamos:

  • Records en Java
  • Clases tradicionales
  • Lombok
  • Data classes en Kotlin
  • Case classes en Scala


1. Clase tradicional (Java)


public class Persona {

    private final String nombre;

    private final int edad;


    public Persona(String nombre, int edad) {

        this.nombre = nombre;

        this.edad = edad;

    }


    public String getNombre() { return nombre; }

    public int getEdad() { return edad; }


    @Override public boolean equals(Object o) { ... }

    @Override public int hashCode() { ... }

    @Override public String toString() { ... }

}

Ventajas

  • Total control
  • Compatible con todo (JPA, frameworks)


Desventajas

  • Mucho boilerplate
  • Propenso a errores


2. Records (Java)


public record Persona(String nombre, int edad) {}


Ventajas

  • Ultra conciso
  • Inmutabilidad garantizada
  • Sin dependencias


Desventajas

  • Menos flexible
  • No sirve bien con JPA
  • No herencia


3. Lombok (@Data)


import lombok.Data;


@Data

public class Persona {

    private String nombre;

    private int edad;

}


Ventajas

  • Reduce mucho código
  • Mutable o inmutable (configurable)


Desventajas

  • Dependencia externa
  • "Magia" en compilación (puede confundir)
  •  Problemas en tooling/debug


4. Data Classes (Kotlin)


data class Persona(val nombre: String, val edad: Int)


Ventajas

  • Muy conciso
  • Inmutable por defecto
  • copy() incluido
  • Destructuring


val (nombre, edad) = persona


Desventajas

  • Requiere usar Kotlin
  • Interoperabilidad Java no siempre perfecta


5. Case Classes (Scala)


case class Persona(nombre: String, edad: Int)


Ventajas

  • Inmutables
  • Pattern matching nativo
  • copy() automático
  • Muy expresivas


persona match {

  case Persona(nombre, edad) => println(nombre)

}


Desventajas

  • Curva de aprendizaje
  • Ecosistema más complejo


Y entonces? Y ninguno es super mejor, pero podemos tener estas reglas: 


Java Records

Ideal para:

  • DTOs simples
  • APIs REST
  • Código moderno sin dependencias


Son el "mínimo viable elegante" en Java.


Lombok

Ideal para:

  • Proyectos legacy
  • Equipos que ya lo usan


 Soluciona el problema… pero no es parte del lenguaje.


Kotlin

La mejor experiencia general para modelado de datos.

  • copy()
  • destructuring
  • null-safety


Es claramente superior en ergonomía.


Scala

El más poderoso conceptualmente.

  • Pattern matching real
  • Inmutabilidad fuerte
  • Integración con FP


Pero más complejo.


 Clases Java

 Siguen siendo necesarias cuando:

  • Usás JPA
  • Necesitás mutabilidad
  • Requerís control total


Si estás en Java moderno → Records

Si querés máxima productividad → Kotlin

Si buscás poder expresivo → Scala

Si estás en legacy → Lombok o clases



lunes, 20 de abril de 2026

Records en Java: Datos Inmutables de Forma Elegante


Desde Java 14, el lenguaje incorporó una nueva forma de definir clases de datos: los records.

Su objetivo es simple: reducir el boilerplate cuando solo queremos representar datos inmutables.


¿Qué problema vienen a resolver?

Antes de records, una clase típica para representar datos se veía así:


public class Persona {

    private final String nombre;

    private final int edad;


    public Persona(String nombre, int edad) {

        this.nombre = nombre;

        this.edad = edad;

    }


    public String getNombre() {

        return nombre;

    }


    public int getEdad() {

        return edad;

    }


    @Override

    public boolean equals(Object o) { ... }


    @Override

    public int hashCode() { ... }


    @Override

    public String toString() { ... }

}


Mucho código repetitivo, ¿no?

Con records, lo mismo se define así:


public record Persona(String nombre, int edad) {}


Y listo.


Java automáticamente genera:

  • Constructor
  • Getters (sin prefijo get)
  • equals()
  • hashCode()
  • toString()


Un record es una clase especial que:

  • Es inmutable
  • Es final
  • Extiende implícitamente de java.lang.Record


Persona p = new Persona("Juan", 30);

System.out.println(p.nombre()); // Juan

System.out.println(p.edad());   // 30


Podemos hacer un constructor personalizado por ejemplo para validar datos:


public record Persona(String nombre, int edad) {

    public Persona {

        if (edad < 0) {

            throw new IllegalArgumentException("Edad inválida");

        }

    }

}


Este es un constructor compacto.

Los records no son solo datos, también pueden tener lógica:


public record Persona(String nombre, int edad) {

    public boolean esMayorDeEdad() {

        return edad >= 18;

    }

}


Restricciones importantes:

  • No podés extender otras clases
  • Los atributos son implícitamente final
  • No hay setters
  • No están pensados para entidades mutables (ej: JPA)


¿Cuándo usar Records?

Usalos cuando:

✔ Tenés objetos de solo datos (DTOs)

✔ Querés inmutabilidad

✔ Buscás claridad y menos boilerplate


Evitarlos cuando:

❌ Necesitás mutabilidad

❌ Estás modelando entidades complejas

❌ Usás frameworks que requieren setters (como JPA tradicional)


Con features modernas (Java 21+), los records se integran muy bien con pattern matching:


if (obj instanceof Persona(String nombre, int edad)) {

    System.out.println(nombre);

}


Esto hace el código más declarativo y expresivo.


Los records son una de las mejoras más importantes en Java moderno:

  • Reducen código repetitivo
  • Fomentan inmutabilidad
  • Mejoran la legibilidad


Son ideales para modelar datos de forma clara y segura.