Oposición Cuerpo de Tecnologías de la Información y las Comunicaciones (TIC) — Administración General del Estado
El presente tema aborda dos de los lenguajes de programación de propósito general más extendidos en el desarrollo de aplicaciones para la administración pública y la empresa privada —PHP y Java— así como los conceptos fundamentales que sustentan el uso de frameworks de desarrollo, herramientas que han transformado la manera en que se construye software profesional en las últimas dos décadas.
PHP y Java representan dos filosofías de desarrollo complementarias. PHP nació como un lenguaje de scripting orientado específicamente a la generación de contenido web dinámico, con una curva de aprendizaje suave y un ciclo de despliegue ágil. Java, por su parte, se diseñó desde su origen como un lenguaje de propósito general, fuertemente tipado, orientado a objetos y pensado para ejecutarse de manera independiente de la plataforma gracias a su máquina virtual, lo que lo convirtió en el estándar de facto para sistemas empresariales de gran envergadura.
En el contexto de la Administración General del Estado, ambos lenguajes conviven en multitud de sistemas de información: PHP es habitual en portales web, sedes electrónicas y aplicaciones de gestión de tamaño medio, mientras que Java domina en sistemas core de gran volumen transaccional, integraciones EAI/ESB y arquitecturas de microservicios corporativas. El conocimiento de ambos lenguajes, así como de los frameworks que los acompañan, resulta por tanto imprescindible para el personal técnico que debe analizar, desarrollar, mantener y auditar sistemas de información públicos.
A lo largo del tema se expondrá la teoría fundamental de cada lenguaje, su sintaxis, su modelo de objetos, sus mecanismos de gestión de errores, acceso a datos y concurrencia, así como ejemplos prácticos de código. Finalmente, se desarrollará el concepto de framework de desarrollo, su clasificación y los principales exponentes en ambos ecosistemas, cerrando con una comparativa que sitúe a ambos lenguajes en su contexto de uso más adecuado.
PHP (acrónimo recursivo de "PHP: Hypertext Preprocessor") es un lenguaje de programación interpretado, de tipado dinámico y débil, diseñado originalmente para el desarrollo web. Fue creado por Rasmus Lerdorf en 1994 bajo el nombre "Personal Home Page Tools", evolucionando posteriormente con la incorporación de Zeev Suraski y Andi Gutmans, quienes reescribieron el núcleo del lenguaje dando lugar a PHP 3 (1998) y al motor Zend Engine que sustenta PHP 4 (2000) y PHP 5 (2004).
Las versiones más relevantes en la evolución del lenguaje son las siguientes:
??) y el operador spaceship (<=>).PHP se caracteriza por:
El ciclo de vida de un script PHP en un entorno web típico sigue el siguiente flujo: el servidor web (Apache, Nginx) recibe una petición HTTP; si la URL solicitada corresponde a un fichero .php, el servidor delega su procesamiento en el intérprete de PHP, ya sea como módulo del propio servidor (mod_php), como proceso CGI, o —de forma mayoritaria en producción actual— mediante PHP-FPM (FastCGI Process Manager), que gestiona un pool de procesos PHP persistentes para mejorar el rendimiento. El intérprete ejecuta el script, genera una salida (típicamente HTML, JSON o XML) y la devuelve al servidor web, que la remite al cliente.
El código PHP se delimita mediante las etiquetas <?php y ?>. Las variables se identifican con el prefijo $ y no requieren declaración previa de tipo:
<?php
$nombre = "Ana"; // string
$edad = 35; // integer
$altura = 1.68; // float (double)
$activo = true; // boolean
$hijos = null; // null
$colores = ["rojo", "verde", "azul"]; // array indexado
$persona = [
"nombre" => "Ana",
"edad" => 35
]; // array asociativo
PHP dispone de los siguientes tipos de datos escalares: int, float (o double), string, bool; tipos compuestos: array y object; y tipos especiales: null, resource (referencias a recursos externos como conexiones a BD o ficheros) y, desde PHP 8.1, callable y los tipos de unión (int|string).
El operador de concatenación de cadenas es el punto (.), distinto del operador de suma aritmética (+):
$saludo = "Hola, " . $nombre . ". Tienes " . $edad . " años.";
Desde PHP 7, es posible declarar tipos en los parámetros y en el valor de retorno de las funciones, y activar el modo estricto al inicio del fichero:
<?php
declare(strict_types=1);
function suma(int $a, int $b): int {
return $a + $b;
}
PHP ofrece las estructuras de control habituales en los lenguajes imperativos: if/elseif/else, switch, match (desde PHP 8.0, una alternativa más estricta y expresiva a switch), bucles for, while, do-while y foreach para la iteración sobre arrays y objetos iterables.
$nota = 7;
$resultado = match(true) {
$nota >= 9 => "Sobresaliente",
$nota >= 7 => "Notable",
$nota >= 5 => "Aprobado",
default => "Suspenso"
};
foreach ($colores as $indice => $color) {
echo "$indice: $color\n";
}
Las funciones se declaran con la palabra clave function, admiten parámetros por valor, por referencia (&$variable), valores por defecto, número variable de argumentos (...$args, variadic functions) y, desde PHP 8.0, argumentos nombrados:
function crearUsuario(string $nombre, int $edad = 18, bool $activo = true): array {
return compact('nombre', 'edad', 'activo');
}
$u = crearUsuario(nombre: "Luis", activo: false);
PHP soporta igualmente funciones anónimas (closures) y funciones flecha (arrow functions, desde PHP 7.4), de gran utilidad en programación funcional sobre colecciones:
$cuadrado = fn($x) => $x * $x;
$pares = array_filter([1, 2, 3, 4, 5, 6], fn($n) => $n % 2 === 0);
Desde PHP 5, el lenguaje incorpora un modelo de objetos completo: clases, herencia simple, interfaces (con herencia múltiple de interfaces), clases y métodos abstractos, traits (mecanismo de reutilización horizontal de código que mitiga la ausencia de herencia múltiple de clases) y visibilidad de miembros (public, protected, private).
<?php
declare(strict_types=1);
interface Notificable {
public function notificar(string $mensaje): void;
}
abstract class Empleado {
public function __construct(
protected string $nombre,
protected float $salarioBase
) {}
abstract public function calcularSalario(): float;
public function getNombre(): string {
return $this->nombre;
}
}
class Comercial extends Empleado implements Notificable {
public function __construct(
string $nombre,
float $salarioBase,
private float $comision
) {
parent::__construct($nombre, $salarioBase);
}
public function calcularSalario(): float {
return $this->salarioBase + $this->comision;
}
public function notificar(string $mensaje): void {
echo "Notificación para {$this->nombre}: $mensaje\n";
}
}
$comercial = new Comercial("María", 1500.0, 300.0);
echo $comercial->calcularSalario(); // 1800.0
Nótese en el ejemplo la promoción de propiedades en el constructor (PHP 8.0), que evita declarar y asignar manualmente cada propiedad. PHP también soporta clases final, constantes de clase, propiedades y métodos estáticos, el operador de resolución estática ::, y desde PHP 8.1, enums (enumeraciones) como tipo de primera clase:
enum EstadoPedido: string {
case Pendiente = 'pendiente';
case Enviado = 'enviado';
case Entregado = 'entregado';
public function descripcion(): string {
return match($this) {
self::Pendiente => 'El pedido está pendiente de procesar',
self::Enviado => 'El pedido ha sido enviado',
self::Entregado => 'El pedido ha sido entregado',
};
}
}
El autoloading de clases se gestiona habitualmente mediante el estándar PSR-4, que mapea espacios de nombres (namespaces) a rutas de directorios, integrado de forma automática por Composer.
PHP distingue entre errores (Error, jerarquía interna del motor, p. ej. TypeError, DivisionByZeroError) y excepciones (Exception), ambas implementando la interfaz Throwable desde PHP 7, lo que permite capturarlas de forma unificada:
function dividir(float $a, float $b): float {
if ($b === 0.0) {
throw new InvalidArgumentException("El divisor no puede ser cero");
}
return $a / $b;
}
try {
echo dividir(10, 0);
} catch (InvalidArgumentException $e) {
echo "Error de argumento: " . $e->getMessage();
} catch (Throwable $e) {
echo "Error inesperado: " . $e->getMessage();
} finally {
echo "Operación finalizada";
}
Es posible definir excepciones personalizadas heredando de Exception, y desde PHP 8.0 capturar varios tipos en una misma cláusula catch (TipoA | TipoB $e).
El acceso a bases de datos relacionales se realiza fundamentalmente mediante dos APIs: la extensión específica MySQLi (solo MySQL/MariaDB) y la capa de abstracción PDO (PHP Data Objects), que permite trabajar con múltiples motores (MySQL, PostgreSQL, SQLite, Oracle, SQL Server) mediante una interfaz común y soporta de forma nativa las consultas preparadas, mecanismo esencial para prevenir inyección SQL:
<?php
$dsn = "mysql:host=localhost;dbname=oposiciones;charset=utf8mb4";
$pdo = new PDO($dsn, "usuario", "contraseña", [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]);
$stmt = $pdo->prepare("SELECT id, nombre FROM empleados WHERE departamento = :dept");
$stmt->execute(['dept' => 'TIC']);
foreach ($stmt as $fila) {
echo $fila['nombre'] . "\n";
}
PHP proporciona soporte nativo para el modelo request-response HTTP: las variables superglobales $_GET, $_POST, $_REQUEST, $_SERVER, $_COOKIE, $_SESSION y $_FILES permiten acceder a los datos de la petición. La gestión de sesiones se inicia con session_start(), que por defecto almacena el identificador de sesión en una cookie y persiste los datos en el servidor (sistema de ficheros, o backends alternativos como Redis o memcached para entornos distribuidos).
session_start();
$_SESSION['usuario_id'] = 42;
setcookie("preferencia_idioma", "es", time() + 3600 * 24 * 30, "/", "", true, true);
Es buena práctica configurar las cookies de sesión con los atributos Secure, HttpOnly y SameSite para mitigar ataques de robo de sesión y cross-site request forgery (CSRF).
Composer es el gestor de dependencias estándar de facto del ecosistema PHP, análogo a Maven/Gradle en Java o npm en JavaScript. Permite declarar las dependencias de un proyecto en un fichero composer.json, descargarlas desde el repositorio público Packagist, y genera un autoloader PSR-4 automático:
{
"require": {
"php": "^8.2",
"laravel/framework": "^11.0",
"guzzlehttp/guzzle": "^7.8"
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}
Entre los aspectos de seguridad más relevantes en el desarrollo PHP cabe destacar: el uso sistemático de consultas preparadas frente a la concatenación de cadenas en SQL (prevención de inyección SQL); el escapado de salida mediante htmlspecialchars() para prevenir Cross-Site Scripting (XSS); la validación y sanitización de toda entrada de usuario; el uso de tokens anti-CSRF en formularios; el almacenamiento de contraseñas mediante funciones de hashing adaptativo como password_hash() (que emplea por defecto el algoritmo bcrypt) y nunca en texto claro ni con algoritmos obsoletos como MD5 o SHA-1; la configuración restrictiva de php.ini (deshabilitar display_errors en producción, limitar allow_url_include, etc.); y el mantenimiento de la versión de PHP y sus dependencias actualizadas, dado que las versiones sin soporte dejan de recibir parches de seguridad.
OPcache es una extensión incluida por defecto desde PHP 5.5 que almacena el bytecode compilado de los scripts en memoria compartida, evitando recompilar el código en cada petición y mejorando sustancialmente el rendimiento. PHP-FPM (FastCGI Process Manager) es el gestor de procesos recomendado en producción, que permite configurar pools de workers con políticas de escalado (estático, dinámico, on-demand), mejorando la concurrencia frente al modelo tradicional de mod_php embebido en Apache. El compilador JIT, introducido en PHP 8.0, compila a código máquina determinadas rutas de ejecución intensivas en CPU, aunque su impacto es limitado en aplicaciones web típicas (más orientadas a E/S que a cómputo), siendo más relevante en cargas de cálculo numérico.
A continuación se muestra un ejemplo práctico que integra varios de los conceptos anteriores: una pequeña API REST que gestiona empleados, con conexión a base de datos mediante PDO, manejo de excepciones y respuesta en formato JSON.
<?php
declare(strict_types=1);
header('Content-Type: application/json; charset=utf-8');
class RepositorioEmpleados {
public function __construct(private PDO $pdo) {}
public function buscarPorId(int $id): ?array {
$stmt = $this->pdo->prepare("SELECT id, nombre, departamento FROM empleados WHERE id = :id");
$stmt->execute(['id' => $id]);
$resultado = $stmt->fetch(PDO::FETCH_ASSOC);
return $resultado ?: null;
}
public function crear(string $nombre, string $departamento): int {
$stmt = $this->pdo->prepare(
"INSERT INTO empleados (nombre, departamento) VALUES (:nombre, :departamento)"
);
$stmt->execute(['nombre' => $nombre, 'departamento' => $departamento]);
return (int) $this->pdo->lastInsertId();
}
}
try {
$pdo = new PDO(
"mysql:host=localhost;dbname=oposiciones;charset=utf8mb4",
"usuario",
"contraseña",
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);
$repo = new RepositorioEmpleados($pdo);
$metodo = $_SERVER['REQUEST_METHOD'];
match ($metodo) {
'GET' => (function () use ($repo) {
$id = (int) ($_GET['id'] ?? 0);
$empleado = $repo->buscarPorId($id);
echo json_encode($empleado ?? ['error' => 'No encontrado'], JSON_UNESCAPED_UNICODE);
})(),
'POST' => (function () use ($repo) {
$datos = json_decode(file_get_contents('php://input'), true);
$id = $repo->crear($datos['nombre'], $datos['departamento']);
http_response_code(201);
echo json_encode(['id' => $id]);
})(),
default => http_response_code(405)
};
} catch (Throwable $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
Este ejemplo ilustra el uso de tipado estricto, promoción de propiedades, match como estructura de control, PDO con consultas preparadas, manejo centralizado de excepciones y generación de respuestas JSON propias de una API REST ligera, sin necesidad de un framework completo.
Java es un lenguaje de programación de propósito general, fuertemente tipado (estático), orientado a objetos y diseñado para ser independiente de la plataforma, siguiendo el principio "write once, run anywhere" (WORA). Fue desarrollado por James Gosling y su equipo en Sun Microsystems, presentado públicamente en 1995, y adquirido junto con el resto de la compañía por Oracle Corporation en 2010, que mantiene actualmente su desarrollo a través del proceso de especificación abierto JCP (Java Community Process) y del proyecto de código abierto OpenJDK.
La clave de la portabilidad de Java reside en su modelo de compilación en dos fases: el código fuente (.java) se compila a un formato intermedio denominado bytecode (ficheros .class), que no es código máquina nativo de ningún procesador concreto, sino que es interpretado y/o compilado just-in-time por la Máquina Virtual de Java (JVM) específica de cada sistema operativo y arquitectura. De este modo, el mismo bytecode se ejecuta sin modificaciones en Windows, Linux o macOS, siempre que exista una JVM compatible.
Desde 2017, Oracle adoptó un modelo de publicación semestral (cada seis meses, en marzo y septiembre), frente al anterior ciclo de varios años entre versiones mayores. Cada cierto número de versiones se designa una LTS (Long Term Support), con soporte extendido por parte de los distintos proveedores (Oracle, Eclipse Adoptium/Temurin, Amazon Corretto, Azul Zulu, Red Hat). Las versiones LTS más relevantes son Java 8 (2014, la más extendida históricamente en entornos empresariales), Java 11 (2018), Java 17 (2021) y Java 21 (2023), siendo esta última la LTS de referencia actual junto con la expectativa de Java 25 (LTS, 2025).
Entre las características fundamentales de Java destacan:
Thread y la interfaz Runnable.El ecosistema Java distingue tres elementos:
javac), el entorno de ejecución y herramientas adicionales (jar, javadoc, jdb, jshell, etc.). Es necesario para desarrollar aplicaciones Java.jlink.El flujo de ejecución de una aplicación Java es el siguiente: el código fuente .java se compila mediante javac a bytecode .class; este bytecode se carga en la JVM a través del ClassLoader; la JVM lo verifica (bytecode verifier) para garantizar que cumple las reglas de seguridad y consistencia del lenguaje; y finalmente lo ejecuta mediante un intérprete y/o un compilador JIT (Just-In-Time), que traduce a código máquina nativo aquellas partes del programa que se ejecutan con mayor frecuencia (hot spots, de ahí el nombre de la implementación HotSpot), combinando la portabilidad del bytecode con un rendimiento cercano al del código nativo.
La JVM organiza la memoria en varias áreas: el Heap (montón), donde se almacenan los objetos y es gestionado por el recolector de basura; el Stack (pila), uno por cada hilo de ejecución, donde se almacenan los marcos de las llamadas a métodos y las variables locales de tipo primitivo o referencias; el Method Area / Metaspace (desde Java 8), donde se almacenan los metadatos de las clases cargadas; y el PC Register y las pilas nativas. El recolector de basura dispone de varias implementaciones seleccionables (Serial, Parallel, G1 —recolector por defecto desde Java 9—, ZGC y Shenandoah, estos últimos orientados a minimizar las pausas en aplicaciones de baja latencia).
Java distingue entre tipos primitivos (byte, short, int, long, float, double, char, boolean), que se almacenan por valor y no son objetos, y tipos de referencia (clases, interfaces, arrays), que se almacenan como referencias a objetos en el heap. Cada tipo primitivo dispone de una clase wrapper asociada (Integer, Double, Boolean, etc.) que permite su uso en contextos que requieren objetos (colecciones genéricas, por ejemplo), mediante un mecanismo automático de conversión denominado autoboxing/unboxing.
int edad = 35;
double altura = 1.68;
boolean activo = true;
String nombre = "Ana"; // String es una clase, no un tipo primitivo
var ciudad = "Madrid"; // inferencia de tipos local (desde Java 10)
int[] numeros = {1, 2, 3, 4, 5};
List<String> colores = List.of("rojo", "verde", "azul"); // lista inmutable (Java 9+)
Desde Java 10, la palabra clave var permite la inferencia de tipos locales (el tipo se sigue determinando en tiempo de compilación; Java continúa siendo de tipado estático, simplemente se omite su escritura explícita cuando es deducible).
Java implementa de forma completa los cuatro pilares de la POO. Toda clase, salvo que extienda explícitamente otra, hereda implícitamente de Object, la raíz de la jerarquía de clases.
public interface Notificable {
void notificar(String mensaje);
}
public abstract class Empleado {
protected String nombre;
protected double salarioBase;
public Empleado(String nombre, double salarioBase) {
this.nombre = nombre;
this.salarioBase = salarioBase;
}
public abstract double calcularSalario();
public String getNombre() {
return nombre;
}
}
public class Comercial extends Empleado implements Notificable {
private double comision;
public Comercial(String nombre, double salarioBase, double comision) {
super(nombre, salarioBase);
this.comision = comision;
}
@Override
public double calcularSalario() {
return salarioBase + comision;
}
@Override
public void notificar(String mensaje) {
System.out.println("Notificación para " + nombre + ": " + mensaje);
}
}
Java incorpora, desde la versión 16 (estabilizado tras preview en versiones anteriores), los records, una forma concisa de declarar clases inmutables que actúan principalmente como portadoras de datos, generando automáticamente constructor, getters, equals(), hashCode() y toString():
public record Punto(int x, int y) {
public double distanciaAlOrigen() {
return Math.sqrt(x * x + y * y);
}
}
Asimismo, desde Java 17 se incorporan las clases selladas (sealed classes), que permiten restringir qué clases pueden extender o implementar una clase o interfaz determinada, mejorando la exhaustividad en combinación con el pattern matching introducido en switch (Java 21):
public sealed interface Forma permits Circulo, Cuadrado {}
public record Circulo(double radio) implements Forma {}
public record Cuadrado(double lado) implements Forma {}
public double calcularArea(Forma forma) {
return switch (forma) {
case Circulo c -> Math.PI * c.radio() * c.radio();
case Cuadrado c -> c.lado() * c.lado();
};
}
El Java Collections Framework proporciona una jerarquía unificada de interfaces y clases para el manejo de grupos de objetos: List (listas ordenadas con duplicados, implementadas por ArrayList, LinkedList), Set (conjuntos sin duplicados, implementados por HashSet, TreeSet, LinkedHashSet), Map (estructuras clave-valor, implementadas por HashMap, TreeMap, LinkedHashMap) y Queue/Deque (colas, implementadas por ArrayDeque, PriorityQueue).
Los genéricos (generics), introducidos en Java 5, permiten parametrizar tipos en clases, interfaces y métodos, proporcionando seguridad de tipos en tiempo de compilación y evitando castings explícitos:
Map<String, List<Empleado>> empleadosPorDepartamento = new HashMap<>();
public <T extends Comparable<T>> T maximo(List<T> lista) {
return lista.stream().max(Comparator.naturalOrder()).orElseThrow();
}
La API de Streams (Java 8) permite procesar colecciones de forma declarativa y funcional, mediante operaciones encadenables como filter, map, reduce, collect o sorted, habitualmente combinadas con expresiones lambda:
List<String> nombresActivos = empleados.stream()
.filter(Empleado::isActivo)
.map(Empleado::getNombre)
.sorted()
.collect(Collectors.toList());
double totalSalarios = empleados.stream()
.mapToDouble(Empleado::calcularSalario)
.sum();
Java distingue tres categorías de condiciones excepcionales, todas heredando de Throwable: los errores (Error, p. ej. OutOfMemoryError), que representan condiciones graves que normalmente no deben capturarse; las excepciones comprobadas (checked exceptions, heredan de Exception pero no de RuntimeException), que el compilador obliga a declarar (throws) o capturar; y las excepciones no comprobadas (unchecked exceptions, heredan de RuntimeException), que no requieren declaración explícita, como NullPointerException o IllegalArgumentException.
public class SaldoInsuficienteException extends Exception {
public SaldoInsuficienteException(String mensaje) {
super(mensaje);
}
}
public void retirar(double cantidad) throws SaldoInsuficienteException {
if (cantidad > saldo) {
throw new SaldoInsuficienteException("Saldo insuficiente para la operación");
}
saldo -= cantidad;
}
try (Connection conn = obtenerConexion()) { // try-with-resources (Java 7+)
// uso del recurso, se cierra automáticamente al finalizar el bloque
} catch (SaldoInsuficienteException e) {
logger.error("Operación rechazada: {}", e.getMessage());
} catch (SQLException e) {
logger.error("Error de base de datos", e);
} finally {
System.out.println("Proceso finalizado");
}
El bloque try-with-resources, introducido en Java 7, simplifica la gestión de recursos que implementan la interfaz AutoCloseable (conexiones, streams, ficheros), garantizando su cierre automático sin necesidad de un bloque finally explícito.
Java incorpora soporte de concurrencia desde su primera versión mediante la clase Thread y la interfaz Runnable, y lo ha ido enriqueciendo sustancialmente con el paquete java.util.concurrent (introducido en Java 5), que proporciona estructuras de alto nivel como ExecutorService (gestión de pools de hilos), Future/CompletableFuture (programación asíncrona), colecciones concurrentes (ConcurrentHashMap), sincronizadores (CountDownLatch, Semaphore, CyclicBarrier) y tipos atómicos (AtomicInteger, AtomicLong).
ExecutorService executor = Executors.newFixedThreadPool(4);
Future<Integer> resultado = executor.submit(() -> {
Thread.sleep(1000);
return 42;
});
CompletableFuture<String> futuro = CompletableFuture
.supplyAsync(() -> obtenerDatos())
.thenApply(datos -> procesar(datos))
.exceptionally(ex -> "Error: " + ex.getMessage());
executor.shutdown();
Una de las novedades más relevantes de los últimos años es la incorporación de los hilos virtuales (virtual threads, Project Loom, estables desde Java 21), hilos gestionados por la JVM (no por el sistema operativo) que permiten una concurrencia masiva con un coste de memoria y planificación muy reducido frente a los hilos de plataforma tradicionales, especialmente beneficioso en aplicaciones con un alto grado de bloqueo por E/S.
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 10_000).forEach(i ->
executor.submit(() -> procesarPeticion(i))
);
}
Java distingue entre la API clásica de entrada/salida (java.io, basada en streams de bytes y caracteres, como InputStream, OutputStream, Reader, Writer) y la API NIO (New I/O, desde Java 4, y NIO.2 desde Java 7), orientada a buffers y canales (ByteBuffer, Channel), que soporta E/S no bloqueante (non-blocking I/O) y proporciona la API java.nio.file (Path, Files) como sustituto moderno y más completo de la clase File tradicional.
Path ruta = Path.of("/datos/informe.txt");
List<String> lineas = Files.readAllLines(ruta, StandardCharsets.UTF_8);
Files.writeString(ruta, "Nuevo contenido", StandardOpenOption.APPEND);
JDBC (Java Database Connectivity) es la API estándar de Java para el acceso a bases de datos relacionales, basada en el patrón driver: cada fabricante de base de datos proporciona una implementación del driver JDBC que traduce las llamadas estándar al protocolo específico de su motor.
String url = "jdbc:postgresql://localhost:5432/oposiciones";
try (Connection conn = DriverManager.getConnection(url, "usuario", "contraseña");
PreparedStatement stmt = conn.prepareStatement(
"SELECT id, nombre FROM empleados WHERE departamento = ?")) {
stmt.setString(1, "TIC");
try (ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
System.out.println(rs.getInt("id") + ": " + rs.getString("nombre"));
}
}
} catch (SQLException e) {
logger.error("Error de acceso a datos", e);
}
En aplicaciones empresariales, JDBC rara vez se utiliza de forma directa, empleándose habitualmente capas de abstracción de mayor nivel como JPA/Hibernate (mapeo objeto-relacional, ORM) o Spring Data JPA, que reducen drásticamente el código repetitivo (boilerplate) necesario para el acceso a datos.
El ritmo de publicación semestral adoptado desde 2017 ha permitido incorporar de forma incremental numerosas mejoras al lenguaje:
var.java.net.http.HttpClient), nuevos métodos en String (isBlank(), strip(), repeat()).switch como expresión (no solo como sentencia), en preview.""").instanceof estabilizados.switch estabilizado, Sequenced Collections (nueva interfaz que unifica el acceso ordenado a colecciones).// Text Block (Java 15+)
String json = """
{
"nombre": "Ana",
"departamento": "TIC"
}
""";
// Pattern matching en switch (Java 21)
String describir(Object obj) {
return switch (obj) {
case Integer i when i > 0 -> "Entero positivo: " + i;
case Integer i -> "Entero no positivo: " + i;
case String s -> "Cadena de longitud " + s.length();
case null -> "Valor nulo";
default -> "Tipo desconocido";
};
}
Maven es la herramienta de construcción (build) y gestión de dependencias más extendida en el ecosistema Java, basada en un fichero de configuración declarativo en XML (pom.xml) y en el concepto de ciclo de vida de build estandarizado (validate, compile, test, package, verify, install, deploy), descargando las dependencias del repositorio central Maven Central.
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>es.administracion</groupId>
<artifactId>app-empleados</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.release>21</maven.compiler.release>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.3.0</version>
</dependency>
</dependencies>
</project>
Gradle, alternativa más moderna basada en un DSL en Groovy o Kotlin en lugar de XML, ofrece mayor flexibilidad y mejor rendimiento gracias a su sistema de caché incremental y compilación en demonio (Gradle Daemon), siendo la herramienta por defecto en proyectos Android y ganando adopción creciente en proyectos Java puros.
El siguiente ejemplo, equivalente conceptual al mostrado para PHP, ilustra una pequeña capa de acceso a datos en Java puro (sin framework), empleando JDBC, records, manejo de excepciones con try-with-resources y streams.
import java.sql.*;
import java.util.*;
public record Empleado(int id, String nombre, String departamento) {}
public class RepositorioEmpleados {
private final String url;
public RepositorioEmpleados(String url) {
this.url = url;
}
public Optional<Empleado> buscarPorId(int id) throws SQLException {
String sql = "SELECT id, nombre, departamento FROM empleados WHERE id = ?";
try (Connection conn = DriverManager.getConnection(url);
PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setInt(1, id);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
return Optional.of(new Empleado(
rs.getInt("id"), rs.getString("nombre"), rs.getString("departamento")
));
}
return Optional.empty();
}
}
}
public List<Empleado> listarPorDepartamento(String departamento) throws SQLException {
List<Empleado> resultado = new ArrayList<>();
String sql = "SELECT id, nombre, departamento FROM empleados WHERE departamento = ?";
try (Connection conn = DriverManager.getConnection(url);
PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setString(1, departamento);
try (ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
resultado.add(new Empleado(
rs.getInt("id"), rs.getString("nombre"), rs.getString("departamento")
));
}
}
}
return resultado;
}
}
public class Main {
public static void main(String[] args) {
var repo = new RepositorioEmpleados("jdbc:postgresql://localhost/oposiciones");
try {
List<Empleado> tic = repo.listarPorDepartamento("TIC");
tic.stream()
.map(Empleado::nombre)
.forEach(System.out::println);
} catch (SQLException e) {
System.err.println("Error de acceso a datos: " + e.getMessage());
}
}
}
Un framework de desarrollo (marco de trabajo) es una estructura de software reutilizable, semicompleta y extensible, que proporciona una arquitectura genérica sobre la que se construyen aplicaciones concretas. A diferencia de una simple librería, un framework define el flujo de control de la aplicación: es el framework quien invoca al código del desarrollador en los puntos adecuados (mediante callbacks, eventos o hooks), y no al contrario. Este principio se conoce como inversión de control (Inversion of Control, IoC), formulado originalmente por Ralph Johnson y Brian Foote, y resumido en la célebre máxima: "no llames al framework, el framework te llamará a ti" (Hollywood principle).
Los frameworks surgen como respuesta a varias necesidades del desarrollo de software profesional:
Es habitual confundir los conceptos de framework, librería y plataforma, si bien presentan diferencias conceptuales relevantes:
Una librería (o biblioteca) es un conjunto de funciones o clases que el programador invoca explícitamente desde su propio código cuando las necesita; el control del flujo de la aplicación permanece siempre en manos del desarrollador. Ejemplos: Guzzle (cliente HTTP en PHP), Jackson (serialización JSON en Java), una librería matemática o de manipulación de fechas.
Un framework, como se ha indicado, invierte esa relación: el desarrollador extiende o configura el framework (mediante herencia, implementación de interfaces, anotaciones o ficheros de configuración), y es el propio framework quien controla el ciclo de vida de la aplicación y llama al código del desarrollador. Ejemplos: Laravel, Symfony, Spring, Jakarta EE.
Una plataforma es un concepto más amplio, que engloba el entorno de ejecución completo (hardware, sistema operativo, máquina virtual, bibliotecas del sistema) sobre el que se ejecutan tanto las librerías como los frameworks y las aplicaciones finales. Java EE/Jakarta EE, por ejemplo, se define formalmente como una plataforma que especifica un conjunto de APIs estándar (Servlets, JPA, CDI, JMS, etc.), sobre la cual distintos fabricantes (Red Hat con WildFly, Eclipse con GlassFish/Payara, IBM con WebSphere/Open Liberty) construyen sus propias implementaciones, denominadas servidores de aplicaciones.
Los frameworks pueden clasificarse atendiendo a distintos criterios:
Por el ámbito de aplicación:
- Frameworks web full-stack, que cubren todas las capas de una aplicación web (enrutamiento, controladores, vistas, acceso a datos, seguridad): Laravel, Symfony, Spring Boot, Django (Python), Ruby on Rails.
- Microframeworks, minimalistas, que proporcionan únicamente el núcleo de enrutamiento HTTP y dejan al desarrollador la elección del resto de componentes: Slim y Lumen en PHP; Spark o Javalin en Java.
- Frameworks de frontend/SPA, orientados a la construcción de interfaces de usuario en el navegador: React, Angular, Vue.js (estos, técnicamente, JavaScript/TypeScript, pero de uso habitual junto a backends PHP o Java mediante APIs REST).
- Frameworks de testing: PHPUnit y Pest en PHP; JUnit, TestNG y Mockito en Java.
- Frameworks de integración y mensajería: Apache Camel, Spring Integration.
- Frameworks ORM (mapeo objeto-relacional): Eloquent (integrado en Laravel) y Doctrine en PHP; Hibernate (implementación de referencia de JPA) en Java.
Por el grado de especificación frente a implementación:
- Especificaciones/estándares, que definen un conjunto de interfaces y comportamientos sin imponer una implementación concreta: Jakarta EE, JPA, Servlet API. Distintos proveedores compiten ofreciendo implementaciones compatibles.
- Implementaciones concretas y de código abierto, que no siguen necesariamente un estándar previo, sino que se convierten en un estándar de facto por su adopción: Spring Framework (que sin ser una especificación oficial de Jakarta EE, se ha convertido en el estándar real del ecosistema Java empresarial).
Por el modelo arquitectónico que promueven:
- MVC (Modelo-Vista-Controlador): Laravel, Symfony, Spring MVC.
- Orientados a componentes/microservicios: Spring Boot junto con Spring Cloud, Micronaut, Quarkus.
- Orientados a eventos/reactivos: Spring WebFlux, Vert.x.
Los frameworks modernos materializan de forma sistemática varios patrones de diseño clásicos del Gang of Four y patrones arquitectónicos posteriores:
index.php o el Dispatcher Servlet en Spring MVC) recibe todas las peticiones HTTP y las redirige al controlador adecuado según las reglas de enrutamiento definidas.@Transactional).Laravel, creado por Taylor Otwell en 2011, es actualmente el framework PHP de referencia, caracterizado por su sintaxis expresiva y elegante, su filosofía de "convención sobre configuración" y un ecosistema propio muy completo: Eloquent (ORM basado en Active Record), Blade (motor de plantillas), Artisan (interfaz de línea de comandos para tareas de scaffolding, migraciones y generación de código), un sistema de colas (queues) para procesamiento asíncrono, broadcasting de eventos en tiempo real, y soluciones de despliegue propias como Laravel Forge y Laravel Vapor. Su curva de aprendizaje suave y su documentación exhaustiva lo han convertido en el framework PHP más utilizado en proyectos de nueva creación.
// Definición de ruta y controlador en Laravel
Route::get('/empleados/{id}', [EmpleadoController::class, 'show']);
class EmpleadoController extends Controller {
public function show(int $id) {
$empleado = Empleado::findOrFail($id); // Eloquent ORM
return view('empleados.show', compact('empleado'));
}
}
Symfony, desarrollado por SensioLabs (Fabien Potencier) desde 2005, sigue una filosofía más modular y orientada a estándares: está compuesto por más de cincuenta componentes independientes y reutilizables (HttpFoundation, Routing, DependencyInjection, Security, Console, entre otros) que pueden emplearse de forma aislada incluso fuera del framework completo. De hecho, muchos de estos componentes son empleados internamente por Laravel y por otros proyectos del ecosistema PHP, lo que convierte a Symfony en una pieza de infraestructura del propio ecosistema, más allá de su uso como framework full-stack. Symfony emplea Doctrine como ORM por defecto (patrón Data Mapper), Twig como motor de plantillas, y es habitual su elección en proyectos empresariales y de la administración pública por su estabilidad, su adherencia a estándares PSR y sus prolongados ciclos de soporte a largo plazo (LTS).
Spring Framework, iniciado por Rod Johnson en 2003, surgió como alternativa más ligera y flexible frente a la complejidad percibida del estándar J2EE/EJB de la época. Su núcleo (Spring Core) se basa en el contenedor de Inversión de Control y la Inyección de Dependencias, sobre el que se construyen módulos especializados: Spring MVC (aplicaciones web siguiendo el patrón MVC), Spring Data (acceso a datos, incluyendo Spring Data JPA para bases relacionales y soporte para bases NoSQL), Spring Security (autenticación y autorización), Spring Cloud (patrones de microservicios: service discovery, circuit breaker, config server) y Spring Batch (procesamiento por lotes).
Spring Boot, lanzado en 2014, no es un framework distinto sino una capa de configuración automática (auto-configuration) sobre Spring Framework, que elimina gran parte de la configuración XML/Java explícita que requería Spring clásico, integra un servidor de aplicaciones embebido (Tomcat, Jetty o Undertow por defecto), y populariza el modelo de "opinionated defaults": valores por defecto sensatos que cubren la mayoría de casos de uso, permitiendo arrancar un proyecto productivo con mínima configuración mediante los denominados starters (spring-boot-starter-web, spring-boot-starter-data-jpa, etc.).
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@RestController
@RequestMapping("/empleados")
public class EmpleadoController {
private final EmpleadoRepository repository;
public EmpleadoController(EmpleadoRepository repository) { // Inyección por constructor
this.repository = repository;
}
@GetMapping("/{id}")
public ResponseEntity<Empleado> obtener(@PathVariable Long id) {
return repository.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
}
@Repository
public interface EmpleadoRepository extends JpaRepository<Empleado, Long> {
List<Empleado> findByDepartamento(String departamento); // consulta derivada del nombre del método
}
Jakarta EE (denominación adoptada en 2019 tras la cesión de la marca "Java EE" por parte de Oracle a la Eclipse Foundation) es el conjunto de especificaciones estándar para el desarrollo de aplicaciones empresariales en Java: Servlets (componentes que procesan peticiones HTTP), JPA (Java Persistence API, estándar de ORM, cuya implementación de referencia histórica es Hibernate), CDI (Contexts and Dependency Injection, estándar de inyección de dependencias), JAX-RS (estándar para construir APIs REST) y JMS (Java Message Service, mensajería asíncrona). A diferencia de Spring, que es una implementación concreta de un único proveedor (ahora bajo VMware/Broadcom), Jakarta EE es un estándar implementable por distintos servidores de aplicaciones (WildFly, Payara, Open Liberty, TomEE), lo que en teoría facilita la portabilidad entre proveedores, si bien en la práctica Spring Boot ha desplazado a Jakarta EE como opción mayoritaria en nuevos desarrollos por su mayor simplicidad y el dinamismo de su comunidad.
Aunque PHP y Java son lenguajes de backend, es habitual que sus aplicaciones se integren con frameworks de frontend que se ejecutan en el navegador, comunicándose mediante APIs REST o GraphQL. Los más relevantes son React (biblioteca declarativa de Facebook/Meta basada en componentes y un DOM virtual), Angular (framework completo de Google basado en TypeScript) y Vue.js (framework progresivo de adopción incremental). Esta separación da lugar a arquitecturas desacopladas (headless o decoupled), en las que el backend en PHP o Java expone únicamente una API de datos, mientras que toda la lógica de presentación e interacción reside en una SPA (Single Page Application) servida de forma independiente, a menudo desde una CDN.
La calidad del software se apoya en frameworks específicos de pruebas automatizadas. En el ecosistema PHP, PHPUnit es el estándar histórico para pruebas unitarias e de integración, complementado por Pest (sintaxis más expresiva sobre el mismo motor) y herramientas de pruebas funcionales como Behat (BDD) o Laravel Dusk/Symfony Panther (pruebas de navegador). En el ecosistema Java, JUnit (actualmente en su versión 5, JUnit Jupiter) es el framework de referencia para pruebas unitarias, habitualmente combinado con Mockito para la creación de dobles de prueba (mocks, stubs) y con Testcontainers, que permite levantar dependencias reales (bases de datos, colas de mensajería) en contenedores Docker durante la ejecución de pruebas de integración, mejorando su fiabilidad frente al uso de mocks puros.
El despliegue moderno de aplicaciones PHP y Java se apoya de forma generalizada en contenedores Docker, que empaquetan la aplicación junto con su entorno de ejecución (intérprete PHP, JVM, dependencias) garantizando reproducibilidad entre entornos de desarrollo, pruebas y producción. La orquestación de dichos contenedores a gran escala se realiza habitualmente mediante Kubernetes. Los frameworks modernos facilitan esta integración: Spring Boot genera aplicaciones empaquetadas como un único fat jar ejecutable, fácilmente containerizable; existen además frameworks Java específicamente optimizados para entornos de contenedores y serverless con tiempos de arranque reducidos y menor consumo de memoria, como Quarkus y Micronaut, que recurren a técnicas de procesamiento en tiempo de compilación en lugar de reflexión en tiempo de ejecución, y que pueden compilarse a binarios nativos mediante GraalVM (Native Image).
Entre las tendencias más relevantes en el ámbito de los frameworks de desarrollo destacan: la consolidación de las arquitecturas de microservicios frente al monolito tradicional, apoyadas en Spring Cloud, Quarkus o Micronaut; la programación reactiva y no bloqueante (Spring WebFlux, Project Reactor) para maximizar el rendimiento bajo alta concurrencia con recursos limitados, tendencia que comparte motivación con la introducción de los hilos virtuales en Java 21; la adopción de APIs REST/GraphQL como contrato estándar entre frontend y backend independientemente del lenguaje empleado en cada capa; el auge de las arquitecturas serverless y de funciones como servicio (FaaS), que demandan frameworks con tiempos de arranque mínimos; y la incorporación progresiva de IA generativa como asistencia en la propia generación y revisión de código dentro de los flujos de desarrollo modernos (IDEs y pipelines de CI/CD).
Ambos lenguajes, pese a perseguir objetivos solapados en el ámbito del desarrollo de aplicaciones empresariales y web, presentan diferencias sustanciales que determinan su idoneidad según el contexto de uso.
En cuanto al tipado, PHP es dinámico y débil (aunque permite tipado opcional progresivamente más estricto desde la versión 7), mientras que Java es estático y fuerte, lo que se traduce en una detección más temprana de errores en Java a costa de mayor verbosidad y tiempo de compilación, frente a la mayor agilidad de desarrollo y flexibilidad de PHP.
En cuanto al modelo de ejecución, PHP es interpretado por petición (cada request HTTP típicamente inicia, ejecuta y finaliza el script, aunque PHP-FPM mantiene procesos persistentes para mejorar el rendimiento), mientras que Java ejecuta procesos de larga duración sobre la JVM, manteniendo el estado en memoria entre peticiones, lo que favorece un mejor rendimiento sostenido en aplicaciones de alta carga, a costa de un consumo de memoria base mayor y tiempos de arranque más prolongados.
En cuanto al ecosistema y caso de uso predominante, PHP domina en el desarrollo web tradicional, gestores de contenido (WordPress, Drupal), sedes electrónicas y aplicaciones de gestión de tamaño pequeño a medio, beneficiándose de un ciclo de desarrollo y despliegue muy ágil. Java domina en sistemas core bancarios, grandes plataformas transaccionales, integración de sistemas empresariales (ESB/EAI) y, crecientemente, en arquitecturas de microservicios de gran escala, donde su robustez, su tipado estático y la madurez de su tooling (depuración, profiling, monitorización) resultan especialmente valiosos.
En cuanto a la concurrencia, Java incorpora un modelo de hilos nativo y maduro desde su origen, recientemente reforzado con los hilos virtuales (Project Loom), mientras que PHP ha sido tradicionalmente single-threaded por petición, delegando la concurrencia en el modelo multiproceso del servidor web (varios procesos PHP-FPM ejecutándose en paralelo), aunque mecanismos más recientes como los fibers (PHP 8.1) introducen concurrencia cooperativa dentro de un mismo proceso.
En definitiva, no existe una superioridad absoluta de un lenguaje sobre el otro: la elección debe fundamentarse en los requisitos no funcionales del sistema (volumen transaccional esperado, criticidad, equipo disponible, plazos), siendo perfectamente legítimo —y habitual en la Administración— que ambos lenguajes convivan en el catálogo de aplicaciones de un mismo organismo, cada uno desempeñando el papel para el que resulta más idóneo.
PHP y Java constituyen dos de los pilares tecnológicos más relevantes en el desarrollo de sistemas de información, tanto en el sector privado como en la Administración Pública. PHP ha evolucionado desde un sencillo lenguaje de scripting hacia un lenguaje moderno con tipado progresivo, programación orientada a objetos completa y un rendimiento notablemente mejorado gracias al compilador JIT introducido en PHP 8. Java, por su parte, mantiene su posición como referencia en el desarrollo empresarial de gran escala, incorporando de forma constante mejoras significativas —records, pattern matching, hilos virtuales— que lo acercan a la expresividad de lenguajes más modernos sin renunciar a la robustez y el rendimiento que lo han caracterizado históricamente.
Los frameworks de desarrollo, en ambos ecosistemas, han resultado determinantes para estandarizar buenas prácticas, acelerar los ciclos de desarrollo y reducir la superficie de exposición a vulnerabilidades conocidas, materializando patrones de diseño consolidados como la inyección de dependencias, el patrón MVC o la programación orientada a aspectos. El conocimiento profundo de ambos lenguajes y de sus respectivos frameworks resulta, por tanto, una competencia esencial para el personal técnico de la Administración General del Estado, llamado a analizar, desarrollar, auditar y mantener sistemas de información heterogéneos que deben garantizar seguridad, eficiencia, interoperabilidad y sostenibilidad a largo plazo.
Tema elaborado con fines de preparación para oposiciones al Cuerpo TIC de la Administración General del Estado. Se recomienda complementar este contenido con la consulta directa de la documentación oficial de cada tecnología y con la normativa vigente en materia de Administración Electrónica (Esquema Nacional de Seguridad, Esquema Nacional de Interoperabilidad) en lo relativo a su aplicación en el desarrollo de sistemas de información públicos.