Este artículo puede incluir enlaces de afiliado. Si compras a través de ellos, me llevo una pequeña comisión sin coste extra para ti. Más información.
Los tipos en java tiene mucha más importancia de la que nos imaginamos. Hace unos años abrí un proyecto mío que llevaba semanas sin tocar. Una clase de gestión de usuarios. Y me encontré con un método cuya firma era void asignar(String a, String b, String c). Tres Strings. Cero pistas.
Estuve un rato largo intentando recordar qué demonios era a, qué era b y qué era c. Tuve que ir al código que llamaba al método para entenderlo. El yo del pasado me había dejado un acertijo.
Ese día empecé a tomarme en serio una idea que llevaba tiempo rondándome: dejar de usar String como tipo universal para todo. Y la verdad es que cambió bastante cómo escribo y, sobre todo, cómo leo lo que escribí.
El problema de tratar todo como un String
En Java es tentador. Un email es texto, ¿no? Pues String. Un identificador de usuario es texto. String. Un código postal, un estado de pedido, un número de teléfono. Todo cabe en un String.
El compilador no se queja. Compila perfecto. El problema no es que falle, es que no te avisa de nada. Un String acepta cualquier cosa que sea texto, tenga sentido o no en ese contexto.
Cuando tienes un método que recibe String email, String nombre, nada te impide llamarlo al revés. Pasarle el nombre donde va el email y el email donde va el nombre. Compila. Y revienta en runtime, o peor, no revienta y guarda basura.
Ese tipo de error es el más tonto y el más caro. Tonto porque es evidente cuando lo ves. Caro porque no lo ves hasta que un usuario te escribe diciendo que algo no funciona.
Lo escribí muchas veces antes de darme cuenta del patrón. El String se había convertido en mi caja de «ya lo arreglo luego». Y luego nunca llegaba.
Por qué empecé a escribir mis propios tipos en Java
La idea de fondo es vieja y se llama type-driven design o, más coloquialmente, hacer que el compilador trabaje para ti. Si modelas el dominio con tipos propios, muchos errores dejan de ser posibles.
Un Email no es lo mismo que un Nombre. Aunque ambos lleven texto dentro, son cosas distintas. Si los represento con clases distintas, el compilador no me deja confundirlos.
Empecé con algo modesto. En lugar de pasar el ID de usuario como String o como long, creé un tipo UserId. Una clase pequeña que envuelve el valor y le da nombre.
Lo bonito es que ese cambio se nota poco al escribirlo y mucho al leerlo. Cuando vuelvo semanas después, la firma del método me cuenta la historia entera sin tener que abrir nada más.
Hace poco leí Java’s Primitive Type System, un artículo que repasa cómo funcionan los tipos primitivos del lenguaje. Me sirvió para entender mejor por qué arrastramos esta costumbre de aplanarlo todo a String o a int. Es cómodo, pero es perezoso.

Cómo se ve esto en código
La forma clásica de hacerlo era crear una clase con el valor dentro, un constructor que validara y un equals decente. Desde Java 16 tenemos los record, que hacen casi todo eso por nosotros.
Mira la diferencia. Un record para envolver un email, con su validación en el constructor compacto. Es poco código y resuelve mucho.
public record Email(String valor) {
public Email {
if (valor == null || !valor.contains("@")) {
throw new IllegalArgumentException("Email no válido: " + valor);
}
}
}
public record UserId(long valor) {}
// Ahora la firma del método habla por sí sola
void registrar(UserId id, Email email) {
// imposible confundir el orden de los argumentos
// imposible pasar un email que no se haya validado antes
}
Lo que más me gusta de este enfoque no es la validación, aunque ayuda. Es que registrar(UserId, Email) es imposible de llamar mal. No puedo pasarle dos Strings cruzados. El tipo me protege.
Y la validación vive en un solo sitio. Si un Email existe en mi programa, es porque pasó por el constructor. No tengo que andar comprobando si trae arroba en quince sitios distintos. Una vez. Ya está.
A esto se le llama a veces «hacer que los estados inválidos sean irrepresentables». Suena pomposo. En la práctica significa que si algo no puede pasar, el código no te deja escribirlo.
El compilador como red de seguridad
Hay una frase de Tony Hoare, el que inventó el null por cierto, que me parece muy buena sobre la simplicidad en el diseño.
«There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies.»
— C. A. R. Hoare
Lo traigo aquí porque modelar con tipos propios va justo en la dirección de la simplicidad evidente. No escondes las reglas del dominio dentro de ifs repartidos. Las pones en el tipo, a la vista, y el compilador las defiende por ti.
Empecé a ver el sistema de tipos no como un obstáculo que me obliga a declarar cosas, sino como un compañero que repasa mi trabajo gratis. Cada tipo propio que añado es una clase de errores que ya no puedo cometer.
Antes pensaba que más tipos era más código y más código era peor. Ahora lo veo distinto. Es más código en un sitio para tener menos comprobaciones desperdigadas por todas partes.
El número de líneas a veces sube. La cantidad de cosas que tengo que tener en la cabeza a la vez baja mucho. Y mi cabeza, a las once de la noche, agradece cualquier ayuda.
Lo que cambió al leer mi propio código semanas después
Este es el motivo real por el que sigo haciéndolo. No es por elegancia ni por seguir una moda. Es porque mi yo del futuro es siempre un desconocido cansado que no recuerda nada.
Cuando vuelvo a un proyecto después de un parón, leo las firmas de los métodos como quien lee un índice. enviar(Email destino, Mensaje cuerpo) me dice qué hace antes de leer una sola línea de implementación.
Con Strings tenía que reconstruir el contexto cada vez. Abrir el método, mirar qué se hacía con cada parámetro, deducir qué representaba. Un trabajo arqueológico sobre código que yo mismo escribí.
Hay algo de humildad en esto. Aceptar que dentro de tres semanas no me voy a acordar de nada. Que mis comentarios mentales se evaporan. Que lo único que queda es lo que el código dice por sí mismo.
Ya hablé de esa sensación de desconfiar de mí mismo cuando programo, de leerme con sospecha en vez de con fe ciega. Esto va por ahí. Escribir tipos propios es, en el fondo, escribir para ese desconocido que seré yo.
Me pasa una cosa curiosa. Cuando el código está bien tipado, leerlo se parece a leer prosa. Las frases tienen sujeto y verbo claros. Cuando todo es String, es como leer un texto donde cada palabra pudiera significar cualquier cosa.
Una analogía con los gatos, ya que estamos
En casa tengo a Bella, a Kika y a Oli, y ahora mismo a Toñi en acogida. Bella y Toñi son las dos atigradas con tonos naranjas, parecidísimas. De lejos las confundo.
Pero no son intercambiables. Bella tiene casi ocho años y es la veterana, con su carácter. Toñi tiene año y medio y está de paso, esperando su casa. Tratarlas como «dos gatas naranjas» sería perderme todo lo que las hace distintas.
Con los tipos pasa igual. Email y Nombre son los dos «texto», igual que Bella y Toñi son las dos «gata atigrada naranja». Pero confundirlos tiene consecuencias. El tipo, como el nombre, sirve para no tratar como iguales cosas que no lo son.
Cuándo no merece la pena
No quiero vender esto como la solución a todo, porque no lo es. Crear un tipo propio para cada String de tu programa es pasarse. Hay un punto en que la ceremonia supera al beneficio.
Para un script de usar y tirar, no. Para un dato que solo vive dentro de un método y no cruza ninguna frontera, tampoco hace falta. El esfuerzo tiene que pagar algo.
Yo aplico una regla sencilla. Si el valor representa un concepto del dominio (un email, un IBAN, un estado), si viaja entre capas, si tiene reglas de validación o si lo confundiría con otro String parecido, entonces merece su tipo.
Si es solo texto que muestro y descarto, lo dejo como String y a otra cosa. El criterio no es «tipos para todo», es «tipos donde el tipo te cuenta algo y te protege de algo».
También hay un coste real de interoperabilidad. Cuando hablas con una librería externa, con una base de datos o con JSON, en algún momento tienes que convertir tus tipos a String y volver. Ese borde existe y hay que gestionarlo.
La clave es que la conversión ocurra en los bordes, no por todas partes. Dentro de tu dominio manejas Email; en la frontera, justo al entrar y justo al salir, traduces. El centro queda limpio.
Foto: turned on flat screen monitors and MacBook Pro por Jannis Brandt en Unsplash.
¿Quieres más?
Recibe los nuevos artículos sobre gatos, escritura y código. Sin frecuencia fija, sin paja de IA, sin spam.
Suscribirme