The API uses a standardized error format via the GenericExceptionResolver. Every GraphQL error response includes an errorCode field inside the extensions block identifying the error type.
| Category | Code | Description | Exception class | Typical scenario |
|---|---|---|---|---|
| 1xxx Auth | 1001 | Invalid credentials | InvalidCredentialsException |
Wrong email or password at login |
| 1xxx Auth | 1002 | Token expired/invalid | TokenExpiredException |
Expired or malformed refresh/verification token |
| 2xxx Business | 2001 | Email already registered | EmailAlreadyExistsException |
Duplicate email on user registration |
| 2xxx Business | 2003 | Media already exists | MediaAlreadyExist |
Attempt to add an already-registered media item |
| 2xxx Business | 2004 | Input validation failed | InputValidationException |
Invalid field value (short password, etc.) |
| 2xxx Business | 2005 | Media already in library | MediaAlreadyInLibraryException |
Media already added to user's library |
| 3xxx Resource | 3403 | Access denied | AccessDeniedException |
Trying to view a user's private library |
| 3xxx Resource | 3404 | Resource not found | NotFoundException |
User or media ID does not exist |
| 5xxx Internal | 5000 | Unexpected server error | (unmapped exceptions) | Any error without a specific code |
| 5xxx Internal | 5001 | Database error | DataAccessException |
Low-level database failure |
{
"errors": [
{
"message": "Invalid credentials",
"locations": [...],
"path": [...],
"extensions": {
"errorCode": 1001
}
}
]
}- Business exceptions (
1xxx–2xxx) return onlymessage+errorCode; the stack trace is never sent to the client. - Internal errors (
5xxx) are logged atERRORlevel on the server; unmapped exceptions returnerrorCode: 5000with the generic message"Unexpected server error".
See the GraphQL guide: GraphQL Guide See the license: Licenses
Other Docs: quick links to other markdown files in the project
INDEX.md: Project index and quick references - INDEX.mdMONITORING.md: Monitoring & observability notes - MONITORING.mddocker/ARCHITECTURE.md: Docker / architecture notes - docker/ARCHITECTURE.mddocker/DEPLOYMENT.md: Docker deployment guide - docker/DEPLOYMENT.mddocker/QUICK_REFERENCE.md: Docker quick reference - docker/QUICK_REFERENCE.mddocker/SETUP.md: Docker setup guide - docker/SETUP.mddocker/TESTING.md: Docker testing notes - docker/TESTING.mdgraphql_guide.md: GraphQL guide - graphql_guide.md
This project exposes a simple administrative Batch API for running and controlling Spring Batch jobs at runtime. The controller is available at the path /admin/batch and provides three endpoints:
-
Run job:
GET /admin/batch/run/{jobName}- Description: Triggers a job bean (by Spring bean name) found in the application context.
- Notes: Adds a timestamp parameter to ensure a new JobParameters set for each run.
- Authorization: requires
adminrole (method guarded by@PreAuthorize("hasRole('admin')")). - Response: HTTP 200 with
Job ID: <id>when submitted, or HTTP 500 with error message on failure.
-
Stop job execution:
GET /admin/batch/stop/{executionId}- Description: Requests to stop a running job execution via
JobOperator.stop(executionId). - Authorization: requires
adminrole. - Response: HTTP 200 with
stop requested: true|falseor HTTP 500 on error.
- Description: Requests to stop a running job execution via
-
Abandon job execution:
GET /admin/batch/abandon/{executionId}- Description: Marks a failed/stopped execution as abandoned using
JobOperator.abandon(executionId). - Authorization: requires
adminrole. - Response: HTTP 200 with
abandonedor HTTP 500 on error.
- Description: Marks a failed/stopped execution as abandoned using
Implementation notes:
- The controller locates jobs by bean name from the
ApplicationContextand uses an asynchronousJobLauncher(qualified asasyncJobLauncher) to start the job, returning theJobExecutionid. - For stop/abandon actions the controller calls the provided
JobOperatorto control executions. - Make sure the
adminrole is available and that requests are authenticated; adjust security configuration if necessary for your environment.
Java 21 / Spring Boot 3 application with optional native image build using GraalVM.
- Install Java 21 (GraalVM or any JDK 21).
- Clone repository.
- Build:
./gradlew clean build - Run dev (hot reload):
./gradlew bootRun
Prerequisites:
- GraalVM JDK 21 installed (same drive as the project to avoid the "'other' has different root" error).
- Visual Studio 2022 (Community or Build Tools) with workload: Desktop development with C++.
Required components:
- MSVC v143 x64/x86 build tools
- Windows 10/11 SDK
- C++ CMake tools for Windows (Optional: Ninja, Git)
Checks:
where cl
where native-image
java -version
If cl not found, open the proper developer prompt.
Build (from an "x64 Native Tools Command Prompt for VS 2022"):
cd E:\Script\EspacoGeek\backend
"C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64 && .\gradlew clean nativeCompile --stacktrace
Resulting binary (Windows): build\native\nativeCompile\espaco-geek.exe
If you moved GraalVM or project: ensure both are on same drive letter (e.g. E:).
Install system dependencies:
sudo apt update
sudo apt install -y curl zip unzip build-essential zlib1g-dev libssl-dev pkg-config
Install GraalVM via SDKMAN:
curl -s https://get.sdkman.io | bash
source "$HOME/.sdkman/bin/sdkman-init.sh"
sdk install java 21.0.8-graal
sdk use java 21.0.8-graal
(If gu not present but native-image symlink exists, you can still build. Otherwise download tar.gz release and extract to /opt.)
Verify:
java -version
native-image --version
Build native (Linux):
cd /path/to/backend
./gradlew clean nativeCompile --stacktrace
Binary: build/native/nativeCompile/espaco-geek
Run: ./build/native/nativeCompile/espaco-geek
WSL Performance Tip: For faster native builds, move project inside Linux filesystem (~/EspacoGeek) instead of /mnt/<drive>.
Create DB:
CREATE DATABASE espacogeek CHARACTER SET utf8mb4;
CREATE USER 'geek'@'localhost' IDENTIFIED BY 'strongpass';
GRANT ALL ON espacogeek.* TO 'geek'@'localhost';
FLUSH PRIVILEGES;
Environment variables (Linux example):
export SPRING_DATASOURCE_URL=jdbc:mysql://localhost:3306/espacogeek
export SPRING_DATASOURCE_USERNAME=geek
export SPRING_DATASOURCE_PASSWORD=strongpass
Run JVM:
./gradlew bootRun
Run native:
SPRING_DATASOURCE_URL=jdbc:mysql://localhost:3306/espacogeek \
SPRING_DATASOURCE_USERNAME=geek \
SPRING_DATASOURCE_PASSWORD=strongpass \
./build/native/nativeCompile/espaco-geek
./gradlew clean
./gradlew build
./gradlew bootRun
./gradlew test
./gradlew nativeCompile
| Issue | Cause | Fix |
|---|---|---|
'other' has different root |
Project & GraalVM on different Windows drives | Place both on same drive |
Failed to find 'vcvarsall.bat' |
MSVC toolchain missing | Install VS 2022 C++ workload |
cl not found |
Environment not initialized | Use Native Tools Command Prompt / run vcvarsall.bat |
native-image not found |
PATH or install incomplete | Ensure GraalVM bin in PATH / reinstall |
| Slow WSL build | Project on /mnt | Move to ~/ inside WSL |
| Experimental option warnings | -H:Name etc. |
Remove those flags or add -H:+UnlockExperimentalVMOptions |
./gradlew clean
rm -rf build/native
Supports three modes: JVM, JVM Debug, Native (GraalVM). Uses docker-compose to orchestrate optional MySQL.
Directory: docker/
Dockerfile.jvm: Multi-stage build producing a JVM runtime image.Dockerfile.native: Multi-stage native-image build (Linux binary).docker-compose.yml: Services: app-jvm, app-jvm-debug, app-native, db.
Prerequisites:
- Docker Engine + Compose Plugin (Docker Desktop or distro packages)
- (Native image build) Sufficient RAM (≥4‑6GB) & CPU time.
Build & Run (all services):
cd docker
docker compose up --build
This will start:
db(MySQL)app-jvmon port 8080app-jvm-debugon port 8081 (debug port 5006)app-nativeon port 8082
Run only a specific service (example native):
cd docker
docker compose build app-native
docker compose up app-native db
JVM Debug attach example (IDE):
- Host: localhost
- Port: 5006
- Mode: Attach (Socket)
Environment overrides:
SPRING_DATASOURCE_URL=jdbc:mysql://db:3306/espacogeek
SPRING_DATASOURCE_USERNAME=geek
SPRING_DATASOURCE_PASSWORD=strongpass
Adjust in docker-compose.yml or via --env flags.
Rebuild after code changes (JVM):
docker compose build app-jvm && docker compose up -d app-jvm
Faster iterative JVM dev (mount source) – optional example (not enabled by default): Add in service:
volumes:
- ../:/workspace
Then run a dev command override.
Cleaning images/containers:
docker compose down
# Remove dangling images
docker image prune -f
Native image notes:
- Native build happens in container; local GraalVM install not required.
- Result binary stays inside the image (distroless run stage).
- For lower image size you could add strip flags (
-H:StripDebugInfo).
Production suggestions:
- Use a separate network/database.
- Externalize secrets via Docker secrets or env file (do not commit
.envwith real credentials).
- JVM:
./gradlew bootRun - Native (host):
./gradlew nativeCompile && ./build/native/nativeCompile/espaco-geek - Configure DB via environment variables as shown earlier.
Short path: JVM dev = ./gradlew bootRun | Docker all = cd docker && docker compose up --build | Native docker = docker compose build app-native.
Main variables consumed by the backend:
# App
APP_NAME=EspacoGeek
FRONTEND_URL=http://localhost:3000
# Database
SPRING_DATASOURCE_URL=jdbc:mysql://localhost:3306/espacogeekdb
SPRING_DATASOURCE_USERNAME=root
SPRING_DATASOURCE_PASSWORD=root
SPRING_AUTO_DDL=update
# CORS
SPRING_MVC_CORS_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173
# JWT / Auth
SECURITY_JWT_ISSUER=espacogeek
SECURITY_JWT_EXPIRATION_MS=604800000
JWT_EXPIRATION_MS=604800000
SECURITY_JWT_SECRET=your_jwt_secret_key_here
SAMESITE_WHEN_SAME_SITE=Lax
# CSRF cookie
SECURITY_CSRF_COOKIE_DOMAIN=
SECURITY_CSRF_COOKIE_SAME_SITE=
# Optional/legacy JWT cookie-domain mapping (line is commented in application.properties)
# SPRING_COOKIE_DOMAIN=.espacogeek.com
# Email
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_USERNAME=your-email@gmail.com
MAIL_PASSWORD=your-app-passwordNotes:
SECURITY_CSRF_COOKIE_DOMAIN: in production, use.espacogeek.comto share CSRF cookie between subdomains.SECURITY_CSRF_COOKIE_SAME_SITE: useNonefor cross-site frontend/backend, orLaxfor same-site flows.JWT_EXPIRATION_MSandSECURITY_JWT_EXPIRATION_MSare kept for compatibility with current configuration and environment binding.