top of page

Building a PC System Dashboard with Java, Spring Boot, and OSHI

  • Writer: Sunfyre
    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


Spring Initializr interface showing project setup options. Includes Maven, Java version 17, Spring Boot 3.5.5, and dependencies like Spring Web.

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


  1. Run the Spring Boot app.

  2. Open your browser.

  3. Access http://localhost:8080.


Example UI


PC System Dashboard showing system overview, memory usage, and disk summary.

You should see your PC system data in UI.

 
 
 

Comments


Single post: Blog_Single_Post_Widget
bottom of page