FastAPI para automatizaciones internas: cuándo elegir Python en vez de Spring Boot
Cuándo tiene sentido usar FastAPI para herramientas internas y cuándo es mejor Spring Boot. Criterios técnicos reales.

Hace un par de años me pidieron montar una herramienta interna para que el equipo de datos pudiera lanzar procesos de scraping bajo demanda. Necesitaba una API REST sencilla, integración con scripts Python que ya existían y un panel mínimo para ver el estado de las ejecuciones. Mi primer impulso fue montar un Spring Boot. Mi segundo impulso, después de pensar diez minutos, fue abrir un fichero main.py y tener un endpoint funcionando en veinte líneas con FastAPI.
Esa decisión me ahorró días de trabajo. Pero no siempre es así. He tenido otros casos donde empecé con FastAPI y terminé deseando tener la estructura de Spring Boot. La diferencia entre una buena y una mala decisión técnica aquí no es qué framework es “mejor”, sino entender qué necesitas realmente y dónde va a doler cada opción.
El contexto importa más que el framework
Antes de comparar features, hay que definir qué tipo de herramienta estás construyendo. No es lo mismo un microservicio que va a recibir tráfico de producción que un endpoint interno que usan tres personas del equipo para lanzar scripts.
Las herramientas internas suelen tener estas características:
- Pocos usuarios (equipo técnico, operaciones, datos)
- Requisitos que cambian rápido (“ahora necesitamos también exportar a CSV”)
- Integración con scripts existentes, notebooks o pipelines de datos
- Prioridad en velocidad de desarrollo sobre escalabilidad
- Ciclo de vida corto o incierto (puede que se use tres meses y se descarte)
En ese contexto, las prioridades técnicas son diferentes a las de un servicio de producción. Y ahí es donde FastAPI y Spring Boot se diferencian de verdad.
FastAPI: cuándo tiene sentido
FastAPI brilla cuando necesitas una API HTTP rápida de montar que se integre con el ecosistema Python. Su diseño está pensado para exactamente eso: definir endpoints con tipado, generar documentación automática y arrancar con el mínimo de configuración.
Un ejemplo real. Necesitaba un servicio interno que recibiera una URL, lanzara un proceso de scraping con httpx y BeautifulSoup, y devolviera el resultado estructurado:
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, HttpUrl
import httpx
from bs4 import BeautifulSoup
app = FastAPI(title="Scraping Service", version="0.1.0")
class ScrapeRequest(BaseModel):
url: HttpUrl
selector: str = "article"
class ScrapeResult(BaseModel):
url: str
title: str | None
content: str
word_count: int
@app.post("/scrape", response_model=ScrapeResult)
async def scrape(request: ScrapeRequest):
async with httpx.AsyncClient(timeout=30) as client:
response = await client.get(str(request.url))
if response.status_code != 200:
raise HTTPException(
status_code=502,
detail=f"Target returned {response.status_code}"
)
soup = BeautifulSoup(response.text, "html.parser")
element = soup.select_one(request.selector)
if not element:
raise HTTPException(status_code=404, detail="Selector not found")
text = element.get_text(strip=True)
return ScrapeResult(
url=str(request.url),
title=soup.title.string if soup.title else None,
content=text[:5000],
word_count=len(text.split())
)Eso es un servicio funcional. Con uvicorn main:app --reload lo tengo corriendo en segundos. La documentación interactiva está en /docs sin configurar nada. Pydantic valida la entrada y serializa la salida. Si necesito añadir otro endpoint, son diez líneas más.
Montar esto en Spring Boot no es difícil, pero necesito un proyecto con estructura, dependencias, configuración de Jackson, una clase de request, una de response, un controlador, posiblemente un servicio. No es que sea complicado, es que es más ceremonia para el mismo resultado en un contexto donde la ceremonia no aporta.
Spring Boot: cuándo sigue siendo mejor opción
FastAPI tiene sus límites. Y esos límites aparecen cuando el proyecto crece o cuando los requisitos son los de un servicio de producción real.
Caso concreto: un servicio interno que empezó como “una API para consultar configuraciones” y fue creciendo hasta tener autenticación, acceso a base de datos, lógica de negocio compleja, transacciones, colas de mensajes y un ciclo de vida de varios años. Eso lo empecé en FastAPI y llegó un punto donde echaba de menos la inyección de dependencias de Spring, las transacciones declarativas, la integración con Kafka y la estructura que el framework te obliga a tener.
Spring Boot es mejor opción cuando:
- El servicio va a crecer y necesita estructura desde el principio
- Hay lógica de negocio compleja con transacciones
- El equipo ya trabaja con JVM y el servicio debe integrarse con otros servicios Java/Kotlin
- Necesitas un ecosistema maduro de librerías enterprise (seguridad, mensajería, batch)
- El ciclo de vida es largo y la mantenibilidad importa más que la velocidad inicial
Tipado: Pydantic vs Kotlin type-safe
Una de las cosas que más me gustan de FastAPI es Pydantic. El sistema de validación es potente, expresivo y se integra de forma natural con el framework:
from pydantic import BaseModel, Field, field_validator
from datetime import datetime
from enum import Enum
class Priority(str, Enum):
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
class TaskCreate(BaseModel):
name: str = Field(min_length=1, max_length=200)
description: str | None = None
priority: Priority = Priority.MEDIUM
due_date: datetime | None = None
@field_validator("due_date")
@classmethod
def due_date_must_be_future(cls, v):
if v and v < datetime.now():
raise ValueError("due_date must be in the future")
return vEn Kotlin con Spring Boot, el equivalente sería algo como:
data class TaskCreate(
@field:NotBlank
@field:Size(max = 200)
val name: String,
val description: String? = null,
val priority: Priority = Priority.MEDIUM,
@field:Future
val dueDate: LocalDateTime? = null
)
enum class Priority { LOW, MEDIUM, HIGH }Ambos enfoques son válidos. La diferencia está en la filosofía:
| Aspecto | Pydantic (FastAPI) | Bean Validation (Spring) |
|---|---|---|
| Validación | En el modelo, con validators custom | Anotaciones + validator classes |
| Serialización | Integrada en el modelo | Jackson separado |
| Coerción de tipos | Automática (str “3” -> int 3) | Estricta por defecto |
| Documentación | Genera JSON Schema automático | Necesita SpringDoc/Swagger |
| Runtime vs compilación | Runtime (Python) | Mix: compilación (Kotlin) + runtime (annotations) |
Pydantic es más flexible para prototipos rápidos. El sistema de tipos de Kotlin es más seguro a largo plazo. No es que uno sea mejor que otro; sirven para contextos diferentes.
Mantenibilidad: donde se nota la diferencia a largo plazo
Aquí es donde tengo que ser honesto con Python. Un proyecto FastAPI de 500 líneas es un placer. Un proyecto FastAPI de 15.000 líneas empieza a necesitar mucha disciplina que el lenguaje no te obliga a tener.
Python con type hints ha mejorado enormemente, pero los tipos son opcionales. Puedes escribir un servicio entero sin type hints y funciona igual. En un equipo de dos personas que mantienen una herramienta interna, eso no es un problema. En un equipo de ocho personas que mantienen un servicio en producción durante tres años, los type hints opcionales significan que parte del código los tendrá y parte no, y el linter te avisa pero no te obliga.
En Kotlin, el compilador te obliga. No puedes pasar un null donde se espera un String. No puedes ignorar un tipo de retorno sin hacer un cast explícito. Esa rigidez cuesta al principio pero paga dividendos cuando el proyecto crece.
Mi regla personal:
Si el proyecto va a durar menos de seis meses y lo mantiene el mismo equipo que lo construyó, FastAPI. Si va a durar más o va a rotar el equipo, Spring Boot con Kotlin.
No es una regla absoluta, pero me ha funcionado como heurística.
Integración con scripts y herramientas de datos
Este es uno de los puntos fuertes de FastAPI. Si tu organización tiene scripts Python para procesamiento de datos, notebooks de Jupyter, pipelines con pandas o modelos de ML, un servicio FastAPI se integra de forma natural:
from fastapi import FastAPI, BackgroundTasks
from app.pipelines import run_daily_report
from app.models import ReportConfig
app = FastAPI()
@app.post("/reports/generate")
async def generate_report(
config: ReportConfig,
background_tasks: BackgroundTasks
):
background_tasks.add_task(run_daily_report, config)
return {"status": "queued", "report_type": config.report_type}Ese run_daily_report puede ser una función que ya existía, que usa pandas, que llama a APIs externas con requests, que genera un CSV y lo sube a S3. No necesitas adaptarla, solo la importas.
En Spring Boot, integrar con código Python requiere llamadas a proceso externo, APIs REST intermedias o algo como GraalPython. Es factible pero no es natural.
Esto no es un argumento para usar FastAPI siempre. Es un argumento para usarlo cuando el ecosistema alrededor es Python. Si el ecosistema es JVM, Spring Boot es la elección natural por la misma razón.
Deploy: las diferencias prácticas
Desplegar un servicio FastAPI y uno Spring Boot tiene diferencias reales que afectan a la operación diaria.
FastAPI con Docker:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]Imagen resultante: ~150MB. Tiempo de arranque: 1-2 segundos. Memoria en reposo: ~50MB.
Spring Boot con Docker:
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY build/libs/app.jar .
CMD ["java", "-jar", "app.jar"]Imagen resultante: ~200-300MB. Tiempo de arranque: 5-15 segundos (sin GraalVM). Memoria en reposo: ~150-300MB.
Para herramientas internas que se levantan y bajan con frecuencia, o que corren en serverless (Lambda, Cloud Run), la diferencia de arranque y memoria es relevante. FastAPI es más ligero. Con GraalVM native image puedes compilar Spring Boot a un binario nativo que arranca en milisegundos, pero la compilación nativa tiene sus propios problemas (tiempo de build, compatibilidad con librerías, reflection).
| Criterio | FastAPI | Spring Boot | Spring Boot + GraalVM |
|---|---|---|---|
| Imagen Docker | ~150MB | ~250MB | ~100MB |
| Arranque | 1-2s | 5-15s | menos de 1s |
| Memoria reposo | ~50MB | ~200MB | ~50MB |
| Tiempo de build | Segundos | 30s-2min | 5-15min |
| Serverless | Natural | Posible | Bueno |
| Ecosistema librerías | pip install | Maven/Gradle | Limitado (reflection) |
Tabla comparativa por escenario
Después de usar ambos en diferentes contextos, esta es mi guía de decisión:
| Escenario | Recomendación | Por qué |
|---|---|---|
| API interna para lanzar scripts Python | FastAPI | Integración directa, sin fricción |
| Microservicio con lógica de negocio compleja | Spring Boot | Estructura, transacciones, DI |
| Wrapper de modelo ML | FastAPI | Ecosistema Python, performance async |
| Servicio que se integra con Kafka/RabbitMQ | Spring Boot | Spring Cloud Stream, madurez |
| Herramienta de datos para equipo de analytics | FastAPI | Pandas, notebooks, ecosystem |
| Servicio con autenticación enterprise (LDAP, OAuth) | Spring Boot | Spring Security, madurez |
| Prototipo rápido para validar una idea | FastAPI | Velocidad de desarrollo |
| Servicio que va a durar 3+ años | Spring Boot | Mantenibilidad, tipado, estructura |
| CRUD simple con pocos endpoints | FastAPI | Menos ceremonia |
| Orquestación de workflows complejos | Depende del equipo | Python si ya hay infra Python, JVM si ya hay infra JVM |
Lo que he aprendido usándolos en paralelo
Uso ambos frameworks en mi día a día. Spring Boot con Kotlin para los servicios principales y FastAPI para herramientas auxiliares, prototipos y todo lo que toca el ecosistema de datos. Esta convivencia me ha enseñado un par de cosas:
La primera es que la elección del framework importa menos de lo que la gente discute en Twitter. Lo que importa es que la herramienta encaje con el contexto: el equipo, el ecosistema, el ciclo de vida del proyecto y los requisitos reales (no los imaginados).
La segunda es que cambiar de framework tiene un coste real. No solo técnico, sino cognitivo. Cada vez que saltas de Kotlin a Python cambias de modelo mental: mutabilidad, tipado, manejo de errores, testing. Si tu equipo hace ese salto constantemente, pierdes la fluidez que ganas con la especialización.
La tercera es que la mejor herramienta interna es la que se construye rápido, se usa de verdad y se puede tirar sin drama cuando deja de ser útil. Si elegir Spring Boot para una herramienta que va a vivir tres meses significa que tardas una semana más en tenerla lista, has optimizado para el criterio equivocado.
No elijas framework por preferencia personal ni por la tendencia del momento. Elige por el contexto concreto del problema que vas a resolver. Y si no estás seguro, empieza con lo más simple que pueda funcionar. Siempre puedes migrar después, pero rara vez necesitas hacerlo.


