DEV Community

Cover image for How I Built a Reactive Fullstack Monolith with Spring Boot 3.x / 4.x and PulsePoint (Without Node.js or React)
Mahendra  S H
Mahendra S H

Posted on

How I Built a Reactive Fullstack Monolith with Spring Boot 3.x / 4.x and PulsePoint (Without Node.js or React)

How I Built a Reactive Fullstack Monolith with Spring Boot 3.x /4.x and PulsePoint (Without Node.js or React)

Introduction

As Java developers, whenever we need to build a modern, interactive web application, we usually face a painful dilemma:

  1. The SPA route: Spin up a separate React/Vue/Next.js frontend with npm, Vite, and duplicate DTO types across TypeScript and Java.
  2. The traditional MPA route: Use Thymeleaf or JSP, sacrificing single-page reactivity for full-page reloads.

Recently, I evaluated PulsePoint v2—a lightweight reactive runtime designed to bridge this exact gap. The goal was to prove whether a Java developer can build a single-page reactive application where Java remains the only backend and build toolchain.

Here is how I built it, the architecture behind it, and what I learned along the way.


The Architecture

Instead of maintaining two separate servers (e.g. Node on port 3000 and Spring Boot on port 8080), the entire application runs as a single monolithic JAR:

Browser (PulsePoint v2 runtime)
   │
   ├─► RPC (POST + X-PP-RPC: true, X-PP-Function, X-CSRF-Token)
   ├─► SSE Streaming (POST + Accept: text/event-stream)
   └─► WebSocket (ws://host/__pulsepoint/ws?name=tasks)
   ▼
Spring Boot Monolith
   │
   ├─► PulsePointCsrfFilter (Cookie / Header bridge)
   ├─► Spring Security (Session auth + CSRF validation)
   ├─► PulsePointRpcFilter (Dispatches RPC & streams SSE chunks)
   │     ├─► TaskService (Business Layer & JPA) ──► PostgreSQL
   │     └─► TaskBroadcaster (Real-time WebSocket events)
   └─► Thymeleaf (Initial HTML Host)
Enter fullscreen mode Exit fullscreen mode

How It Works

1. Zero Build Step Frontend

You download a single static file (pp-reactive-v2.min.js) into src/main/resources/static/js/. Inside your Thymeleaf HTML template, you simply declare:

<script type="module">
    import { ComponentInit as PP } from "/js/pp-reactive-v2.min.js";
    PP.bootstrap();
</script>
Enter fullscreen mode Exit fullscreen mode

No npm install, no package.json, no bundler configuration.

2. Reactive UI with React Hook Semantics

Components are defined directly in HTML templates with clean, intuitive state hooks:

<template pp-component="task_manager">
    <div>
        <h3>Tasks ({tasks.length})</h3>
        <ul>
            <template pp-for="task in tasks">
                <li key="{task.id}">{task.title}</li>
            </template>
        </ul>
        <script>
            const [tasks, setTasks] = pp.state([]);
            pp.effect(() => {
                pp.rpc("listTasks").then(setTasks);
            }, []);
        </script>
    </div>
</template>
Enter fullscreen mode Exit fullscreen mode

3. The Java RPC Bridge

PulsePoint issues HTTP POST requests with custom headers (X-PP-RPC: true, X-PP-Function: <functionName>). In Spring Boot, a reusable PulsePointRpcFilter intercepts these requests, looks up the registered Java service method, validates Jakarta Bean annotations (@NotBlank, @Size), and returns clean JSON responses.


Key Challenges & How I Solved Them

  1. CSRF Token Bridge:

    Spring Security standardizes on XSRF-TOKEN cookies and X-XSRF-TOKEN headers. PulsePoint looks for pp_csrf and sends X-CSRF-Token.

    Solution: Configured Spring Security's CookieCsrfTokenRepository with setCookieName("pp_csrf") and setHeaderName("X-CSRF-Token") to make them interoperate seamlessly.

  2. Non-JSON Error Responses:

    Standard Spring Boot error pages emit HTML. If a 401 or 403 occurs, the PulsePoint client parser expects JSON and throws a SyntaxError.

    Solution: Configured JSON-aware Spring Security entry points specifically for requests bearing X-PP-RPC: true.

  3. Event Target Recycling:

    In modern browsers, accessing event.currentTarget.reset() after await pp.rpc(...) can fail because native event targets are cleared asynchronously.

    Solution: Always cache const form = event.currentTarget; before any await statement.


Verdict: Is It Worth It?

  • Lightweight & Fast: First-paint is instant because HTML is rendered on the server.
  • Single Source of Truth: Validation rules and permissions live in Java DTOs and Spring Security.
  • Great for React Devs & Java Devs: React developers already know pp.state and pp.effect, while Java devs never have to leave Maven or their IDE.

If you are building dashboards, internal admin tools, or SaaS portals and want single-page reactivity without the overhead of a JavaScript build toolchain, Spring Boot + PulsePoint is a game changer.

Top comments (1)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.