Technology Stack

The stack prioritizes minimal learning curve, code reusability, and cloud-native principles. {: .fs-6 .fw-300 }

This document describes the recommended technology stack for this React-Java monorepo, designed to provide a natural migration path from traditional Java EE (JSF/CDI) environments to modern, cloud-native development. The stack prioritizes minimal learning curve, code reusability, and cloud-native principles.

Target Audience: Java developers transitioning from JSF/CDI/EJB to modern frameworks.

Table of Contents

  1. Technology Stack
    1. Table of Contents
    2. Full Recommended Stack
      1. Backend
      2. Frontend
      3. DevOps & Deployment
    3. Why This Stack?
      1. For Java EE Developers (Migration Benefits)
        1. 1. Familiar Programming Model
        2. 2. Stateless Thinking (REST over JSF)
        3. 3. Cloud-Native by Default
        4. 4. Modern Frontend Separation
    4. Current Project Alignment
      1. βœ… Already Implemented
      2. πŸ”Ά Partially Implemented (Gaps)
      3. ❌ Not Yet Implemented
    5. Migration Guide: From Java EE to Quarkus
      1. Phase 1: Foundation (Weeks 1-2)
      2. Phase 2: Authentication & Security (Weeks 3-4)
      3. Phase 3: Frontend Migration (Weeks 5-8)
      4. Phase 4: Data Persistence (Weeks 9-10)
      5. Phase 5: Observability & DevOps (Weeks 11+)
    6. Best Practices by Layer
      1. Backend (Quarkus)
        1. Project Structure
        2. Dependency Injection
        3. REST Endpoints
        4. Panache ORM
      2. Frontend (React)
        1. Component Structure
        2. HTTP Client Setup
      3. DevOps
        1. Docker Multi-Stage Build (JVM)
        2. Kubernetes Deployment (Optional)
    7. Common Pitfalls & Solutions
      1. 1. Trying to Keep JSF Session State
      2. 2. Returning Entities Directly from REST Endpoints
      3. 3. Mixing Business Logic in REST Endpoints
      4. 4. Not Validating on Frontend
      5. 5. Hardcoding API URLs
    8. Next Steps: Roadmap
      1. Short-term (Months 1-2)
      2. Medium-term (Months 3-4)
      3. Long-term (Months 5+)
    9. Learning Resources
      1. Quarkus
      2. React & TypeScript
      3. Security
      4. DevOps
    10. FAQ
    11. Conclusion

Backend

Layer Technology Version Purpose
Framework Quarkus 3.8.3+ Cloud-native Java framework with CDI, REST, ORM built-in
Language Java 21 LTS Modern JVM with virtual threads (Project Loom) and pattern matching
REST API RESTEasy Reactive (via Quarkus) Stateless REST endpoints (replaces JSF)
ORM Hibernate ORM + Panache (via Quarkus) Simplified JPA with less boilerplate
Dependency Injection CDI (ArC) (via Quarkus) Same as Java EE CDI β€” @Inject, @ApplicationScoped, etc.
Bean Validation Hibernate Validator (via Quarkus) Same JSR-380 validation as Java EE
API Documentation SmallRye OpenAPI (via Quarkus) Auto-generated Swagger UI and OpenAPI specs
Database PostgreSQL 14+ Production database (H2 for local development)
Migrations Flyway 9.0+ Database versioning and migrations
Security SmallRye JWT / OIDC (via Quarkus) Token-based auth (replaces container-managed security)
Testing JUnit 5 + REST Assured (via Quarkus) Integration and REST API testing
Build Tool Maven 3.8+ Same as Java EE projects

Frontend

Layer Technology Version Purpose
Framework React 19+ Component-based UI (replaces JSF server-side rendering)
Language TypeScript 5.9+ Type-safe JavaScript development
Routing React Router 6+ Client-side routing (replaces JSF navigation rules)
Build Tool Vite 8+ Ultra-fast development server and production bundler
HTTP Client axios / fetch - REST API communication with backend
Styling CSS Modules / Tailwind - Component-scoped or utility-first styling
Testing Vitest + React Testing Library - Unit and component testing
Linting ESLint 9+ Code quality and consistency

DevOps & Deployment

Layer Technology Purpose
Containerization Docker Multi-stage builds for JVM/native binaries
Orchestration Kubernetes (optional) Production deployment and scaling
Container Registry Docker Hub / ECR / Harbor Image storage and distribution
Database Persistence PostgreSQL + Persistent Volumes Stateful database in containers
Monitoring Micrometer / Prometheus (Quarkus built-in) Application metrics and health checks

Why This Stack?

For Java EE Developers (Migration Benefits)

1. Familiar Programming Model

  • CDI remains unchanged: @Inject, @ApplicationScoped, interceptors, events work identically
  • JPA/Hibernate: Same ORM, just simpler with Panache (less boilerplate)
  • Bean Validation: Same @NotNull, @Size, @Email annotations
  • Maven: No build tool learning curve

Translation:

Java EE            β†’    Modern (Quarkus)
Managed Beans      β†’    CDI beans (@ApplicationScoped, @Inject)
EJB                β†’    CDI beans + @ApplicationScoped
JNDI               β†’    Constructor/field injection with @Inject
Session Beans      β†’    CDI application-scoped beans
Entity Beans       β†’    JPA @Entity (identical)

2. Stateless Thinking (REST over JSF)

The main mental shift:

JSF (Stateful)              β†’    REST (Stateless)
JSF Managed Bean            β†’    @Path + @POST/@GET endpoint
JSF Form binding            β†’    JSON request body
JSF Session scope           β†’    JWT token in Authorization header
JSF Navigation rules        β†’    React Router client-side
FacesContext.addMessage()   β†’    HTTP response status + JSON error

3. Cloud-Native by Default

  • Fast startup (milliseconds vs seconds) β€” fits containerized environments
  • Low memory footprint β€” optimized for cloud resource constraints
  • Native compilation (GraalVM) β€” executable without JVM (~50MB vs 200MB+ with JVM)
  • Health checks β€” Quarkus includes /q/health out of the box

4. Modern Frontend Separation

  • React replaces JSF server-side rendering β€” frontend is a separate SPA
  • Clear API boundary β€” easier testing, scaling, and team separation
  • Better UX β€” instant client-side navigation without page reloads
  • Flexible deployment β€” frontend can be CDN-hosted; backend is pure API

Current Project Alignment

βœ… Already Implemented

The project is ~70% aligned with the recommended stack:

Component Status Details
Quarkus 3.8.3 βœ… Recent stable version
RESTEasy Reactive βœ… Endpoints wired up
Hibernate ORM Panache βœ… User domain entity in place
CDI (ArC) βœ… AuthService with @ApplicationScoped
Bean Validation βœ… LoginRequest with @Valid
OpenAPI/Swagger UI βœ… Accessible at /swagger-ui
React 19 + TypeScript βœ… Latest versions
Vite βœ… Modern build pipeline
Docker Compose βœ… Local orchestration working
H2 (development) βœ… Zero-config DB for dev

πŸ”Ά Partially Implemented (Gaps)

Component Current Recommendation Priority
Java version 17 Upgrade to 21 LTS Medium
Database H2 in-memory PostgreSQL prod config High
Migrations drop-and-create Flyway versioning High
Authentication Plain message response JWT tokens High
Frontend routing Single page React Router Medium
Security config None SmallRye JWT extension High

❌ Not Yet Implemented

  • Flyway database migrations
  • Production PostgreSQL configuration profile
  • JWT token generation and validation
  • React Router setup
  • Error boundary and global error handling
  • State management (Zustand/Redux) β€” optional, add as needed
  • Component/unit test suite

Migration Guide: From Java EE to Quarkus

Phase 1: Foundation (Weeks 1-2)

  • Understand REST-first thinking (no more stateful components)
  • Read Quarkus documentation on CDI, JPA, REST
  • Familiarize with Quarkus dev mode: quarkus:dev
  • Practice: Convert one JSF backing bean to a REST endpoint

Phase 2: Authentication & Security (Weeks 3-4)

  • Implement JWT flow (replace form authentication)
    • Backend: Generate JWT on login
    • Frontend: Store token in localStorage, send in Authorization header
    • Backend: Validate token on protected endpoints
  • Remove session/stateful auth assumptions

Phase 3: Frontend Migration (Weeks 5-8)

  • Add React Router for navigation
  • Implement HTTP client (axios/fetch) for API calls
  • Migrate JSF pages to React components
  • Test React components with Vitest/React Testing Library

Phase 4: Data Persistence (Weeks 9-10)

  • Set up PostgreSQL locally (Docker)
  • Create Flyway migration for schema
  • Replace H2 with PostgreSQL in dev environment
  • Test data migrations

Phase 5: Observability & DevOps (Weeks 11+)

  • Add Prometheus metrics
  • Configure health checks
  • Set up centralized logging
  • Prepare Kubernetes manifests

Best Practices by Layer

Backend (Quarkus)

Project Structure

src/main/java/com/example/
β”œβ”€β”€ domain/              # Pure business logic, no framework deps
β”‚   β”œβ”€β”€ User.java
β”‚   └── UserRepository.java (interface)
β”œβ”€β”€ application/         # Use cases, orchestration
β”‚   └── AuthService.java
β”œβ”€β”€ infrastructure/      # Tech-specific implementations
β”‚   β”œβ”€β”€ JpaUserRepository.java
β”‚   └── SecurityConfig.java
└── api/                 # REST endpoints (thin layer)
    β”œβ”€β”€ AuthResource.java
    └── dto/
        β”œβ”€β”€ LoginRequest.java
        └── LoginResponse.java

Dependency Injection

// βœ… Good: Constructor injection (Quarkus)
@ApplicationScoped
public class AuthService {
    private final UserRepository userRepository;

    public AuthService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

// ⚠️ Avoid: Field injection (harder to test)
@ApplicationScoped
public class AuthService {
    @Inject
    UserRepository userRepository;
}

REST Endpoints


@Path("/api/auth")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class AuthResource {

    @Inject
    AuthService authService;

    @POST
    @Path("/login")
    public Response login(@Valid LoginRequest request) {
        LoginResult result = authService.login(request.username(), request.password());
        if (result.isSuccess()) {
            // Return JWT token, not just a message
            String token = generateJWT(result.userId());
            return Response.ok(new LoginResponse(token))
                    .build();
        }
        return Response.status(Response.Status.UNAUTHORIZED)
                .entity(new LoginResponse("Invalid credentials"))
                .build();
    }
}

Panache ORM

// βœ… Good: Use Panache repository methods
User user = User.find("username", username)
                .firstResult();

// Instead of traditional JPA boilerplate
User user = em.createQuery("SELECT u FROM User u WHERE u.username = :username", User.class)
        .setParameter("username", username)
        .getSingleResult();

Frontend (React)

Component Structure

// Keep components small and focused
export function LoginPage() {
  const [username, setUsername] = useState('');
  const [error, setError] = useState('');
  const navigate = useNavigate();

  async function handleLogin(e: FormEvent) {
    e.preventDefault();
    try {
      const response = await fetch('/api/auth/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ username, password }),
      });
      if (response.ok) {
        const { token } = await response.json();
        localStorage.setItem('authToken', token);
        navigate('/dashboard');
      } else {
        setError('Login failed');
      }
    } catch (err) {
      setError('Network error');
    }
  }

  return (
    <form onSubmit={handleLogin}>
      {/* form JSX */}
    </form>
  );
}

HTTP Client Setup

// src/api/client.ts
export const apiClient = {
  async post<T>(path: string, data: unknown): Promise<T> {
    const response = await fetch(`/api${path}`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${localStorage.getItem('authToken')}`,
      },
      body: JSON.stringify(data),
    });
    if (!response.ok) throw new Error(`API error: ${response.status}`);
    return response.json();
  },
};

// Usage in components
const { token } = await apiClient.post<LoginResponse>('/auth/login', { username, password });

DevOps

Docker Multi-Stage Build (JVM)

# Build stage
FROM maven:3.9-eclipse-temurin-21 AS builder
WORKDIR /build
COPY . .
RUN mvn clean package -DskipTests

# Runtime stage
FROM eclipse-temurin:21-jre-alpine
COPY --from=builder /build/target/*-runner.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

Kubernetes Deployment (Optional)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend
spec:
  replicas: 3
  selector:
    matchLabels:
      app: backend
  template:
    metadata:
      labels:
        app: backend
    spec:
      containers:
        - name: backend
          image: myregistry/backend:latest
          ports:
            - containerPort: 8080
          livenessProbe:
            httpGet:
              path: /q/health/live
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 10

Common Pitfalls & Solutions

1. Trying to Keep JSF Session State

Problem: Expecting @SessionScoped or FacesContext to work.
Solution: Embrace statelessness. Use JWT tokens and store state on the client.

2. Returning Entities Directly from REST Endpoints

Problem: Circular references, N+1 queries, exposing internal structure.
Solution: Use DTOs (Data Transfer Objects) and Panache @Query with fetch=FetchType.EAGER.

// ❌ Bad
@POST
public User createUser(User user) { ...}

// βœ… Good
@POST
public Response createUser(CreateUserRequest request) {
    User user = new User(request.username(), request.email());
    user.persist();
    return Response.created(URI.create("/users/" + user.id))
            .entity(new UserResponse(user.id(), user.username()))
            .build();
}

3. Mixing Business Logic in REST Endpoints

Problem: Controllers become god objects.
Solution: Keep the REST layer thin; push logic to services (application layer).

4. Not Validating on Frontend

Problem: Poor UX, assuming backend validation catches everything.
Solution: Validate client-side with React forms + server-side with Bean Validation.

5. Hardcoding API URLs

Problem: Different URLs for dev/prod break deployments.
Solution: Use environment variables or relative URLs.

// βœ… Good: Use relative URLs
const API_BASE = '/api';

// βœ… Better: Use env variables
const API_BASE = import.meta.env.VITE_API_URL || '/api';

Next Steps: Roadmap

Short-term (Months 1-2)

  • Upgrade Java to 21 LTS
  • Implement JWT authentication in AuthService
  • Add SmallRye JWT extension to pom.xml
  • Create Flyway migration for initial schema
  • Add PostgreSQL configuration profile
  • Install React Router and set up routing
  • Connect LoginPage to backend API

Medium-term (Months 3-4)

  • Add role-based access control (RBAC) with JWT claims
  • Create additional REST endpoints (user management, profile, etc.)
  • Build React pages for new endpoints
  • Add integration tests (JUnit 5 + REST Assured)
  • Set up centralized error handling (backend + frontend)
  • Add Prometheus metrics and health checks

Long-term (Months 5+)

  • Kubernetes deployment manifests
  • CI/CD pipeline (GitHub Actions / GitLab CI)
  • OpenTelemetry tracing
  • Rate limiting and API gateway
  • Advanced security (mTLS, OAuth2 flows)
  • Performance optimization (caching, indexing)

Learning Resources

Quarkus

React & TypeScript

Security

DevOps


FAQ

Q: Can I use Spring Boot instead of Quarkus?
A: Yes, but you lose the cloud-native advantages (fast startup, memory efficiency, native compilation). For a Java EE background, Quarkus is more natural.

Q: Do I need to learn reactive programming (Mutiny)?
A: No, not initially. Quarkus works fine with traditional imperative style. Reactive becomes valuable for high-concurrency APIs.

Q: When should I use PostgreSQL instead of H2?
A: Immediately in any environment beyond local dev (staging, production). H2 is for development only.

Q: How do I handle CORS in production?
A: Configure it in application.properties with specific origins, not *. Use environment variables for different environments.

Q: Can I deploy both backend and frontend together?
A: Yes, serve the React build as static files from Quarkus. But separate deployments (frontend on CDN, backend on Kubernetes) is more scalable.


Conclusion

This stack provides a natural evolution from Java EE to modern, cloud-native development. The core Java concepts ( CDI, JPA, Bean Validation) remain unchanged, but you gain:

  • ⚑ Performance: Fast startup, low memory
  • ☁️ Cloud-ready: Docker + Kubernetes native
  • πŸ§ͺ Testability: Stateless APIs, no session complexity
  • πŸš€ Developer experience: Hot reload, instant feedback
  • πŸ“ˆ Scalability: Horizontal scaling without session affinity

Start with the fundamentals (CDI, REST, Panache), master JWT authentication, then scale to reactive patterns and distributed tracing as your needs grow.