Build a Broadband Telemetry API
Build a Spring Boot API that triages broadband telemetry and outages.
Introduction
30 Second Summary
A weak broadband signal can feel like a small slowdown until an entire area starts losing service. Support teams need a fast way to separate routine connection problems from critical outages.
In this project, you will build a fictional REST broadband telemetry service with Java 21 plus Spring Boot 3.5.16. Full-context tests will prove the service's SNR boundary rules.
What You'll Build
Your finished service gives support engineers a saved health classification for each modem reading plus focused regional and outage views.
By the end of this project, you'll have:
- A live telemetry intake that accepts a modem reading. Its response shows the saved ID, calculated status, and recorded timestamp.
- Two support views that retrieve readings by region or active outage status.
- A review-ready backend that you can demonstrate through Swagger UI.
- Secret Mission: Write an architecture decision record that defends REST for this service while identifying a stronger GraphQL use case.
Are there any prerequisites?
You should be comfortable reading Java and have internet access for Maven dependency downloads. The confirmed Windows environment already includes IntelliJ IDEA, JDK 21, and Maven.
Before We Start
This opening checkpoint gives your local broadband telemetry service a clear operational purpose. Every later layer stays focused on helping support engineers identify regional modem connections that require outage triage.
Configure the Spring Boot Project
A telemetry service only becomes dependable when every layer starts from the same Java runtime and dependency baseline. A version-pinned foundation prevents later controller, service, repository, and entity work from drifting apart.
You’ll define a Maven project pinned to Spring Boot 3.5.16 on Java 21. This model gives every project layer the same build configuration.
IntelliJ IDEA will import the Maven model. Windows PowerShell will prove that the application starts in your verified Java environment.
In this step, get ready to:
- Create the Maven project structure on your Desktop.
- Configure Spring Web, Spring Data JPA, H2, Spring Boot Test, and springdoc OpenAPI dependencies.
- Launch the application and confirm Swagger UI loads on port 8080.
Build the Maven project foundation
The pom.xml file is Maven’s contract for the project. It records the application identity, Java version, dependencies, and Spring Boot build plugin.
- Press the Windows key to open Windows search.
- Type Windows PowerShell.
- Press Enter to open Windows PowerShell.
- Move PowerShell to your Desktop by running this command:
Set-Location -Path "$HOME\Desktop"
What does this command do?
The command makes your Desktop the current PowerShell location. Your project folder will appear somewhere easy to find.
- Create the project directory structure by running this command:
New-Item -ItemType "Directory" -Path ".\spectrum-telemetry\src\main\java\com\spectrum\telemetry", ".\spectrum-telemetry\src\main\resources" -Force
What does this command create?
- The first path creates the Java package folders for com.spectrum.telemetry.
- The second path creates src/main/resources for application configuration.
- The -Force parameter allows PowerShell to create the missing parent folders.
You’ll see PowerShell list the two leaf directories it created. Your spectrum-telemetry project structure now exists on the Desktop.
Project folders missing?
- Confirm PowerShell is currently pointing to your Desktop.
- Check that both paths begin with .\spectrum-telemetry.
- Run the directory command again if PowerShell only created one path.
- Help me create the Maven directory structure in Windows PowerShell.
- Press the Windows key to return to Windows search.
- Type IntelliJ IDEA.
- Press Enter to open IntelliJ IDEA.
- Click Open on the IntelliJ IDEA welcome screen.
- Select the spectrum-telemetry folder on your Desktop.
- Click Open to load the folder.
The folder trust prompt can look serious because IntelliJ checks projects before running their code. Approve the prompt if it appears because you created every file in this folder.
- Right-click spectrum-telemetry in IntelliJ IDEA’s Project pane.
- Select New.
- Select File.
- Enter pom.xml.
- Press Enter to create the file.
- Define the project identity by replacing the file contents with this Maven skeleton:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.16</version>
<relativePath/>
</parent>
<groupId>com.spectrum</groupId>
<artifactId>spectrum-telemetry</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spectrum-telemetry</name>
<description>Regional broadband telemetry and outage triage engine</description>
<properties>
<java.version>21</java.version>
</properties>
</project>
What does this POM section define?
- The Spring Boot parent pins the project to version 3.5.16.
- The project coordinates identify the artifact as com.spectrum:spectrum-telemetry:0.0.1-SNAPSHOT.
- The java.version property tells Maven to compile the project for Java 21.
- Save pom.xml.
- Confirm pom.xml appears directly beneath spectrum-telemetry in the Project pane.
POM shown in the wrong folder?
- Move pom.xml directly beneath the spectrum-telemetry folder.
- Check that the filename ends with .xml.
- Help me place pom.xml correctly in my Spring Boot project.
- Find the closing </properties> line in pom.xml.
- Place your cursor below that line.
- Add the project dependencies by pasting this block before </project>:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.9.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
What do these dependencies provide?
- Spring Web provides the MVC components needed for REST operations.
- Spring Data JPA supplies relational repository support.
- H2 provides the local in-memory database used during this project.
- The springdoc starter generates an OpenAPI description and hosts Swagger UI. Spring Boot Test provides the testing foundation used later.
- Save pom.xml.
- Confirm IntelliJ IDEA shows all five dependency elements inside dependencies.
Dependencies marked as invalid?
- Confirm the dependencies block sits after properties.
- Check that every dependency element has a matching closing tag.
- Verify the springdoc version is 2.9.1.
- Help me debug the dependency section in pom.xml.
- Find the closing </dependencies> line.
- Place your cursor below that line.
- Add the Spring Boot build plugin by pasting this block before </project>:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
Why add the build plugin?
The Spring Boot Maven plugin gives Maven the goal used to launch the application. It also supports packaging the service as an executable JAR later in the project.
- Save pom.xml.
- Look for a small Maven icon beside pom.xml.
A Maven icon means IntelliJ IDEA imported the build model automatically.
- Right-click pom.xml if the Maven icon is absent.
- Select Add as Maven Project.
The first import can take a few minutes while Maven downloads the project dependencies. A busy progress indicator during this stage is expected.
- Confirm spectrum-telemetry appears in IntelliJ IDEA’s Maven tool window.
Maven project not detected?
- Confirm pom.xml is directly inside spectrum-telemetry.
- Check that </project> is the final XML tag.
- Wait for any dependency download shown in IntelliJ IDEA to finish.
- Help me import my pom.xml as an IntelliJ IDEA Maven project.
✔️ Awesome, I've got everything!
Your Maven project is imported. Double check that pom.xml is saved.
ⓧ I'd like to double check the full code
Your complete pom.xml should match this file.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.16</version>
<relativePath/>
</parent>
<groupId>com.spectrum</groupId>
<artifactId>spectrum-telemetry</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spectrum-telemetry</name>
<description>Regional broadband telemetry and outage triage engine</description>
<properties>
<java.version>21</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.9.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Add the application and database configuration
The application class gives Spring Boot a starting point. The properties file names the service and configures its local database.
Why use an in-memory database?
The H2 database keeps this project local while preserving relational persistence through JPA. Its data lasts only for the running application session.
This setup keeps the focus on Java layering and repository behavior. Durable production storage belongs in a later system design.
- Expand src/main/java/com/spectrum/telemetry in IntelliJ IDEA’s Project pane.
- Right-click the telemetry package.
- Select New.
- Select Java Class.
- Enter SpectrumTelemetryApplication.
- Replace the generated class contents with this application bootstrap code:
package com.spectrum.telemetry;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpectrumTelemetryApplication {
public static void main(String[] args) {
SpringApplication.run(SpectrumTelemetryApplication.class, args);
}
}
What does this class do?
- The package keeps the class at the top of the project’s component-scanning hierarchy.
- The @SpringBootApplication annotation enables configuration, auto-configuration, and component scanning.
- The main() method passes the application class to SpringApplication.run().
- Save SpectrumTelemetryApplication.java.
- Confirm IntelliJ IDEA shows the class beneath the com.spectrum.telemetry package without red syntax markers.
Application imports unresolved?
- Confirm IntelliJ IDEA has finished importing the Maven dependencies.
- Check that the class begins with package com.spectrum.telemetry;.
- Verify the filename is SpectrumTelemetryApplication.java.
- Help me resolve the Spring Boot imports in SpectrumTelemetryApplication.java.
✔️ Awesome, I've got everything!
Your Spring Boot entry point is ready. Double check that the Java file is saved.
ⓧ I'd like to double check the full code
Your complete SpectrumTelemetryApplication.java file should match this code.
package com.spectrum.telemetry;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpectrumTelemetryApplication {
public static void main(String[] args) {
SpringApplication.run(SpectrumTelemetryApplication.class, args);
}
}
- Right-click src/main/resources in the Project pane.
- Select New.
- Select File.
- Enter application.properties.
- Press Enter to create the file.
- Configure the application by pasting these properties:
spring.application.name=spectrum-telemetry
spring.datasource.url=jdbc:h2:mem:telemetrydb;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.open-in-view=false
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
springdoc.swagger-ui.path=/swagger-ui.html
What does this configuration control?
- The application name identifies the running service as spectrum-telemetry.
- The datasource URL creates the in-memory telemetrydb database. Spring Boot controls its shutdown behavior.
- The JPA settings recreate the schema for each run. They also disable the web request persistence context.
- The final properties enable the H2 console at /h2-console and Swagger UI at /swagger-ui.html.
- Save application.properties.
- Confirm the file appears beneath src/main/resources.
Configuration file not detected?
- Confirm the filename is exactly application.properties.
- Check that the file sits directly inside src/main/resources.
- Verify the datasource URL includes DB_CLOSE_ON_EXIT=FALSE.
- Help me debug application.properties in my Spring Boot project.
✔️ Awesome, I've got everything!
Your local application configuration is ready. Double check that application.properties is saved.
ⓧ I'd like to double check the full code
Your complete application.properties file should match these values.
spring.application.name=spectrum-telemetry
spring.datasource.url=jdbc:h2:mem:telemetrydb;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.open-in-view=false
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
springdoc.swagger-ui.path=/swagger-ui.html
Verify the toolchain and launch the service
The project files now describe one shared build. The version checks confirm that PowerShell uses Java 21 before Maven starts the application.
- Switch back to Windows PowerShell from earlier.
- Check the active Java runtime by running this command:
java -version
What does this check prove?
The command reports the Java runtime available to PowerShell. The version output should identify Java 21.
- Check Maven’s active runtime by running this command:
mvn -version
What does the Maven check prove?
Maven reports its own version plus the Java runtime it will use for the build. The Java line should also identify version 21.
Seeing a different Java version?
- Confirm both version commands reference the same Java installation.
- Restart Windows PowerShell after correcting your Java environment configuration.
- Run both version checks again before starting the application.
- Help me align Maven with Java 21 on Windows.
- Move PowerShell into the project folder by running this command:
Set-Location -Path "$HOME\Desktop\spectrum-telemetry"
Why change folders?
Maven looks for pom.xml in the current PowerShell location. This command places the terminal inside the folder that contains your build file.
Before you start the service, ask yourself which port Spring Boot will use.
The first launch can take a few minutes while Maven downloads the pinned dependencies. Ongoing download output means the build is still progressing.
- Start the Spring Boot application by running this command:
mvn spring-boot:run
What does this command do?
Maven invokes the Spring Boot plugin declared in pom.xml. The plugin compiles the application before starting its embedded web server.
You’ll see startup logs that identify spectrum-telemetry as running on port 8080.
- Press the Windows key to open Windows search.
- Type Microsoft Edge.
- Press Enter to open Microsoft Edge.
- Enter http://localhost:8080/swagger-ui.html in the address bar.
- Press Enter to load Swagger UI.
You’ll see the Swagger UI interface load successfully. The API has no custom operations yet because the controller arrives in the next step.
Swagger UI not loading?
- Confirm Windows PowerShell still shows the application as running.
- Check that the startup logs identify port 8080.
- Verify springdoc.swagger-ui.path=/swagger-ui.html remains in application.properties.
- Help me diagnose why my Spring Boot Swagger UI page is unavailable.
That is the foundation locked in: your Spring Boot service now starts from the pinned Maven model and exposes its local documentation page. Next up, you’ll build the first telemetry ingestion path.
Ingest the First Telemetry Readings
Your Spring Boot 3.5.16 application is running with an H2 database behind it. The application still lacks a domain model that can represent modem telemetry.
This step builds the first complete write path from an HTTP request to a saved database row. You will submit two readings to discover how the current threshold order handles each value.
In this step, get ready to:
- Model each telemetry reading as a persistent Java entity.
- Classify each reading through a service that saves it in H2.
- Submit two POST requests to the ingestion endpoint.
Create the telemetry entity
A JPA entity maps a Java object to a relational database row. TelemetryReading holds the submitted values plus the status and timestamp generated by the service.
- Switch back to IntelliJ IDEA.
- Right-click src/main/java in the Project tool window.
- Select New.
- Select Java Class.
- Enter com.spectrum.telemetry.model.TelemetryReading as the class name.
- Replace the generated contents of TelemetryReading.java with this code:
package com.spectrum.telemetry.model;
import java.time.LocalDateTime;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
@Entity
public class TelemetryReading {
@Id @GeneratedValue private Long id;
private String deviceId;
private String region;
private double snr;
private String status;
private LocalDateTime recordedAt;
public TelemetryReading() {}
public TelemetryReading(Long id, String deviceId, String region, double snr, String status, LocalDateTime recordedAt) { this.id = id; this.deviceId = deviceId; this.region = region; this.snr = snr; this.status = status; this.recordedAt = recordedAt; }
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getDeviceId() { return deviceId; }
public void setDeviceId(String deviceId) { this.deviceId = deviceId; }
public String getRegion() { return region; }
public void setRegion(String region) { this.region = region; }
public double getSnr() { return snr; }
public void setSnr(double snr) { this.snr = snr; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public LocalDateTime getRecordedAt() { return recordedAt; }
public void setRecordedAt(LocalDateTime recordedAt) { this.recordedAt = recordedAt; }
}
What does this entity do?
- The @Entity annotation tells JPA to map this class to a database table.
- The id field uses @GeneratedValue so the persistence layer assigns each saved row an identifier.
- The no-argument constructor lets JPA create entity instances.
- The getters and setters let Spring read the request body into the entity.
- Save TelemetryReading.java.
- Return to Windows PowerShell from the previous step.
- Press Ctrl+C to stop the running application.
- Compile the entity through the Maven test phase by running:
mvn test
What does this command check?
The Maven test phase compiles the application before looking for tests. A successful run proves that the entity uses valid Java and JPA types.
Maven should complete without compilation failures. Your persistent telemetry model is now part of the application.
Seeing an entity compilation error?
- Confirm the first line says package com.spectrum.telemetry.model;.
- Check that the file is named TelemetryReading.java with matching capitalization.
- Compare the braces around the class and each method with the reference below.
- Help me debug my telemetry entity.
✔️ Awesome, I've got everything!
Your entity compiles successfully. Double-check that TelemetryReading.java is saved.
ⓧ I'd like to double check the full code
package com.spectrum.telemetry.model;
import java.time.LocalDateTime;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
@Entity
public class TelemetryReading {
@Id @GeneratedValue private Long id;
private String deviceId;
private String region;
private double snr;
private String status;
private LocalDateTime recordedAt;
public TelemetryReading() {}
public TelemetryReading(Long id, String deviceId, String region, double snr, String status, LocalDateTime recordedAt) { this.id = id; this.deviceId = deviceId; this.region = region; this.snr = snr; this.status = status; this.recordedAt = recordedAt; }
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getDeviceId() { return deviceId; }
public void setDeviceId(String deviceId) { this.deviceId = deviceId; }
public String getRegion() { return region; }
public void setRegion(String region) { this.region = region; }
public double getSnr() { return snr; }
public void setSnr(double snr) { this.snr = snr; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public LocalDateTime getRecordedAt() { return recordedAt; }
public void setRecordedAt(LocalDateTime recordedAt) { this.recordedAt = recordedAt; }
}
What should match?
This reference contains the complete entity for this step. Compare its package declaration and fields with your saved file.
Persist and classify each reading
The entity defines the data shape. A repository now gives the service a persistence API through Spring Data JPA.
- Right-click src/main/java in the Project tool window.
- Select New.
- Select Java Class.
- Enter com.spectrum.telemetry.repository.TelemetryReadingRepository as the class name.
- Replace the generated contents of TelemetryReadingRepository.java with this code:
package com.spectrum.telemetry.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.spectrum.telemetry.model.TelemetryReading;
public interface TelemetryReadingRepository extends JpaRepository<TelemetryReading, Long> {}
What does this repository do?
- Extending JpaRepository gives the interface standard persistence operations.
- The first generic type identifies TelemetryReading as the stored entity.
- The second generic type identifies Long as the entity identifier type.
- Save TelemetryReadingRepository.java.
- Verify the repository compiles by running:
mvn test
What does this build prove?
This run checks that the repository extends the correct Spring Data interface. It also confirms that its entity and identifier types compile together.
Maven should finish without compilation failures. The application now has a persistence boundary for telemetry rows.
Repository not compiling?
- Confirm the file declares an interface instead of a class.
- Check that TelemetryReading imports from com.spectrum.telemetry.model.
- Help me fix my repository declaration.
✔️ Awesome, I've got everything!
Your repository compiles successfully. Keep TelemetryReadingRepository.java saved.
ⓧ I'd like to double check the full code
package com.spectrum.telemetry.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.spectrum.telemetry.model.TelemetryReading;
public interface TelemetryReadingRepository extends JpaRepository<TelemetryReading, Long> {}
What should match?
This reference is the complete repository for this step. Its entity type and identifier type must match TelemetryReading.
The service owns the ingestion rules. It uses constructor injection so Spring supplies the repository when the application starts.
- Right-click src/main/java in the Project tool window.
- Select New.
- Select Java Class.
- Enter com.spectrum.telemetry.service.TelemetryService as the class name.
- Replace the generated contents of TelemetryService.java with this code:
package com.spectrum.telemetry.service;
import java.time.LocalDateTime;
import org.springframework.stereotype.Service;
import com.spectrum.telemetry.model.TelemetryReading;
import com.spectrum.telemetry.repository.TelemetryReadingRepository;
@Service
public class TelemetryService {
public static final String STATUS_CONNECTED = "CONNECTED";
public static final String STATUS_CONNECTION_ISSUE = "CONNECTION_ISSUE";
public static final String STATUS_CRITICAL_OUTAGE = "CRITICAL_OUTAGE";
private final TelemetryReadingRepository repository;
public TelemetryService(TelemetryReadingRepository repository) { this.repository = repository; }
public TelemetryReading ingest(TelemetryReading reading) {
reading.setId(null);
if (reading.getSnr() < 22.0) reading.setStatus(STATUS_CONNECTION_ISSUE);
else if (reading.getSnr() <= 10.0) reading.setStatus(STATUS_CRITICAL_OUTAGE);
else reading.setStatus(STATUS_CONNECTED);
reading.setRecordedAt(LocalDateTime.now());
return repository.save(reading);
}
}
How does ingestion work?
- The three constants keep each status value consistent across the service.
- The constructor receives the repository through constructor injection.
- The ingest() method clears any incoming ID so persistence can generate one.
- The conditional evaluates its branches from top to bottom. The first matching branch sets the status.
- The method records the current time before saving the entity.
- Save TelemetryService.java.
- Verify the service compiles by running:
mvn test
What does this build verify?
This run compiles the entity and repository with the new service. It confirms that Spring can detect the service annotation and resolve its Java types.
Maven should complete without compilation failures. The classification and save path now compile as one unit.
Service not compiling?
- Check that TelemetryReadingRepository imports from the repository package.
- Confirm every status constant uses the exact spelling shown in the code.
- Compare the parentheses around each reading.getSnr() condition.
- Help me debug my ingestion service.
✔️ Awesome, I've got everything!
Your service compiles with the entity and repository. Double-check that TelemetryService.java is saved.
ⓧ I'd like to double check the full code
package com.spectrum.telemetry.service;
import java.time.LocalDateTime;
import org.springframework.stereotype.Service;
import com.spectrum.telemetry.model.TelemetryReading;
import com.spectrum.telemetry.repository.TelemetryReadingRepository;
@Service
public class TelemetryService {
public static final String STATUS_CONNECTED = "CONNECTED";
public static final String STATUS_CONNECTION_ISSUE = "CONNECTION_ISSUE";
public static final String STATUS_CRITICAL_OUTAGE = "CRITICAL_OUTAGE";
private final TelemetryReadingRepository repository;
public TelemetryService(TelemetryReadingRepository repository) { this.repository = repository; }
public TelemetryReading ingest(TelemetryReading reading) {
reading.setId(null);
if (reading.getSnr() < 22.0) reading.setStatus(STATUS_CONNECTION_ISSUE);
else if (reading.getSnr() <= 10.0) reading.setStatus(STATUS_CRITICAL_OUTAGE);
else reading.setStatus(STATUS_CONNECTED);
reading.setRecordedAt(LocalDateTime.now());
return repository.save(reading);
}
}
What should match?
This reference is the complete first-pass service. Compare the status constants and condition order with your saved file.
Expose and test the ingestion endpoint
A REST controller connects an HTTP request to the service method. Spring converts the JSON request body into a TelemetryReading before ingestion begins.
- Right-click src/main/java in the Project tool window.
- Select New.
- Select Java Class.
- Enter com.spectrum.telemetry.controller.TelemetryController as the class name.
- Replace the generated contents of TelemetryController.java with this code:
package com.spectrum.telemetry.controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.spectrum.telemetry.model.TelemetryReading;
import com.spectrum.telemetry.service.TelemetryService;
@RestController
@RequestMapping("/api")
public class TelemetryController {
private final TelemetryService telemetryService;
public TelemetryController(TelemetryService telemetryService) { this.telemetryService = telemetryService; }
@PostMapping("/telemetry")
public TelemetryReading ingest(@RequestBody TelemetryReading reading) { return telemetryService.ingest(reading); }
}
What does this controller do?
- The @RestController annotation makes the returned entity part of the HTTP response.
- The class-level @RequestMapping("/api") adds the shared API path.
- The @PostMapping("/telemetry") annotation maps POST requests to ingest().
- The constructor gives the controller access to TelemetryService.
- Save TelemetryController.java.
- Start the application from the spectrum-telemetry project directory by running:
mvn spring-boot:run
What does this command start?
The Spring Boot Maven plugin compiles the new controller and launches the application. Spring discovers the entity and repository during startup.
The terminal should report that the application started on port 8080. Leave this PowerShell window running.
Application not starting?
- Check the first compilation error for the file name and line number.
- Confirm every new file begins with the package declaration shown in its code block.
- Verify the earlier application process stopped before this run claimed port 8080.
- Help me diagnose my Spring Boot startup failure.
✔️ Awesome, I've got everything!
The application is running with the ingestion endpoint registered. Keep the server PowerShell window open.
ⓧ I'd like to double check the full code
package com.spectrum.telemetry.controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.spectrum.telemetry.model.TelemetryReading;
import com.spectrum.telemetry.service.TelemetryService;
@RestController
@RequestMapping("/api")
public class TelemetryController {
private final TelemetryService telemetryService;
public TelemetryController(TelemetryService telemetryService) { this.telemetryService = telemetryService; }
@PostMapping("/telemetry")
public TelemetryReading ingest(@RequestBody TelemetryReading reading) { return telemetryService.ingest(reading); }
}
What should match?
This reference is the complete controller for this step. Compare its base path and POST mapping with your saved file.
- Press the Windows key to open Windows search.
- Type PowerShell.
- Press Enter to open a second PowerShell window.
Before you send the first request, which status do you expect for an SNR of 25.0?
- Submit the connected reading from the second PowerShell window by running:
$connectedBody = '{"deviceId":"modem-1001","region":"Northeast","snr":25.0}'
Invoke-RestMethod -Method Post -Uri "http://localhost:8080/api/telemetry" -ContentType "application/json" -Body $connectedBody
What does this request do?
- The first line stores a JSON telemetry reading in $connectedBody.
- The second line sends that JSON to POST /api/telemetry.
- PowerShell converts the JSON response into an object whose properties appear in the terminal.
You should see a generated id and a populated recordedAt value. The status property should be CONNECTED.
That first write path works. Your API has classified and stored a modem reading through every application layer.
Connected request not working?
- Confirm the first PowerShell window still shows the running application.
- Check that the controller uses the combined path /api/telemetry.
- Verify the JSON uses deviceId and snr with matching capitalization.
- Help me debug the connected POST request.
Now focus on the lower threshold. Before you run the next request, which conditional branch do you think evaluates first for an SNR of 10.0?
- Submit the low-SNR reading from the second PowerShell window by running:
$criticalBody = '{"deviceId":"modem-1002","region":"Northeast","snr":10.0}'
Invoke-RestMethod -Method Post -Uri "http://localhost:8080/api/telemetry" -ContentType "application/json" -Body $criticalBody
What does this request test?
This request follows the same ingestion path with a lower SNR value. Its returned status shows which threshold condition matched first.
You will see CONNECTION_ISSUE in the response. The broader condition accepts 10.0 before the critical condition gets a chance to evaluate it.
This shortfall is intentional
The response proves that condition order affects classification. You have reproduced the threshold defect that the next step resolves.
Low-SNR request not returning a reading?
- Confirm the request body contains 10.0 for snr.
- Check the server PowerShell window for request-processing errors.
- Verify the service still checks reading.getSnr() < 22.0 first.
- Help me debug the low-SNR POST request.
Your ingestion endpoint now persists telemetry with generated IDs and timestamps. Next, you will correct the threshold order before adding regional and active-outage queries.
Fix Triage and Add Outage Queries
Your ingestion path now saves modem readings in H2. The 10.0 check also exposed the intended threshold-ordering defect.
This step fixes the threshold precedence. You will then use Spring Data JPA derived queries to retrieve regional readings and active outages through two new REST operations.
In this step, get ready to:
- Reorder the SNR rules so critical outages take priority.
- Add repository queries for regions and statuses.
- Expose regional telemetry and active outages through GET operations.
Fix the threshold precedence
An if chain stops at its first matching condition. The broad rule for values below 22.0 currently captures 10.0 before the critical rule can evaluate it.
- Switch back to the Windows PowerShell window running the application.
- Press Ctrl+C to stop the application.
- Return to TelemetryService.java in IntelliJ IDEA.
- Find the current threshold chain inside ingest():
if (reading.getSnr() < 22.0) reading.setStatus(STATUS_CONNECTION_ISSUE);
else if (reading.getSnr() <= 10.0) reading.setStatus(STATUS_CRITICAL_OUTAGE);
else reading.setStatus(STATUS_CONNECTED);
Why Does This Order Fail?
A value of 10.0 satisfies snr < 22.0. Java stops at that first match, so the narrower critical condition never runs.
- Replace the threshold chain with this corrected order:
if (reading.getSnr() <= 10.0) reading.setStatus(STATUS_CRITICAL_OUTAGE);
else if (reading.getSnr() < 22.0) reading.setStatus(STATUS_CONNECTION_ISSUE);
else reading.setStatus(STATUS_CONNECTED);
Why Does This Order Work?
- The first branch assigns CRITICAL_OUTAGE to values at or below 10.0.
- The second branch assigns CONNECTION_ISSUE to the remaining values below 22.0.
- The final branch assigns CONNECTED to values at or above 22.0.
- Save TelemetryService.java.
- Restart the application from the project directory by running:
mvn spring-boot:run
What Does This Command Do?
The Spring Boot Maven plugin compiles the updated service before launching it. A successful startup proves the reordered Java code is valid.
You should see the application start on port 8080.
Before you send the boundary reading again, do you expect the narrower condition to claim it first?
- Switch back to the Windows PowerShell window used for API requests.
- Send the 10.0 SNR reading again by running:
$criticalBody = '{"deviceId":"modem-1002","region":"Northeast","snr":10.0}'
Invoke-RestMethod -Method Post -Uri "http://localhost:8080/api/telemetry" -ContentType "application/json" -Body $criticalBody
What Does This Request Prove?
The POST request sends the same boundary value that exposed the defect. The returned object shows which branch now handles that value.
You should see modem-1002 with a generated ID. Its status should now be CRITICAL_OUTAGE.
Still Seeing the Wrong Status?
- Confirm the <= 10.0 condition appears before the < 22.0 condition.
- Confirm you stopped the old application process before restarting it.
- Check that IntelliJ IDEA saved TelemetryService.java before Maven compiled it.
- Help me debug the SNR threshold order.
Add the outage query methods
Derived repository queries turn method names into database lookups. The repository needs one method for a region and one method for a status.
- Switch to the Windows PowerShell window running the application.
- Press Ctrl+C to stop the application.
- Return to TelemetryReadingRepository.java in IntelliJ IDEA.
- Find the current repository interface:
package com.spectrum.telemetry.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.spectrum.telemetry.model.TelemetryReading;
public interface TelemetryReadingRepository extends JpaRepository<TelemetryReading, Long> {}
What Can the Repository Do Now?
The empty interface inherits standard persistence operations from JpaRepository. It does not yet declare lookups for this project's region or status fields.
- Replace the repository contents with the regional and status query declarations below:
package com.spectrum.telemetry.repository;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import com.spectrum.telemetry.model.TelemetryReading;
public interface TelemetryReadingRepository extends JpaRepository<TelemetryReading, Long> {
List<TelemetryReading> findByRegion(String region);
List<TelemetryReading> findByStatus(String status);
}
What Do These Methods Add?
- The List return type supports multiple matching readings.
- The findByRegion() method looks up rows whose region field matches the supplied value.
- The findByStatus() method looks up rows whose status field matches the supplied value.
- Save TelemetryReadingRepository.java.
- Compile the repository by starting the application with:
mvn spring-boot:run
What Does This Startup Check?
Spring creates implementations for both derived query methods while building the application context. A successful startup confirms that their names map to fields on TelemetryReading.
You should see the application start successfully on port 8080.
Application Fails to Start?
- Check that the import uses java.util.List.
- Check that the method names contain the entity field names Region and Status.
- Confirm both declarations end with Java statement terminators.
- Help me fix my derived repository methods.
✔️ Awesome, I've got everything!
Your repository now provides regional and status lookups. Double check that TelemetryReadingRepository.java is saved.
ⓧ I'd like to double check the full code
Compare your repository with this complete file.
package com.spectrum.telemetry.repository;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import com.spectrum.telemetry.model.TelemetryReading;
public interface TelemetryReadingRepository extends JpaRepository<TelemetryReading, Long> {
List<TelemetryReading> findByRegion(String region);
List<TelemetryReading> findByStatus(String status);
}
What Should Match?
The full file imports List and declares both query methods inside the repository interface.
The repository can now retrieve each required group. The service still needs to turn those lookups into operations for the controller.
- Switch to the Windows PowerShell window running the application.
- Press Ctrl+C to stop the application.
- Return to TelemetryService.java in IntelliJ IDEA.
- Find the current Java time import:
import java.time.LocalDateTime;
Why Add Collection Imports?
The service currently imports only its timestamp type. The new read operations return lists, while the outage operation builds one combined list.
- Replace the current Java time import with these collection imports:
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
What Do These Imports Support?
The List type defines each method's return value. The ArrayList type provides a mutable collection for combining outage categories.
- Find the closing brace immediately after ingest().
- Insert these query methods before the class closing brace:
public List<TelemetryReading> getByRegion(String region) { return repository.findByRegion(region); }
public List<TelemetryReading> getActiveOutages() {
List<TelemetryReading> outages = new ArrayList<>();
outages.addAll(repository.findByStatus(STATUS_CRITICAL_OUTAGE));
outages.addAll(repository.findByStatus(STATUS_CONNECTION_ISSUE));
return outages;
}
How Does the Service Build Each View?
- The getByRegion() method delegates the regional lookup to the repository.
- The getActiveOutages() method creates one result list.
- The two addAll() calls include critical outages and connection issues in that result.
- Save TelemetryService.java.
- Compile the new service operations by starting the application with:
mvn spring-boot:run
What Does This Startup Prove?
A successful startup confirms that the service can call both repository methods. It also confirms that the list types compile correctly.
You should see the application start on port 8080 without repository or service errors.
Seeing a Compilation Error?
- Confirm List and ArrayList are imported from java.util.
- Confirm both service methods sit inside the TelemetryService class.
- Check that each repository method name matches its declaration exactly.
- Help me fix my telemetry query service methods.
✔️ Awesome, I've got everything!
Your service can now retrieve a region and aggregate both active outage statuses. Double check that TelemetryService.java is saved.
ⓧ I'd like to double check the full code
Compare your service with this complete file.
package com.spectrum.telemetry.service;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Service;
import com.spectrum.telemetry.model.TelemetryReading;
import com.spectrum.telemetry.repository.TelemetryReadingRepository;
@Service
public class TelemetryService {
public static final String STATUS_CONNECTED = "CONNECTED";
public static final String STATUS_CONNECTION_ISSUE = "CONNECTION_ISSUE";
public static final String STATUS_CRITICAL_OUTAGE = "CRITICAL_OUTAGE";
private final TelemetryReadingRepository repository;
public TelemetryService(TelemetryReadingRepository repository) { this.repository = repository; }
public TelemetryReading ingest(TelemetryReading reading) {
reading.setId(null);
if (reading.getSnr() <= 10.0) reading.setStatus(STATUS_CRITICAL_OUTAGE);
else if (reading.getSnr() < 22.0) reading.setStatus(STATUS_CONNECTION_ISSUE);
else reading.setStatus(STATUS_CONNECTED);
reading.setRecordedAt(LocalDateTime.now());
return repository.save(reading);
}
public List<TelemetryReading> getByRegion(String region) { return repository.findByRegion(region); }
public List<TelemetryReading> getActiveOutages() {
List<TelemetryReading> outages = new ArrayList<>();
outages.addAll(repository.findByStatus(STATUS_CRITICAL_OUTAGE));
outages.addAll(repository.findByStatus(STATUS_CONNECTION_ISSUE));
return outages;
}
}
What Should Match?
The complete service contains the corrected threshold order. It also contains both query methods beneath ingest().
Expose and verify the GET operations
The service methods are ready, but clients need controller mappings to reach them. One path accepts a region from the URL, while the other returns the combined outage list.
- Switch to the Windows PowerShell window running the application.
- Press Ctrl+C to stop the application.
- Return to TelemetryController.java in IntelliJ IDEA.
- Replace the controller contents with the three API operations below:
package com.spectrum.telemetry.controller;
import java.util.List;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.spectrum.telemetry.model.TelemetryReading;
import com.spectrum.telemetry.service.TelemetryService;
@RestController
@RequestMapping("/api")
public class TelemetryController {
private final TelemetryService telemetryService;
public TelemetryController(TelemetryService telemetryService) { this.telemetryService = telemetryService; }
@PostMapping("/telemetry") public TelemetryReading ingest(@RequestBody TelemetryReading reading) { return telemetryService.ingest(reading); }
@GetMapping("/telemetry/region/{region}") public List<TelemetryReading> getByRegion(@PathVariable("region") String region) { return telemetryService.getByRegion(region); }
@GetMapping("/outages/active") public List<TelemetryReading> getActiveOutages() { return telemetryService.getActiveOutages(); }
}
What Do the GET Mappings Expose?
- The regional mapping captures the region segment from the request path.
- The active-outage mapping calls the service operation that combines both outage statuses.
- The existing POST mapping continues to send incoming readings through ingest().
- Save TelemetryController.java.
✔️ Awesome, I've got everything!
Your controller now exposes one POST operation and two GET operations. Double check that TelemetryController.java is saved.
ⓧ I'd like to double check the full code
Compare your controller with this complete file.
package com.spectrum.telemetry.controller;
import java.util.List;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.spectrum.telemetry.model.TelemetryReading;
import com.spectrum.telemetry.service.TelemetryService;
@RestController
@RequestMapping("/api")
public class TelemetryController {
private final TelemetryService telemetryService;
public TelemetryController(TelemetryService telemetryService) { this.telemetryService = telemetryService; }
@PostMapping("/telemetry") public TelemetryReading ingest(@RequestBody TelemetryReading reading) { return telemetryService.ingest(reading); }
@GetMapping("/telemetry/region/{region}") public List<TelemetryReading> getByRegion(@PathVariable("region") String region) { return telemetryService.getByRegion(region); }
@GetMapping("/outages/active") public List<TelemetryReading> getActiveOutages() { return telemetryService.getActiveOutages(); }
}
What Should Match?
The complete controller imports the GET mapping types. It exposes all three operations beneath the constructor.
- Start the completed application from the project directory by running:
mvn spring-boot:run
What Does This Final Startup Check?
Maven compiles the controller against the new service methods. Spring then registers all three request mappings during startup.
You should see the application start successfully on port 8080.
- Switch back to the Windows PowerShell window used for API requests.
- Create a Northeast outage for the two GET checks by running:
$criticalBody = '{"deviceId":"modem-1002","region":"Northeast","snr":10.0}'
Invoke-RestMethod -Method Post -Uri "http://localhost:8080/api/telemetry" -ContentType "application/json" -Body $criticalBody
Why Submit the Reading Again?
The in-memory database starts with a fresh set of rows after the application restarts. This request gives both GET operations a saved Northeast outage to retrieve.
You should see the saved reading with status CRITICAL_OUTAGE.
Before you query both views, which lists do you expect to include modem-1002?
- Query the Northeast readings and active outages by running:
Invoke-RestMethod -Method Get -Uri "http://localhost:8080/api/telemetry/region/Northeast"
Invoke-RestMethod -Method Get -Uri "http://localhost:8080/api/outages/active"
What Do These Queries Check?
- The first request retrieves every saved reading whose region is Northeast.
- The second request retrieves readings classified as CRITICAL_OUTAGE or CONNECTION_ISSUE.
You should see modem-1002 with region Northeast and status CRITICAL_OUTAGE in both results.
Missing a Reading from the Results?
- Confirm the POST request ran after the latest application restart.
- Check that the regional URL ends with the exact saved value Northeast.
- Confirm getActiveOutages() calls findByStatus() for both outage constants.
- Help me debug my regional or active-outage query.
That closes the triage gap. Your service now classifies the 10.0 boundary correctly and returns saved readings through both operational views.
Next up, you will turn those boundary expectations into full-context tests that catch future regressions automatically.
Prove the Boundary Rules with Full-Context Tests
Your triage service now classifies critical outages before broader connection issues. Exact threshold behavior can regress after a later code change.
A full-context JUnit test loads the complete Spring Boot application context. It proves each boundary through the real H2 persistence layer.
In this step, get ready to:
- Create a full-context test with an injected TelemetryService.
- Prove the critical and connected SNR boundaries.
- Run the tests through Maven.
Create the critical boundary test
The @SpringBootTest annotation starts the application context during the test. Spring injects the real TelemetryService so the test reaches the configured repository.
- In IntelliJ IDEA's project panel, create src/test/java/com/spectrum/telemetry/service/TelemetryServiceTest.java.
- Replace the generated file contents by pasting this code:
package com.spectrum.telemetry.service;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.spectrum.telemetry.model.TelemetryReading;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest
class TelemetryServiceTest {
@Autowired private TelemetryService telemetryService;
@Test
void snrAtCriticalBoundaryIsCriticalOutage() {
TelemetryReading reading = new TelemetryReading(null, "device-critical", "Northeast", 10.0, null, null);
TelemetryReading saved = telemetryService.ingest(reading);
assertThat(saved.getStatus()).isEqualTo(TelemetryService.STATUS_CRITICAL_OUTAGE);
assertThat(saved.getId()).isNotNull();
assertThat(saved.getRecordedAt()).isNotNull();
}
}
What does this test do?
- The @SpringBootTest annotation loads the Spring Boot application context.
- The @Autowired field receives the service bean managed by that context.
- The 10.0 reading exercises the inclusive critical boundary.
- The first assertion confirms the CRITICAL_OUTAGE classification.
- The remaining assertions prove that H2 generated an ID and stored a timestamp.
- Save TelemetryServiceTest.java.
- Switch back to the Windows PowerShell window in the folder containing pom.xml.
Before you run the test, do you expect an SNR of 10.0 to pass through the real service as a critical outage?
- Run the current test by executing this command:
mvn test
What does this command do?
- The mvn test command runs Maven's test lifecycle phase.
- The test starts the Spring context because the class uses @SpringBootTest.
- The service saves the boundary reading through the configured H2 repository.
Maven's summary should show one test with zero failures and zero errors. This proves the inclusive critical boundary works through the full application stack.
Does the first test fail?
- Confirm the file is saved at src/test/java/com/spectrum/telemetry/service/TelemetryServiceTest.java.
- Check that @SpringBootTest appears directly above TelemetryServiceTest.
- Check that the injected field uses the exact name telemetryService.
- Help me diagnose the failing full-context test.
Add the connected boundary test
An SNR of 22.0 is the first value outside the connection-issue range. This second test protects the exact point where the service returns CONNECTED.
- Paste the following method below snrAtCriticalBoundaryIsCriticalOutage() and above the final closing brace:
@Test
void snrAtConnectionBoundaryIsConnected() {
TelemetryReading reading = new TelemetryReading(null, "device-connected", "Southwest", 22.0, null, null);
TelemetryReading saved = telemetryService.ingest(reading);
assertThat(saved.getStatus()).isEqualTo(TelemetryService.STATUS_CONNECTED);
assertThat(saved.getId()).isNotNull();
assertThat(saved.getRecordedAt()).isNotNull();
}
What does this test prove?
- The 22.0 reading exercises the connected boundary.
- The status assertion confirms that the strict connection-issue comparison excludes 22.0.
- The ID assertion proves the repository saved the entity.
- The timestamp assertion proves the service recorded the ingestion time.
- Save TelemetryServiceTest.java.
✔️ Awesome, I've got everything!
Your saved test file now contains both boundary methods.
ⓧ I'd like to double check the full code
The finished TelemetryServiceTest.java file should match this reference.
package com.spectrum.telemetry.service;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.spectrum.telemetry.model.TelemetryReading;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest
class TelemetryServiceTest {
@Autowired private TelemetryService telemetryService;
@Test
void snrAtCriticalBoundaryIsCriticalOutage() {
TelemetryReading reading = new TelemetryReading(null, "device-critical", "Northeast", 10.0, null, null);
TelemetryReading saved = telemetryService.ingest(reading);
assertThat(saved.getStatus()).isEqualTo(TelemetryService.STATUS_CRITICAL_OUTAGE);
assertThat(saved.getId()).isNotNull();
assertThat(saved.getRecordedAt()).isNotNull();
}
@Test
void snrAtConnectionBoundaryIsConnected() {
TelemetryReading reading = new TelemetryReading(null, "device-connected", "Southwest", 22.0, null, null);
TelemetryReading saved = telemetryService.ingest(reading);
assertThat(saved.getStatus()).isEqualTo(TelemetryService.STATUS_CONNECTED);
assertThat(saved.getId()).isNotNull();
assertThat(saved.getRecordedAt()).isNotNull();
}
}
How is this file structured?
This reference keeps the context annotation and injected service at class level. Each test creates one boundary reading before checking the saved result.
- Return to the Windows PowerShell window in the folder containing pom.xml.
Before you run the full suite, which status do you expect each exact threshold to produce?
- Run both full-context tests by executing this command:
mvn test
What does the full test run prove?
- Maven discovers both methods marked with @Test.
- The critical test proves that 10.0 becomes CRITICAL_OUTAGE.
- The connected test proves that 22.0 becomes CONNECTED.
- Both tests verify generated IDs and timestamps through H2.
Maven should report two tests with zero failures and zero errors. You have now protected both SNR boundaries with repeatable checks against the real Spring configuration.
Does the full suite fail?
- Confirm both test methods remain inside the TelemetryServiceTest class braces.
- Check that the connected test uses the exact value 22.0.
- Check that its assertion compares against TelemetryService.STATUS_CONNECTED.
- Help me fix the failing boundary test suite.
Your triage rules are now protected by full-context tests. Next, you'll demonstrate the API contract and package the tested service.
Inspect, Document, and Package the Service
Your full-context tests now prove both threshold boundaries against the real Spring Boot configuration.
A reviewer still needs to inspect the service without reading its implementation. Swagger UI makes the API contract visible.
The H2 Database console reveals saved telemetry rows. Maven packages the tested service into an executable JAR.
In this step, get ready to:
- Exercise all three API operations through Swagger UI.
- Inspect a saved telemetry row through the H2 console.
- Package the tested service as an executable JAR.
Exercise the API in Swagger UI
OpenAPI describes your controller as a machine-readable contract. Swagger UI turns that contract into an interactive page for sending requests.
- Return to the Windows PowerShell window where mvn test passed.
- Start the application by running this command:
mvn spring-boot:run
What does this command do?
The mvn spring-boot:run command launches the application through the Spring Boot Maven plugin. The process stays active in this PowerShell window.
You'll see startup output reporting that the application is running on port 8080. Your local API is now ready to accept browser requests.
- Leave the running PowerShell process active during the API inspection.
Swagger UI cannot reach the application?
- Confirm the PowerShell window still shows the running application process.
- Check that you started the application from the same directory where the Maven tests passed.
- Review the startup output for the first reported failure.
- Help me diagnose why my Spring Boot application does not start on port 8080.
- Press the Windows key to open system search.
- Type Microsoft Edge into the search field.
- Press Enter to open Microsoft Edge.
- Enter http://localhost:8080/swagger-ui.html in the address bar.
You'll see the POST /api/telemetry operation documented on the page.
The same page lists GET /api/telemetry/region/{region} plus GET /api/outages/active.
- Expand the POST operation at /api/telemetry.
- Activate the operation's editable request controls.
- Set the deviceId value in the request body to modem-1002.
- Set the region value in the request body to Northeast.
- Set the snr value in the request body to 10.0.
- Submit the POST request through the operation controls.
The response includes a generated id plus a recordedAt timestamp. Its status is CRITICAL_OUTAGE.
- Expand the GET operation at /api/telemetry/region/{region}.
- Activate the operation's request controls.
- Enter Northeast as the region path value.
- Submit the regional request.
You'll see the saved modem-1002 reading in the regional response.
- Expand the GET operation at /api/outages/active.
- Activate the operation's request controls.
- Submit the active-outage request.
You'll see the same CRITICAL_OUTAGE reading in the active-outage response.
- Enter http://localhost:8080/v3/api-docs in the Microsoft Edge address bar.
You'll see generated OpenAPI JSON with paths for all three operations. This is the machine-readable version of the contract shown in Swagger UI.
- Return to Swagger UI with the browser's back control.
That is your API demonstration complete. Every controller path is now visible and callable.
Inspect the saved row in H2
H2 keeps this project's relational data in memory while the application process runs. The console gives you a development-only view of the generated table.
This connection is a little exacting because every character in the configured database address must match. The blank password belongs only to this local development configuration.
- Enter http://localhost:8080/h2-console in the Microsoft Edge address bar.
- Copy the configured database address into the console's connection form by using this value:
jdbc:h2:mem:telemetrydb;DB_CLOSE_ON_EXIT=FALSE
Why use this database address?
The telemetrydb name identifies the in-memory database used by the running application. DB_CLOSE_ON_EXIT=FALSE lets Spring Boot control database shutdown.
- Set the connection username to sa.
- Leave the password value empty.
- Submit the connection form.
You'll see the database schema created from the TelemetryReading entity.
- Select the generated telemetry table in the schema tree.
- Use the console's table inspection control to display its rows.
You'll see the modem-1002 row with Northeast data. Its saved status is CRITICAL_OUTAGE.
You have now traced one API request all the way into relational storage.
Cannot see the saved telemetry row?
- Confirm the Spring Boot process is still running in PowerShell.
- Check that the database address exactly matches the value shown above.
- Leave the password value empty for this local configuration.
- Help me find why my H2 console does not show the telemetry row created through Swagger UI.
Package the tested service
The Maven package phase reruns the tests before creating the distributable artifact. A failing boundary test prevents an unverified JAR from becoming the finished deliverable.
- Return to the PowerShell window running the application.
- Press Ctrl+C to stop the application process.
You'll see the PowerShell prompt again. The application has released port 8080.
Before you package the service, do you expect Maven to create the JAR if either boundary test fails?
- Package the service by running this command:
mvn package
What does this command do?
The mvn package command runs the Maven lifecycle through the package phase. Maven executes the tests before creating the executable JAR.
A test failure stops the build before the artifact is completed. Passing tests allow Maven to write the JAR under target.
You'll see zero failures plus zero errors in the test summary. The build then finishes with BUILD SUCCESS.
- Switch back to IntelliJ IDEA from earlier.
- Refresh the project tree.
- Expand the target folder.
You'll see target/spectrum-telemetry-0.0.1-SNAPSHOT.jar in the project tree. That file is the packaged executable application.
Package build did not succeed?
- Scroll to the first reported test or compilation failure in PowerShell.
- Resolve that first failure before running the package phase again.
- Refresh the IntelliJ IDEA project tree if the build succeeded but the JAR is missing from view.
- Help me diagnose why mvn package does not produce my Spring Boot executable JAR.
You made the service review-ready. A reviewer can now inspect the full result without opening the implementation code.
Secret mission
Defend REST Against GraphQL
Your service works, but its code does not capture why the API uses REST. Write an architecture decision record that defends the current choice. Identify a future dashboard where GraphQL fits better.
Clean Up Your Resources
Clean Up Your Resources
Everything in this project runs locally with no ongoing costs or cloud resources. Choose whether to keep the files, leave the application stopped, or delete the local project.
Resources you used:
- The local Maven project folder containing pom.xml, src/, docs/rest-vs-graphql.md, and target/spectrum-telemetry-0.0.1-SNAPSHOT.jar.
Keep everything running
No action is needed. Choose this option if you plan to review the implementation or extend the telemetry service.
- Keep the local project folder with its source code, tests, architecture decision record, and packaged JAR.
Pause - I'll come back to this later
Your Spring Boot application is already stopped. Choose this option to keep the project dormant while preserving every local file.
- Leave the application stopped.
- Keep the local project folder so you can continue from the same code later.
Delete - I don't want to use this again
Removing the project folder is the only destructive option. The in-memory H2 rows are already gone, so there is no separate database file to remove.
- Use Windows File Explorer to locate the top-level project folder that contains pom.xml.
- Select that project folder.
- Press Shift+Delete to request permanent deletion.
- Approve the confirmation prompt.
- Check the folder's former location in Windows File Explorer.
You should no longer see the project folder. This confirms that every local project artifact is removed.
Nice Work!
Nice Work!
You did it. Your fictional broadband telemetry service now turns modem readings into tested outage decisions.
You've learned how to:
- Build a layered Spring Boot backend from controller through persistence. Expose REST ingestion for modem readings. Add regional telemetry lookup. Add active-outage triage.
- Persist telemetry in H2 through JPA. Use derived repository queries for operational views. Fix threshold precedence so 10.0 becomes CRITICAL_OUTAGE. Keep 22.0 as CONNECTED.
- Prove both SNR boundaries with full-context JUnit tests. Generate an OpenAPI contract. Execute every operation through Swagger UI. Package the tested service as an executable JAR.
- Complete the optional Secret Mission by writing an architecture decision record. Defend REST for the current service. Identify GraphQL as a fit for a future flexible telemetry dashboard.
Ready to quiz yourself?