Full-Stack Java in 2026: Building Web Apps Without JavaScript

Actually, I should clarify — I looked at my node_modules folder the other day, and it was heavier than the rest of my operating system. Okay, well, that’s not entirely accurate, but you know the feeling. You start a “simple” web project, and suddenly you’re configuring Webpack, fighting with TypeScript definitions, and trying to figure out why your frontend build takes three minutes.

I’m a Java developer. I like strong typing. I like the JVM. And honestly? I’m probably tired of context-switching between my backend logic and a fragile JavaScript frontend ecosystem that changes every six weeks.

So, last Tuesday, I decided to try something different. No React. No Angular. Just pure Java, running on the server, rendering the UI. I built a component-driven web app using Vaadin Flow running on Quarkus.

The result? I shipped a functional dashboard in an afternoon, and the performance on my M3 Pro was ridiculous. Startup time clocked in at 0.65 seconds. And we’ll cover some core Java concepts — Classes, Interfaces, and Streams — along the way.

Why Pure Java for Web?

The premise is simple: you write Java code that defines your UI layout and logic. The framework (Vaadin) handles the communication with the browser automatically. It sends JSON back and forth, but you never touch it. You just modify the state of a Java object, and the browser updates. It feels like magic, but it’s really just a very smart abstraction.

And when you pair this with Quarkus (the “Supersonic Subatomic Java” framework), you get hot-reload that actually works. I changed a button color in the Java class, hit save, and the browser updated instantly. No recompiling the whole world.

software developer computer - Systems Analyst vs. Software Developer: Complementary Technical ...
software developer computer – Systems Analyst vs. Software Developer: Complementary Technical …

The Setup

For this tutorial, I’m using Java 25 (though 21 works fine) and Quarkus 3.19.1. If you’re setting this up, just grab the Vaadin extension for Quarkus from code.quarkus.io.

1. The Data Model (Java Records)

Let’s build a simple “Server Monitor” dashboard. First, we need data. Since Java 16, we’ve had Records. They are perfect for this because they are immutable data carriers. No more boilerplate getters, setters, or toString() methods.

package com.example.monitor;

// A concise way to define a data class
public record ServerStatus(
    String id, 
    String name, 
    int loadPercentage, 
    boolean isOnline
) {}

2. The Service Layer (Interfaces & Streams)

We need a way to fetch this data. Good architecture usually involves an Interface to define the contract, and a concrete implementation. This lets us swap out the real database for a dummy one during testing (which is exactly what I did here because I didn’t want to spin up Docker just for a demo).

package com.example.monitor;

import java.util.List;

public interface ServerService {
    List<ServerStatus> findAll();
    List<ServerStatus> findCritical();
}

Now, the implementation. Here is where we use the Stream API. Streams are powerful for processing collections of data in a functional style. Instead of writing for loops with if statements inside, we pipeline operations.

package com.example.monitor;

import jakarta.enterprise.context.ApplicationScoped;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;

@ApplicationScoped
public class ServerServiceImpl implements ServerService {
    
    private final List<ServerStatus> servers = new ArrayList<>();

    public ServerServiceImpl() {
        // Simulating some data
        var random = new Random();
        for (int i = 1; i <= 20; i++) {
            servers.add(new ServerStatus(
                "srv-" + i, 
                "Node " + i, 
                random.nextInt(100), 
                random.nextBoolean()
            ));
        }
    }

    @Override
    public List<ServerStatus> findAll() {
        return servers;
    }

    @Override
    public List<ServerStatus> findCritical() {
        // Using Java Streams to filter data
        return servers.stream()
            .filter(ServerStatus::isOnline) 
            .filter(s -> s.loadPercentage() > 80)
            .collect(Collectors.toList());
    }
}

3. The UI (The View Class)

software developer computer - Software Developer vs. Engineer vs. Programmer | University of Phoenix
software developer computer – Software Developer vs. Engineer vs. Programmer | University of Phoenix

Here is the fun part. We aren’t writing HTML. We aren’t writing CSS (unless we want to). We are writing a Java Class that extends a UI component.

In Vaadin, a “Route” is a view mapped to a URL.

package com.example.monitor;

import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.html.H1;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.router.Route;
import jakarta.inject.Inject;

@Route("") // Maps to root URL
public class DashboardView extends VerticalLayout {

    private final ServerService serverService;
    private final Grid<ServerStatus> grid = new Grid<>(ServerStatus.class);

    @Inject
    public DashboardView(ServerService serverService) {
        this.serverService = serverService;

        // Layout configuration
        setSizeFull();
        setAlignItems(Alignment.CENTER);

        H1 title = new H1("Server Monitor 2026");
        
        configureGrid();
        
        Button refreshBtn = new Button("Refresh Data");
        refreshBtn.addClickListener(e -> updateList());

        add(title, refreshBtn, grid);
        
        updateList();
    }

    private void configureGrid() {
        grid.setColumns("name", "loadPercentage");
        
        // Adding a custom column with a component renderer
        grid.addComponentColumn(server -> {
            String statusColor = server.isOnline() ? "green" : "red";
            Button statusBadge = new Button(server.isOnline() ? "Online" : "Offline");
            statusBadge.getStyle().set("color", statusColor);
            return statusBadge;
        }).setHeader("Status");
    }

    private void updateList() {
        // Fetch data and bind it to the grid
        grid.setItems(serverService.findAll());
    }
}

How It Feels to Code This

There is a specific kind of relief in typing server. inside your UI code and seeing your IDE autocomplete the methods from your backend entity. You don’t have to wonder if the JSON property was “load_percentage” or “loadPercentage”. It’s just Java.

When I ran this, I hit a snag initially — I forgot to annotate the service with @ApplicationScoped, which threw a nasty CDI error during build. But once fixed, the startup was instant. The grid renders lazily, meaning if I had 10,000 rows, it wouldn’t crash the browser; it only sends what’s visible.

Your email address will not be published. Required fields are marked *