Building a PC System Dashboard with Java, Spring Boot, and OSHI
- Sunfyre
- Aug 29
- 4 min read
Updated: Aug 31
A full-stack approach to monitoring your PC’s system information using modern Java tooling.
Introduction
System dashboards are incredibly useful for monitoring the health, performance, and specifications of your machine. Whether you're a devops engineer, backend developer, or just a curious enthusiast, having a self-built dashboard provides transparency and learning opportunities.
In this blog, we’ll walk through how to create a PC System Dashboard using:
Java 17+
Spring Boot
oshi-core (Open Source Hardware Info)
Thymeleaf
What is OSHI (oshi-core) ?
OSHI (Operating System and Hardware Information) is a Java library that provides cross-platform access to system data such as:
CPU usage
RAM stats
Disk info
Motherboard details
OS metadata
Boot time and uptime
It’s a dependency-light, no-native-library-required solution, perfect for Java apps that want system introspection.
Let's start the project setup
1. Initialize Spring Boot Project
Use Spring Initializr (https://start.spring.io/) or your IDE to generate a project with:
Spring Web
Spring Boot DevTools
Thymeleaf

2. Add OSHI Dependency
<dependency>
<groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId>
<version>6.8.3</version>
</dependency>
Creating the System Info Service
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.List;
import org.springframework.stereotype.Service;
import oshi.SystemInfo;
import oshi.hardware.CentralProcessor;
import oshi.hardware.GlobalMemory;
import oshi.hardware.HardwareAbstractionLayer;
import oshi.software.os.OSFileStore;
import oshi.software.os.OperatingSystem;
@Service
public class SystemInfoService {
private final SystemInfo systemInfo;
private final HardwareAbstractionLayer hal;
private final OperatingSystem os;
public SystemInfoService() {
this.systemInfo = new SystemInfo();
this.hal = systemInfo.getHardware();
this.os = systemInfo.getOperatingSystem();
}
public String getHostname() {
return os.getNetworkParams().getHostName();
}
public String getManufacturer() {
return hal.getComputerSystem().getManufacturer();
}
public String getModel() {
return hal.getComputerSystem().getModel();
}
public String getBootTime() {
long bootEpoch = os.getSystemBootTime();
LocalDateTime bootTime = LocalDateTime.ofInstant(Instant.ofEpochSecond(bootEpoch), ZoneId.systemDefault());
return bootTime.toString();
}
public String getUptime() {
long seconds = os.getSystemUptime();
Duration duration = Duration.ofSeconds(seconds);
long days = duration.toDays();
long hours = duration.minusDays(days).toHours();
long minutes = duration.minusDays(days).minusHours(hours).toMinutes();
return String.format("%d days, %d hours, %d minutes", days, hours, minutes);
}
public String getCpuInfo() {
CentralProcessor processor = systemInfo.getHardware().getProcessor();
return processor.getProcessorIdentifier().getName();
}
public long getTotalMemory() {
GlobalMemory memory = systemInfo.getHardware().getMemory();
return memory.getTotal() / (1024 * 1024 * 1024); // in GB
}
public long getAvailableMemory() {
GlobalMemory memory = systemInfo.getHardware().getMemory();
return memory.getAvailable() / (1024 * 1024 * 1024); // in GB
}
public List<OSFileStore> getDiskInfo() {
return systemInfo.getOperatingSystem().getFileSystem().getFileStores();
}
public String getOsInfo() {
OperatingSystem os = systemInfo.getOperatingSystem();
return os.toString();
}
}
Create a Controller
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import com.jonathan.assetmanagementsystem.service.SystemInfoService;
@Controller
public class DashboardController {
@Autowired
private SystemInfoService systemInfoService;
@GetMapping("/")
public String showDashboard(Model model) {
model.addAttribute("hostname", systemInfoService.getHostname());
model.addAttribute("os", systemInfoService.getOsInfo());
model.addAttribute("bootTime", systemInfoService.getBootTime());
model.addAttribute("uptime", systemInfoService.getUptime());
model.addAttribute("manufacturer", systemInfoService.getManufacturer());
model.addAttribute("model", systemInfoService.getModel());
model.addAttribute("cpu", systemInfoService.getCpuInfo());
model.addAttribute("totalMemory", systemInfoService.getTotalMemory());
model.addAttribute("availableMemory", systemInfoService.getAvailableMemory());
model.addAttribute("disks", systemInfoService.getDiskInfo());
return "dashboard_v2";
}
}
Create an UI with Thymleaf
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>System Dashboard</title>
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<style>
body {
background-color: #2c3e50; /* Darker background */
color: #ecf0f1; /* Light text for contrast */
font-family: 'Poppins', sans-serif; /* Apply Google Font */
}
.container {
padding-top: 50px;
padding-bottom: 50px;
}
h1, h4, h5 {
color: #ecf0f1; /* Ensure headings are also light */
}
.card {
background-color: #34495e; /* Slightly lighter dark for cards */
border: none;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); /* Subtle shadow for depth */
border-radius: 10px; /* Slightly rounded corners */
}
.card-header {
background-color: #1abc9c !important; /* A vibrant accent color */
color: white !important;
border-bottom: none;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
font-weight: 600;
}
.card-title {
color: #ecf0f1; /* Card titles light */
font-weight: 500;
}
dt {
color: #bdc3c7; /* Muted color for labels */
font-weight: 400;
}
dd {
color: #ecf0f1; /* Data values light */
}
.table {
color: #ecf0f1; /* Table text light */
}
.table thead th {
border-bottom: 2px solid #1abc9c; /* Accent color for table header border */
color: #1abc9c; /* Accent color for table headers */
font-weight: 600;
}
.table tbody tr {
transition: background-color 0.3s ease; /* Smooth hover effect */
}
.table tbody tr:hover {
background-color: #4a627d; /* Lighter background on hover */
}
.table-bordered th, .table-bordered td {
border-color: #4a627d; /* Darker border for table cells */
}
.progress {
height: 25px; /* Make progress bars a bit taller */
background-color: #4a627d; /* Darker background for the empty part of progress bar */
}
.progress-bar {
background-color: #1abc9c; /* Vibrant color for filled part */
color: #ecf0f1;
font-weight: 500;
line-height: 25px; /* Vertically center text in progress bar */
}
.metric-value {
font-size: 1.25rem; /* Larger font for key metrics */
font-weight: 600;
color: #1abc9c; /* Accent color for key numbers */
}
</style>
</head>
<body>
<div class="container mt-4">
<h1 class="mb-4 text-center">PC System Dashboard</h1>
<div class="card mb-4">
<div class="card-header">
<i class="fas fa-info-circle me-2"></i>
<h4 class="mb-0 d-inline-block">System Overview</h4>
</div>
<div class="card-body">
<dl class="row">
<dt class="col-sm-3"><i class="fas fa-desktop me-2"></i>Hostname</dt>
<dd class="col-sm-9" th:text="${hostname}"></dd>
<dt class="col-sm-3"><i class="fas fa-laptop-code me-2"></i>Operating System</dt>
<dd class="col-sm-9" th:text="${os}"></dd>
<dt class="col-sm-3"><i class="fas fa-building me-2"></i>Manufacturer</dt>
<dd class="col-sm-9" th:text="${manufacturer}"></dd>
<dt class="col-sm-3"><i class="fas fa-microchip me-2"></i>Model</dt>
<dd class="col-sm-9" th:text="${model}"></dd>
<dt class="col-sm-3"><i class="fas fa-microchip me-2"></i>CPU</dt>
<dd class="col-sm-9" th:text="${cpu}"></dd>
<dt class="col-sm-3"><i class="fas fa-clock me-2"></i>Boot Time</dt>
<dd class="col-sm-9" th:text="${bootTime}"></dd>
<dt class="col-sm-3"><i class="fas fa-hourglass-start me-2"></i>Uptime</dt>
<dd class="col-sm-9" th:text="${uptime}"></dd>
</dl>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<div class="card h-100">
<div class="card-body d-flex flex-column justify-content-between">
<div>
<h5 class="card-title"><i class="fas fa-memory me-2"></i>Memory Usage</h5>
<p class="card-text">
Total: <span class="metric-value" th:text="${totalMemory + ' GB'}"></span><br>
Available: <span class="metric-value" th:text="${availableMemory + ' GB'}"></span>
</p>
</div>
<div th:with="usedMemory=${totalMemory - availableMemory},
memoryPercentage=${T(java.lang.Math).round((usedMemory * 100.0) / totalMemory)}">
<div class="progress mt-3">
<div class="progress-bar" role="progressbar"
th:style="'width: ' + ${memoryPercentage} + '%'"
th:aria-valuenow="${memoryPercentage}"
aria-valuemin="0" aria-valuemax="100"
th:text="${memoryPercentage} + '% Used'">
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card h-100">
<div class="card-body d-flex flex-column justify-content-between">
<h5 class="card-title"><i class="fas fa-sd-card me-2"></i>Disk Usage Summary</h5>
<p class="card-text">
Total Disks: <span class="metric-value" th:text="${#lists.size(disks)}"></span>
</p>
<div class="alert alert-info bg-transparent border-0 p-0" style="color:#bdc3c7;">
<small>See detailed disk information below.</small>
</div>
</div>
</div>
</div>
</div>
<div class="card mb-4">
<div class="card-header">
<i class="fas fa-hdd me-2"></i>
<h4 class="mb-0 d-inline-block">Disks</h4>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>Mount</th>
<th>Type</th>
<th>Total (GB)</th>
<th>Usable (GB)</th>
<th>Used %</th> <!-- Added for percentage -->
</tr>
</thead>
<tbody>
<tr th:each="disk : ${disks}">
<td th:text="${disk.mount}"></td>
<td th:text="${disk.type}"></td>
<td th:with="totalGB=${disk.getTotalSpace() / 1073741824.0}"
th:text="${#numbers.formatDecimal(totalGB, 1, 2)}"></td>
<td th:with="usableGB=${disk.getUsableSpace() / 1073741824.0}"
th:text="${#numbers.formatDecimal(usableGB, 1, 2)}"></td>
<td th:with="totalBytes=${disk.getTotalSpace()},
usableBytes=${disk.getUsableSpace()},
usedBytes=${totalBytes - usableBytes},
diskPercentage=${T(java.lang.Math).round((usedBytes * 100.0) / totalBytes)}">
<div class="progress" style="min-width: 100px;">
<div class="progress-bar" role="progressbar"
th:style="'width: ' + ${diskPercentage} + '%'"
th:aria-valuenow="${diskPercentage}"
aria-valuemin="0" aria-valuemax="100"
th:text="${diskPercentage} + '%'">
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
Run and Test
Run the Spring Boot app.
Open your browser.
Access http://localhost:8080.
Example UI

You should see your PC system data in UI.
Comments