Llevo años desarrollando en el ecosistema Java en entornos empresariales y el uso de Spring y SpringBoot ya está muy extendido. Aún recuerdo los comienzos con Struts, JPA, EJB… pero la llegada de Spring nos comenzó a solucionar problemas comunes a todos los aplicativos (seguridad, acceso a base de datos, auditoría…) y se impuso como una solución bastante estándar en el mundo backend.

Al mismo tiempo en que los recursos sobre los que desplegamos han ido evolucionando, hemos perdido un poco el foco de las optimizaciones tanto de memoria física como de memoria RAM. Hemos alcanzado cifras bastante altas con imágenes de un microservicio simple cuando realmente no estamos utilizando todo ese software.

Una de las alternativas que más fuerza está cogiendo en el entorno empresarial es Quarkus. Es un proyecto que, aunque es relativamente joven (2019), cada vez va teniendo más integraciones y hace que sea una opción real en la que basar nuestras aplicaciones (dentro del ecosistema Java).

¿Por qué debería usar Quarkus?

Quarkus se basa en tres pilares fundamentales:

Si comparamos Spring vs Quarkus, Spring presenta una curva de aprendizaje mejor, mayor madurez y gran cantidad de configuraciones y librerías que hace que sea más lento en el arranque y sus ejecutables e imágenes sean más pesadas. Por contra, Quarkus aligera mucho el uso de memoria, es más ligero y rápido, pero no tiene la comunidad ni la cantidad de integraciones que tiene Spring.

Otro punto a favor de Quarkus es la reactividad, lo que permite manejar grandes volúmenes de solicitudes sin bloquear hilos, optimizando el uso de CPU y memoria. Por debajo usa el modelo de event loop basado en Vert.x para evitar bloqueos innecesarios. Además, se puede integrar con sistemas de mensajería no bloqueante (como Kafka). El resultado es tener sistemas de alto rendimiento, altamente escalables y eficientes.

He hecho pruebas con el mismo código de un microservicio para garantizar una comparación justa (respetando las mismas funcionalidades y sin irnos a una compilación nativa). El tamaño de la imagen es menos de la mitad y el tiempo de arranque pasa de 20-30 segundos a 1-2 segundos. Si extrapolamos esto a un ecosistema de microservicios, podríamos reducir significativamente los costos operacionales (OPEX) y mejorar la capacidad de escalado.

Además, Quarkus está diseñado para generar ejecutables nativos con GraalVM, lo que en mis pruebas ha reducido el uso de CPU al 0,33% en comparación con un microservicio normal basado en una imagen JDK. ¿Cuántos recursos podríamos ahorrar si migramos de Spring por Quarkus? Además de incidir directamente en el coste, un uso más eficiente del cloud hace que nuestras soluciones sean más sostenibles y reduzcan su huella ambiental.

Ok, pero… ¿cómo migramos? Vamos a intentar hacer una pequeña lista con un aplicativo que expone un endpoint REST y se comunica con una BBDD (aunque no es infalible).

Controlador:

@Api(description = "the Products API")
@Path("/products")
@jakarta.annotation.Generated(value = "org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen", date = "2025-02-27T10:46:38.175609+01:00[Europe/Madrid]")
public interface ProductsApi {

    @POST
    @Consumes({ "application/json" })
    @Produces({ "application/json", "application/problem+json" })
    @ApiOperation(value = "Method to add one product to the data storage", notes = "This method creates a new product with the given body information", authorizations = {
        @Authorization(value = "adfs", scopes = {
             })
         }, tags={ "products" })
    @ApiResponses(value = { 
        @ApiResponse(code = 201, message = "Created", response = ProductResponse.class),
        @ApiResponse(code = 401, message = "Unauthorized", response = ProblemDetailsResponse.class) })
    Response createProduct(@Valid @NotNull ProductRequest productRequest);

Persistencia:

@ApplicationScoped
public class ProductRepository implements PanacheRepository<ProductEntityModel> {

    public Optional<ProductEntityModel> findByIdOptional(Long id) {
        return find("id", id).firstResultOptional();
    }

    @Transactional
    public ProductEntityModel save(ProductEntityModel product) {
        persist(product);
        return product;
    }

    @Transactional
    public boolean deleteById(Long id) {
        return ProductEntityModel.deleteById(id);
    }
}
import java.math.BigDecimal;
import java.time.OffsetDateTime;

import io.quarkus.hibernate.orm.panache.PanacheEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;

@Entity
@Table(name = "products")
public class ProductEntityModel extends PanacheEntity {

    private String name;
    private String description;

Desarrollando:

¿Y qué puedo hacer si quiero continuar con Spring?

No es para nada el fin del mundo, pero sí que actualizar las versiones ayuda a ganar compatibilidad para poder generar imágenes nativas. Spring está eliminando la reflexión de su código poco a poco.

Si quiero optimizar y no puedo usar GraalVM por compatibilidad, lo que podemos hacer es usar Spring CDS (Class Data Sharing). Es una optimización en la JVM que permite compartir clases preprocesadas para reducir tiempos de inicio y uso de memoria (es aparte GraalVM). Cuando se ejecuta por primera vez, se crea un archivo CDS (una especie de caché) de clases.

El tiempo de inicio y el consumo de memoria se reducen significativamente porque la JVM carga directamente las clases procesadas en ejecuciones anteriores en lugar de hacerlo desde los .jar. Esto es ideal para Java 17/21 y SpringBoot 3.X o superior.

Sería algo así:

java -XX:DumpLoadedClassList=app.classlist -jar ./target/springsample-0.0.1.jar
java -Xshare:dump -XX:SharedClassListFile=app.classlist -XX:SharedArchiveFile=app.jsa -jar ./target/springsample-0.0.1.jar
java -Xshare:on -XX:SharedArchiveFile=app.jsa -jar ./target/springsample-0.0.1.jar

Al final, como perfiles de desarrollo debemos buscar reducir costes y hacer software más sostenible. Además, la optimización de nuestros recursos puede llegar a ser una ventaja competitiva. La elección del uso de un framework u otro depende de muchos factores como la experiencia de los equipos de desarrollo, magnitud de la solución, de la compañía…

CDS es una mejora incremental dentro de la JVM, mientras que GraalVM es una reingeniería más profunda con mayores beneficios, pero requiere adaptaciones.

Cuéntanos qué te parece.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.

Suscríbete