Crafting High-Performance Full-Stack Applications: Quarkus Native and Angular

Written by Issam on November 5, 2025 โ€ข 13 min read

appfrontendbackend

Quick summary: How I built a production-grade full-stack app with ultra-fast startup and superior memory efficiency (20.72MiB backend + 2.625MiB frontend = ~23MiB total at startup) using Quarkus Native Image - 58% better than Spring Boot Native!


๐Ÿš€ The Problem: Slow Java Application Startup

Traditional Java applications are notorious for their slow startup times. A typical Java application can take 3-5 seconds to start, which becomes a significant bottleneck in:

๐Ÿ’ก The Solution: Quarkus + GraalVM Native Image

Enter Quarkus - the Supersonic Subatomic Java framework designed for cloud-native applications, combined with GraalVM Native Image - a technology that compiles Java applications ahead-of-time into native executables. The results are astonishing:

๐Ÿ—๏ธ Project Architecture

I built a complete user management system with the following stack:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚   Angular 20+   โ”‚    โ”‚    Quarkus 3    โ”‚    โ”‚    MariaDB      โ”‚
โ”‚   Frontend      โ”‚โ—„โ”€โ”€โ–บโ”‚   Native Image  โ”‚โ—„โ”€โ”€โ–บโ”‚    Database     โ”‚
โ”‚   (Port 4200)   โ”‚    โ”‚   (Port 8080)   โ”‚    โ”‚   (Port 3306)   โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Technology Stack:

๐Ÿ”ง Implementation Deep Dive

1. Backend Setup with Native Image Support

Quarkus makes native compilation incredibly straightforward. I configured the Maven project with native support:

<properties>
  <quarkus.platform.version>3.15.1</quarkus.platform.version>
  <maven.compiler.release>21</maven.compiler.release>
</properties>

<profiles>
  <profile>
    <id>native</id>
    <properties>
      <quarkus.native.enabled>true</quarkus.native.enabled>
      <quarkus.native.container-build>true</quarkus.native.container-build>
      <quarkus.container-image.build>true</quarkus.container-image.build>
    </properties>
  </profile>
</profiles>

2. User Entity with Panache

Quarkus Panache makes data access incredibly simple. No need for repositories - the entity itself provides all CRUD operations:

@Entity
public class User extends PanacheEntity {

    @Column(nullable = false)
    public String name;

    @Column(nullable = false, unique = true)
    public String email;
}

Thatโ€™s it! No getters, setters, or repository interface needed. Panache provides all the methods like User.listAll(), User.findById(), user.persist(), etc.

3. REST API Resource (JAX-RS)

The REST API uses JAX-RS with Quarkusโ€™s RESTEasy Reactive for optimal performance:

@Path("/api/users")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class UserResource {

    @GET
    public List<User> all() {
        return User.listAll();
    }

    @GET
    @Path("/{id}")
    public User byId(@PathParam("id") Long id) {
        User u = User.findById(id);
        if (u == null) throw new NotFoundException();
        return u;
    }

    @POST
    @Transactional
    public User create(User dto) {
        if (User.find("email", dto.email).firstResult() != null) {
            throw new BadRequestException("User with email already exists");
        }
        dto.persist();
        return dto;
    }

    @PUT
    @Path("/{id}")
    @Transactional
    public User update(@PathParam("id") Long id, User dto) {
        User u = User.findById(id);
        if (u == null) throw new NotFoundException();
        u.name = dto.name;
        u.email = dto.email;
        return u;
    }

    @DELETE
    @Path("/{id}")
    @Transactional
    public void delete(@PathParam("id") Long id) {
        if (!User.deleteById(id)) throw new NotFoundException();
    }
}

4. Angular Frontend with Modern Architecture

I used Angular 20โ€™s standalone components for a modern, lightweight approach:

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [UserListComponent],
  templateUrl: './app.html',
  styleUrl: './app.css'
})
export class App {
  title = 'User Management Application';
}

5. Service Layer for API Communication

@Injectable({
  providedIn: 'root'
})
export class UserService {
  private apiUrl = 'http://localhost:8080/api/users';
  
  constructor(private http: HttpClient) { }
  
  getAllUsers(): Observable<User[]> {
    return this.http.get<User[]>(this.apiUrl);
  }
  
  createUser(user: UserRequest): Observable<User> {
    return this.http.post<User>(this.apiUrl, user);
  }
  
  // Additional CRUD operations...
}

๐Ÿ“Š Performance Results

The performance improvements were dramatic, and when compared to Spring Boot Native, Quarkus shows even better resource efficiency:

Performance Comparison (Native Images):

๐Ÿณ Real Docker Performance Metrics

Here are the actual production Docker stats from our running Quarkus Native full-stack application:

CONTAINER ID   NAME               CPU %     MEM USAGE / LIMIT     MEM %     NET I/O           BLOCK I/O   PIDS
5facbc5751f3   quarkus-app        0.00%     20.72MiB / 64MiB      32.37%    4.79kB / 5.31kB   0B / 0B     13
4ba659835ac3   quarkus-frontend   0.00%     2.625MiB / 7.604GiB   0.03%     746B / 0B         0B / 0B     2
c1beedaca839   quarkus-mariadb    1.51%     131.4MiB / 7.604GiB  1.69%     6.88kB / 4kB      0B / 0B     10

Key Observations - Why Quarkus Excels:

Backend Performance (quarkus-app):

Frontend Performance (nginx):

Full Stack Total:

This real-world data confirms that Quarkus Native Image not only matches Spring Boot Native performance but actually uses 58% less memory at startup and 44% less under load - making it the superior choice for resource-constrained environments and cloud-native deployments.

Memory Growth Analysis:

๐Ÿณ Docker Deployment

I containerized the entire application for easy deployment:

services:
  mariadb:
    image: mariadb:12
    environment:
      MYSQL_DATABASE: userdb
      MYSQL_USER: appuser
      MYSQL_PASSWORD: apppassword
    ports:
      - "3306:3306"
      
  app:
    image: quarkus-native-users:latest
    depends_on:
      mariadb:
        condition: service_healthy
    environment:
      QUARKUS_DATASOURCE_JDBC_URL: jdbc:mariadb://mariadb:3306/userdb
      QUARKUS_DATASOURCE_USERNAME: appuser
      QUARKUS_DATASOURCE_PASSWORD: apppassword
    ports:
      - "8080:8080"

  frontend:
    build:
      context: ./front
      dockerfile: Dockerfile
    ports:
      - "4200:80"
    depends_on:
      - app
    environment:
      - API_URL=http://app:8080

๐Ÿš€ Building and Running

Backend (Native Image)

Quarkus makes building native images straightforward:

# Development mode with hot reload
cd quarkus
./mvnw quarkus:dev

# Build native executable
./mvnw clean package -Pnative -Dquarkus.native.container-build=true

# Build native Docker image directly
./mvnw clean package -Pnative -Dquarkus.native.container-build=true -Dquarkus.container-image.build=true -Dquarkus.container-image.name=quarkus-native-users -Dquarkus.container-image.tag=latest

Run Full Stack with Docker Compose

# First, build the native Docker image
cd quarkus
./mvnw -Pnative -Dquarkus.native.container-build=true -Dquarkus.container-image.build=true -Dquarkus.container-image.name=quarkus-native-users -Dquarkus.container-image.tag=latest clean package

# Then start all services
cd ..
docker-compose up -d

Frontend

cd front
npm install
npm start

๐Ÿ’ก Pro Tip: Use nginx instead of Node.js for production frontend serving

For production deployments, use nginx to serve your Angular build instead of running a Node.js server. Even when using Node.js with Alpine base image and http-server (the smallest Node.js option), nginx is significantly more efficient:

# front/Dockerfile
# Build stage
FROM node:20-alpine AS build

WORKDIR /app

# Install dependencies
COPY package*.json ./
RUN npm ci

# Build application
COPY . .
RUN npm run build

# Production stage
FROM nginx:alpine

# Copy Angular build and nginx config
COPY --from=build /app/dist/user-management/browser /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf

# Handle Angular's CSR (Client-Side Rendering) build output
# Angular generates index.csr.html for CSR builds, but nginx expects index.html
RUN if [ -f /usr/share/nginx/html/index.csr.html ]; then \
    cp /usr/share/nginx/html/index.csr.html /usr/share/nginx/html/index.html; \
    fi

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

Nginx.conf:

# front/nginx.conf
events {
    worker_connections 1024;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    sendfile on;
    gzip on;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml text/javascript;

    server {
        listen 80;
        server_name localhost;
        root /usr/share/nginx/html;
        index index.html;

        # Proxy API requests to backend
        location /api/ {
            proxy_pass http://app:8080;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
        }

        # Angular routing - serve index.html for all routes
        location / {
            try_files $uri $uri/ /index.html;
        }

        # Cache static assets
        location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
            expires 1y;
            add_header Cache-Control "public, immutable";
        }
    }
}

Benefits of nginx (Proven with Real Data):

We built both versions to compare (Node.js uses Alpine base image with http-server for fair comparison):

MetricnginxNode.js (Alpine + http-server)Advantage
Image Size53.2 MB141 MB62% smaller
Memory Usage2.625 MiB15.89 MiB83% less memory
CPU Usage0.00%0.00%Comparable
Process Count2 processes20+ processesMinimal overhead

Key Benefits:

This gives you a fully containerized full-stack application with:

๐Ÿ” Key Learnings and Challenges

1. Native Image Compilation with Quarkus

2. Quarkus-Specific Advantages

3. Docker Optimization Discoveries

4. Performance Insights

5. Best Practices Discovered

๐ŸŽฏ Real-World Applications

This architecture is perfect for:

๐Ÿ”ฎ Future Enhancements

๐Ÿ“š Resources and Code

The complete source code is available on GitHub: https://github.com/issam1991/quarkus-native-angular-sample

Key Dependencies:

๐ŸŽ‰ Conclusion

Building this application taught me that native compilation isnโ€™t just a performance optimizationโ€”itโ€™s a paradigm shift. The combination of Quarkus Native Image and Angular creates a powerful, modern full-stack solution thatโ€™s:

Why Choose Quarkus Over Spring Boot Native?

The real-world Docker stats tell the story clearly:

MetricSpring Boot NativeQuarkus NativeAdvantage
Backend Memory (startup)50 MiB20.72 MiB58% less
Backend Memory (under load)50 MiB+28 MiB44% less
Frontend (nginx)~15-100 MiB (Node.js)2.625 MiB95-97% less
Total App Memory (startup)~70-150 MiB~23 MiB77-85% less
Total App Memory (under load)~100-150 MiB~31 MiB69-79% less
ConfigurationMore complexSimpler (zero-config)Developer friendly
Code SimplificationStandard JPAPanache (Active Record)Less boilerplate

Quarkusโ€™s philosophy of โ€œdeveloper joyโ€ combined with superior resource efficiency makes it the perfect choice for modern Java applications. The framework is specifically designed for cloud-native, containerized environments, and the performance numbers prove it.

The future of Java applications is native, and Quarkus not only makes getting there easierโ€”it makes it more efficient.


๐Ÿ“– Whatโ€™s Next?

If you found this article helpful, consider:

  1. Starring the repository on GitHub
  2. Trying the application yourself
  3. Contributing improvements or features
  4. Sharing with your development team

Happy coding! ๐Ÿš€


Follow me on GitHub and connect with ForTek Advisor for more technical content and project updates.

#Quarkus #Angular #NativeImage #GraalVM #Java #TypeScript #FullStack #WebDevelopment #Performance #Microservices #CloudNative