TutorJava Nivel Básico: tutorial completo Java 21

Java cumplió 30 años en 2025 y sigue siendo uno de los lenguajes más demandados en el mercado laboral. No por inercia, sino porque la plataforma ha sabido renovarse sin romper lo que ya funcionaba. Si buscas tu primer empleo como desarrollador backend, las ofertas con Java no escasean. Si ya programas en otro lenguaje y quieres ampliar opciones, Java te abre puertas en empresas grandes, banca y aplicaciones de servidor que llevan décadas en producción.

Java 21 es la versión LTS más importante desde Java 8. LTS significa Long Term Support: soporte garantizado durante años, sin presión de actualizar cada seis meses. Las novedades de Java 21 no son cosméticas. Los virtual threads cambian cómo se escribe código concurrente, los records eliminan la verbosidad de las clases de datos y el pattern matching simplifica el código de comprobación de tipos. Este tutorial los cubre todos.

Instalación: JDK 21 con Temurin

Olvídate del JDK de Oracle si no quieres lidiar con licencias. Usa Eclipse Temurin, la distribución gratuita y open source mantenida por Adoptium. Descárgala desde adoptium.net y elige la versión 21 LTS para tu sistema operativo.

Tras instalar, verifica que todo funciona:

java -version
# java version "21.0.3" 2024-04-16 LTS

javac -version
# javac 21.0.3

java ejecuta programas compilados y javac compila código fuente. Con eso tienes lo mínimo para empezar.

Hola Mundo

Crea un archivo HolaMundo.java con este contenido:

public class HolaMundo {
    public static void main(String[] args) {
        System.out.println("Hola, mundo");
    }
}

Compílalo y ejecútalo:

javac HolaMundo.java
java HolaMundo

Para proyectos reales no compilarás a mano. Maven se encarga de todo. La estructura básica de un proyecto Maven tiene este aspecto:

mi-proyecto/
??? pom.xml
??? src/
    ??? main/
    ?   ??? java/
    ?       ??? com/ejemplo/App.java
    ??? test/
        ??? java/
            ??? com/ejemplo/AppTest.java

El archivo pom.xml define las dependencias y la versión de Java. Un pom.xml mínimo para Java 21:

<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.ejemplo</groupId>
    <artifactId>mi-proyecto</artifactId>
    <version>1.0</version>
    <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
    </properties>
</project>

POO en Java: clases, herencia e interfaces

Java es un lenguaje orientado a objetos desde su origen. Aquí no hay escape: todo vive dentro de una clase. Veamos los conceptos clave con un ejemplo concreto.

Clases y constructores

public class Circulo {
    private double radio;

    public Circulo(double radio) {
        this.radio = radio;
    }

    public double getRadio() {
        return radio;
    }

    public double area() {
        return Math.PI * radio * radio;
    }
}

this.radio distingue el atributo del parámetro del constructor cuando tienen el mismo nombre. Los métodos getter dan acceso controlado a los atributos privados.

Herencia

Con extends una clase hereda el comportamiento de otra. La subclase puede sobrescribir métodos con @Override:

public abstract class Forma {
    public abstract double area();

    public void describir() {
        System.out.println("Área: " + area());
    }
}

public class Rectangulo extends Forma {
    private double ancho, alto;

    public Rectangulo(double ancho, double alto) {
        this.ancho = ancho;
        this.alto = alto;
    }

    @Override
    public double area() {
        return ancho * alto;
    }
}

Forma es abstracta: no se puede instanciar directamente, solo a través de sus subclases. Rectangulo está obligado a implementar area() porque el contrato lo exige.

Interfaces

Una interfaz define un contrato sin implementación (salvo los métodos default, que existen desde Java 8). La diferencia con las clases abstractas es que una clase puede implementar varias interfaces pero solo extender una clase:

public interface Dibujable {
    void dibujar();

    default String descripcion() {
        return "Objeto dibujable";
    }
}

public class Triangulo extends Forma implements Dibujable {
    private double base, altura;

    public Triangulo(double base, double altura) {
        this.base = base;
        this.altura = altura;
    }

    @Override
    public double area() {
        return (base * altura) / 2;
    }

    @Override
    public void dibujar() {
        System.out.println("Dibujando triángulo con base " + base);
    }
}

Usa interfaces cuando quieras definir comportamiento que pueden compartir clases sin relación de herencia. Usa clases abstractas cuando haya estado o lógica común que tenga sentido heredar.

Las novedades de Java 21 que deberías conocer desde el principio

Records

Antes de Java 16, crear una clase de datos requería constructor, getters, equals(), hashCode() y toString(). Con records, todo eso cabe en una línea:

record Punto(int x, int y) {}

// Uso
Punto p = new Punto(3, 7);
System.out.println(p.x());   // 3
System.out.println(p);       // Punto[x=3, y=7]

Los records son inmutables por definición. Van de perlas para DTOs, respuestas de API o cualquier objeto que solo transporte datos.

Sealed classes

Permiten controlar exactamente qué clases pueden extender una clase base. Útil cuando modelas un dominio cerrado:

public sealed class Resultado permits Exito, Error {}

public final class Exito extends Resultado {
    public final String mensaje;
    public Exito(String mensaje) { this.mensaje = mensaje; }
}

public final class Error extends Resultado {
    public final String causa;
    public Error(String causa) { this.causa = causa; }
}

El compilador sabe que Resultado solo puede ser Exito o Error, lo que permite hacer comprobaciones exhaustivas sin casos por defecto.

Pattern matching para instanceof

Antes de Java 16 hacías esto:

if (obj instanceof String) {
    String s = (String) obj;
    System.out.println(s.length());
}

Desde Java 16, el cast es automático:

if (obj instanceof String s) {
    System.out.println(s.length());
}

Menos código, menos riesgo de ClassCastException. En Java 21 esto se combina con switch para hacer pattern matching completo:

String descripcion = switch (forma) {
    case Circulo c    -> "Círculo con radio " + c.getRadio();
    case Rectangulo r -> "Rectángulo " + r.ancho + "x" + r.alto;
    default           -> "Forma desconocida";
};

Text blocks

Los strings multilínea ya no necesitan concatenaciones ni escapes:

String json = """
        {
            "nombre": "Ana",
            "edad": 30
        }
        """;

Mucho más legible cuando trabajas con SQL, JSON o HTML dentro de código Java.

Lambdas y Streams API

Las lambdas llegaron en Java 8 y cambiaron la forma de escribir código en colecciones. Una lambda es una función anónima que puedes pasar como argumento:

// Antes
List<String> nombres = Arrays.asList("Ana", "Luis", "María");
Collections.sort(nombres, new Comparator<String>() {
    public int compare(String a, String b) {
        return a.compareTo(b);
    }
});

// Con lambda
nombres.sort((a, b) -> a.compareTo(b));

// O todavía más corto con method reference
nombres.sort(String::compareTo);

Las interfaces funcionales más usadas son:

  • Predicate<T>: recibe un T y devuelve boolean. Se usa para filtrar.
  • Function<T, R>: recibe un T y devuelve un R. Se usa para transformar.
  • Consumer<T>: recibe un T y no devuelve nada. Se usa para efectos secundarios.
  • Supplier<T>: no recibe nada y devuelve un T. Se usa para crear objetos bajo demanda.

Streams API

Los Streams permiten procesar colecciones de forma declarativa. Un ejemplo con una lista de personas:

record Persona(String nombre, int edad) {}

List<Persona> personas = List.of(
    new Persona("Ana", 28),
    new Persona("Luis", 17),
    new Persona("María", 35),
    new Persona("Carlos", 22)
);

// Nombres de mayores de 18, ordenados alfabéticamente
List<String> resultado = personas.stream()
    .filter(p -> p.edad() >= 18)
    .map(Persona::nombre)
    .sorted()
    .collect(Collectors.toList());

// resultado: [Ana, Carlos, María]

La cadena de operaciones es fácil de leer: filtra por edad, extrae el nombre, ordena y recoge. Sin bucles explícitos, sin variables temporales.

Otros métodos de Stream que usarás a menudo: reduce() para acumular valores, findFirst() para obtener el primer elemento, count() para contar y anyMatch() para comprobar si algún elemento cumple una condición.

Concurrencia con virtual threads

La concurrencia clásica en Java usa threads del sistema operativo. Crear miles de ellos es caro: cada uno consume entre 0,5 y 2 MB de memoria y el SO tiene que gestionarlos. Eso limita la escalabilidad de aplicaciones con muchas conexiones simultáneas.

Java 21 trae los virtual threads del Project Loom. Son threads gestionados por la JVM, no por el SO. Puedes crear un millón sin problema:

// Thread clásico
Thread t = new Thread(() -> System.out.println("Thread clásico"));
t.start();

// Virtual thread
Thread vt = Thread.ofVirtual().start(() -> System.out.println("Virtual thread"));

// Con ExecutorService
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    for (int i = 0; i < 10_000; i++) {
        executor.submit(() -> {
            // simular I/O
            Thread.sleep(100);
            return "ok";
        });
    }
}

Los virtual threads brillan en operaciones I/O: llamadas a bases de datos, peticiones HTTP, lectura de ficheros. Cuando el virtual thread queda bloqueado esperando una respuesta, la JVM aprovecha el thread del SO para ejecutar otro. El resultado es una concurrencia masiva sin el coste de crear threads reales.

No los uses para operaciones CPU intensivas como cálculos matemáticos complejos o procesamiento de imágenes. Ahí los threads clásicos siguen siendo la opción correcta. Para profundizar en estrategias de concurrencia avanzada en Java, tienes un artículo dedicado en este mismo sitio.

Herramientas que necesitas desde el primer día

Maven

Maven gestiona dependencias y el ciclo de vida del proyecto. Los comandos más habituales son:

  • mvn compile: compila el código fuente.
  • mvn test: ejecuta los tests.
  • mvn package: genera el JAR o WAR desplegable.
  • mvn clean install: limpia, compila, pasa tests e instala en el repositorio local.

JUnit 5

Los tests no son opcionales si quieres código que aguante el paso del tiempo. JUnit 5 es el estándar en Java:

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class CirculoTest {

    @Test
    void areaCirculoRadio5() {
        Circulo c = new Circulo(5);
        assertEquals(Math.PI * 25, c.area(), 0.001);
    }

    @Test
    void radioNegativoLanzaExcepcion() {
        assertThrows(IllegalArgumentException.class,
            () -> new Circulo(-1));
    }
}

Añade JUnit 5 al pom.xml con esta dependencia:

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.10.2</version>
    <scope>test</scope>
</dependency>

IDE: IntelliJ IDEA o VS Code

IntelliJ IDEA Community es gratuito y el mejor IDE para Java. Autocompletado inteligente, refactoring, depurador integrado y soporte nativo para Maven y JUnit. Si prefieres algo más ligero, VS Code con el Extension Pack for Java funciona bien para proyectos pequeños y medianos.

Por dónde seguir

Con esto tienes la base: POO, las novedades de Java 21, lambdas, Streams y virtual threads. El siguiente paso lógico es profundizar en colecciones avanzadas (HashMap, TreeMap, LinkedList), en el manejo de excepciones y en la API de ficheros. También conviene explorar frameworks como Spring Boot, que es donde trabaja la mayoría del código Java empresarial.

Si te interesa la concurrencia más allá de los virtual threads, te recomiendo leer sobre CompletableFuture, locks explícitos y las estructuras de java.util.concurrent. Java 25 LTS ya está en el horizonte: conoce qué trae Java 25 LTS, la siguiente versión de soporte largo.

La documentación oficial de Java 21 está en docs.oracle.com/en/java/javase/21/ y es más legible de lo que parece. Los Javadocs de las clases estándar son tu mejor referencia cuando no recuerdes qué hace un método.

Imagen: Pexels

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP