← Volver al blog
·9 min de lectura

Headless Delivery vs JSONWS: elegir la API correcta en Liferay DXP

LiferayAPIJava

Dos APIs, una plataforma, mucha confusion

Si trabajas con Liferay DXP, en algun momento te habras encontrado con una situacion confusa: para obtener los mismos datos -- digamos, una lista de articulos web -- tienes al menos dos formas completamente diferentes de hacerlo. Una usa /api/jsonws y parece llamar directamente a metodos Java. La otra usa /o/headless-delivery/v1.0 y se comporta como una API REST moderna. Ambas funcionan, ambas devuelven datos, pero tienen filosofias radicalmente distintas.

Entender las diferencias entre JSONWS y Headless Delivery no es un ejercicio academico. La eleccion impacta la seguridad, mantenibilidad y futuro de tu proyecto. Despues de trabajar con ambas APIs desde Liferay 7.1 hasta 7.4, puedo decir que la decision no siempre es obvia, especialmente cuando migras proyectos legacy.

JSONWS: la API legacy

JSONWS (JSON Web Services) fue el primer intento de Liferay de exponer sus servicios internos como APIs HTTP. Cada servicio Java registrado en Liferay se convierte automaticamente en un endpoint invocable via HTTP.

Como funciona

Accede a /api/jsonws en cualquier instancia de Liferay y veras un explorador interactivo con cientos de servicios disponibles. Cada uno corresponde a un metodo Java real. Por ejemplo:

# Obtener un articulo web por groupId y articleId
/api/jsonws/journal.journalarticle/get-article \
  -d groupId=20123 \
  -d articleId=MI-ARTICULO \
  -d version=1.0

Desde JavaScript en el contexto de Liferay, usas el objeto global Liferay.Service():

Liferay.Service(
  '/journal.journalarticle/get-article',
  {
    groupId: themeDisplay.getScopeGroupId(),
    articleId: 'MI-ARTICULO',
    version: 1.0,
  },
  function (article) {
    console.log(article.title)
  }
)

Esto ejecuta una llamada AJAX que invoca directamente el metodo JournalArticleService.getArticle() en el backend Java. La respuesta es una serializacion directa del objeto Java, lo que significa que obtienes todos los campos internos de la entidad, incluidos muchos que probablemente no necesitas.

Ventajas de JSONWS

  • Acceso directo a todo: Cada servicio registrado en Liferay esta disponible. Si existe un metodo Java para hacerlo, puedes invocarlo.
  • Simplicidad conceptual: Llamas a un metodo, pasas parametros, obtienes resultado. No hay que pensar en recursos REST ni rutas semanticas.
  • Explorador integrado: La interfaz en /api/jsonws permite probar servicios directamente, ver parametros y respuestas.

Problemas de JSONWS

  • Seguridad deficiente: JSONWS expone la superficie completa de servicios de Liferay. Por defecto, un usuario autenticado puede llamar a cualquier servicio para el que tenga permisos, incluyendo algunos potencialmente peligrosos. No hay concepto de scopes o restriccion por aplicacion.
  • Sin estandar: La respuesta es una serializacion ad-hoc de objetos Java. No sigue OpenAPI, JSON:API, ni ningun estandar. Cada servicio tiene su propio formato de respuesta.
  • Acoplamiento fuerte: Tu codigo frontend depende de la firma exacta del metodo Java. Si Liferay cambia un parametro o renombra un servicio entre versiones, tu integracion se rompe.
  • CSRF obligatorio: Cada peticion necesita el token p_auth para proteccion CSRF, lo que complica las llamadas desde fuera del contexto de Liferay.
  • Sin paginacion estandar: Algunos servicios aceptan start y end para paginar, otros no. No hay un formato uniforme de respuesta paginada.

Headless Delivery: la API moderna

A partir de Liferay 7.1 (y mejorando significativamente en 7.2+), Liferay introdujo APIs basadas en la especificacion OpenAPI. Estas APIs estan agrupadas bajo el prefijo /o/ y siguen convenciones REST estandar.

Endpoints principales

Liferay organiza sus APIs Headless en varios modulos:

  • /o/headless-delivery/v1.0: Contenido estructurado, documentos, blogs, categorias, paginas
  • /o/headless-admin-user/v1.0: Usuarios, organizaciones, roles
  • /o/headless-admin-taxonomy/v1.0: Vocabularios y categorias
  • /o/headless-commerce-delivery-catalog/v1.0: Productos y catalogo (Commerce)
  • /o/c/{objectName}: Objects personalizados (auto-generados)

Como funciona

# Obtener contenido estructurado de un site
curl -X GET \
  "http://localhost:8080/o/headless-delivery/v1.0/sites/20123/structured-contents" \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiI..." \
  -H "Accept: application/json"

La respuesta sigue un formato estandar con paginacion:

{
  "items": [
    {
      "id": 45231,
      "title": "Bienvenida",
      "dateCreated": "2025-06-15T10:30:00Z",
      "dateModified": "2025-06-20T14:22:00Z",
      "contentFields": [
        {
          "name": "contenido",
          "contentFieldValue": {
            "data": "<p>Texto del articulo...</p>"
          }
        }
      ]
    }
  ],
  "page": 1,
  "pageSize": 20,
  "totalCount": 134,
  "lastPage": 7
}

Nota la diferencia: la respuesta esta estructurada, los campos tienen nombres semanticos, la paginacion es consistente, y los datos estan normalizados. No estas viendo la serializacion cruda de un objeto Java.

Comparacion directa

| Aspecto | JSONWS | Headless Delivery | |---------|--------|-------------------| | Base path | /api/jsonws | /o/headless-*/v1.0, /o/c/ | | Autenticacion | Cookie + p_auth (CSRF) | OAuth2, Basic Auth, Cookie | | Formato respuesta | Serializacion Java ad-hoc | JSON estandar con schema OpenAPI | | Paginacion | Inconsistente (start/end) | Estandar (page/pageSize/totalCount) | | Filtrado | Parametros por metodo | OData syntax uniforme | | Documentacion | Explorador en /api/jsonws | OpenAPI spec + /o/api | | Versionado | Sin versiones (cambia con Liferay) | Versionado en URL (v1.0, v2.0) | | Scopes | Todo o nada | OAuth2 scopes granulares | | GraphQL | No | Si, en /o/graphql | | Futuro | Deprecated progresivamente | Modelo recomendado |

Por que JSONWS esta siendo deprecado

Liferay ha sido gradual pero consistente en mover el ecosistema hacia Headless. Las razones principales son:

Seguridad

JSONWS expone internamente servicios que no estaban disenados para acceso HTTP. La distincion entre *Service (con verificacion de permisos) y *LocalService (sin verificacion) es critica en Java pero invisible via JSONWS. Un desarrollador que no entiende esta distincion puede crear vulnerabilidades serias.

En versiones recientes de Liferay, el acceso a JSONWS se ha restringido progresivamente. En Liferay DXP 7.4, muchos servicios estan deshabilitados por defecto y requieren configuracion explicita para habilitarse.

La cuestion de LocalService

Este es un punto que merece atencion especial. En la arquitectura interna de Liferay, cada entidad tiene dos capas de servicio:

  • *LocalService: Ejecuta operaciones directamente, sin verificar permisos. Es para uso interno entre modulos del servidor.
  • *Service: Wrapper que primero verifica que el usuario tiene permisos, y luego delega al LocalService.

En codigo Java dentro de Liferay, un modulo puede llamar a JournalArticleLocalService.getArticle() directamente, bypaseando la verificacion de permisos. Esto es valido en ciertos contextos (un servicio del sistema que necesita acceso incondicional), pero peligroso si se expone externamente.

JSONWS originalmente exponia ambos. Liferay fue cerrando el acceso a LocalServices via HTTP progresivamente, pero la confusion persiste en proyectos que se iniciaron en versiones anteriores.

Headless Delivery resuelve esto de raiz: todos los endpoints pasan por la capa de permisos. No hay forma de bypasear la verificacion desde la API REST.

Estandares y ecosistema

JSONWS no sigue ningun estandar HTTP o API reconocido. Esto significa que herramientas estandar (Postman collections generadas automaticamente, generadores de clientes, documentacion interactiva) no funcionan bien con JSONWS.

Headless Delivery expone un spec OpenAPI completo en /o/api. Puedes importar esta especificacion en cualquier herramienta que soporte OpenAPI y generar automaticamente clientes en cualquier lenguaje, colecciones de Postman, o documentacion.

La alternativa GraphQL

Ademas de REST, Liferay expone un endpoint GraphQL en /o/graphql que permite consultar los mismos datos disponibles en Headless Delivery pero solicitando solo los campos que necesitas.

query {
  structuredContents(siteKey: "20123", filter: "title eq 'Inicio'") {
    items {
      id
      title
      dateModified
      contentFields {
        name
        contentFieldValue {
          data
        }
      }
    }
    totalCount
  }
}

GraphQL es particularmente util cuando:

  • Necesitas datos de multiples entidades en una sola peticion
  • Quieres minimizar el payload (no recibes campos que no usas)
  • Tu frontend ya usa Apollo Client o similar

Sin embargo, en la practica, REST sigue siendo la opcion mas comun en proyectos Liferay porque la documentacion y ejemplos estan mas orientados a REST, y la mayoria de equipos enterprise ya tienen herramientas establecidas para APIs REST.

Configuracion de OAuth2

Para consumir Headless APIs desde fuera de Liferay (aplicaciones externas, Client Extensions remotas, integraciones), necesitas configurar OAuth2.

Client Credentials (para servicios backend)

Este flujo es el indicado para integraciones servidor-a-servidor donde no hay un usuario humano involucrado:

  1. En el Panel de Control de Liferay, ve a OAuth2 Administration
  2. Crea una nueva aplicacion con:
    • Client Profile: Headless Server
    • Allowed Grant Types: Client Credentials
  3. Anota el Client ID y Client Secret
  4. Asigna scopes: selecciona que APIs puede consumir esta aplicacion

Para obtener un token:

curl -X POST "http://localhost:8080/o/oauth2/token" \
  -d "grant_type=client_credentials" \
  -d "client_id=tu-client-id" \
  -d "client_secret=tu-client-secret"

La respuesta incluye un access_token que usas en el header Authorization: Bearer.

Authorization Code (para aplicaciones frontend)

Para aplicaciones donde un usuario se autentica:

  1. Crea la aplicacion OAuth2 con Allowed Grant Types: Authorization Code + PKCE
  2. Configura las Callback URIs de tu aplicacion
  3. Tu aplicacion redirige al usuario a Liferay para autenticarse
  4. Liferay redirige de vuelta con un codigo de autorizacion
  5. Tu backend intercambia el codigo por un token

Migracion practica: de JSONWS a Headless

Si tienes un proyecto que usa JSONWS y necesitas migrar, el enfoque gradual funciona mejor que una reescritura total.

Paso 1: Inventario

Lista todas las llamadas JSONWS en tu codigo. Busca Liferay.Service(, /api/jsonws, y p_auth como indicadores. Clasifica cada llamada por entidad (journal, user, document, etc.).

Paso 2: Mapeo de equivalencias

Para cada llamada JSONWS, encuentra el endpoint Headless equivalente. Ejemplos comunes:

# Articulos web
JSONWS:    /api/jsonws/journal.journalarticle/get-articles
Headless:  /o/headless-delivery/v1.0/sites/{siteId}/structured-contents

# Documentos
JSONWS:    /api/jsonws/dlapp/get-file-entries
Headless:  /o/headless-delivery/v1.0/sites/{siteId}/documents

# Usuarios
JSONWS:    /api/jsonws/user/get-user-by-id
Headless:  /o/headless-admin-user/v1.0/user-accounts/{id}

# Categorias
JSONWS:    /api/jsonws/assetcategory/get-categories
Headless:  /o/headless-admin-taxonomy/v1.0/taxonomy-categories

Paso 3: Migracion incremental

Reemplaza las llamadas una por una, empezando por las mas usadas. Para cada una:

  1. Implementa la nueva llamada usando Headless
  2. Verifica que los datos devueltos contengan lo que tu frontend necesita (los formatos son diferentes)
  3. Adapta el parsing de la respuesta (la estructura JSON cambia)
  4. Prueba con los mismos permisos de usuario

Paso 4: Adaptar la autenticacion

Si antes dependias de la cookie de sesion + CSRF token, evalua si necesitas migrar a OAuth2. Para Client Extensions que corren dentro de Liferay, Liferay.Util.fetch() sigue manejando la autenticacion automaticamente. Para integraciones externas, configura OAuth2.

Conclusion practica

Mi recomendacion despues de varios proyectos con ambas APIs: si empiezas un proyecto nuevo en Liferay 7.4, usa exclusivamente Headless Delivery. No hay razon valida para elegir JSONWS en un proyecto greenfield.

Si mantienes un proyecto legacy, migra gradualmente. JSONWS sigue funcionando, pero cada version de Liferay restringe mas su acceso. Es mejor migrar proactivamente que verse forzado cuando una actualizacion rompa algo.

Y si necesitas acceso a un servicio interno que Headless no expone, la solucion correcta no es volver a JSONWS: es crear un endpoint Headless personalizado a traves de REST Builder o exponer la funcionalidad como un Object Action. El ecosistema de Liferay se mueve firmemente hacia Headless, y alinear tu proyecto con esa direccion reduce la deuda tecnica futura.