EV charging decision backend that analyzes UK grid carbon-intensity data and recommends the cleanest charging window in the next 48 hours.
Live frontend preview · Frontend repository
CarCharging is a Spring Boot backend that helps EV owners charge when the grid is cleanest rather than simply when electricity is available.
It integrates with the UK carbon intensity API and exposes a simple REST interface for:
- retrieving forecasted daily energy mix,
- calculating the clean-energy percentage,
- finding the best charging window for a selected duration.
This is a compact but strong portfolio project because it shows:
- external API integration,
- domain-specific data transformation,
- optimization logic over time-series intervals,
- clean REST endpoint design,
- Dockerized deployment.
For EV users, charging at the right time can reduce carbon impact without changing hardware. CarCharging turns raw generation-mix data into an actionable recommendation:
- When should I charge?
- How clean is the grid over the next few days?
- What is the best 1-6 hour charging window in the next 48 hours?
- Best charging window calculation for a user-selected duration
- 48-hour optimization window based on forecasted generation data
- Daily energy mix summaries for the coming days
- Clean energy percentage calculation using selected low-carbon sources
- Simple REST API designed for frontend consumption
- CORS-enabled endpoints for web integration
- Dockerized runtime for easy deployment
- Frontend-ready backend with a linked React/TypeScript UI
| Category | Technologies |
|---|---|
| Language | Java 17 |
| Framework | Spring Boot 3.3.5 |
| API Style | REST |
| External Data Source | NESO / UK Carbon Intensity API |
| Build Tool | Maven |
| Containerization | Docker |
| Frontend Consumer | React + TypeScript frontend repo |
| Testing | Spring Boot Test |
Frontend / Client
|
v
Spring Boot REST Controllers
|
v
Service Layer
| |
| +--> Charging window optimization
|
+-----------------> Carbon intensity API client
|
v
UK generation forecast data
client/
config/
controller/
model/dto/
model/external/
service/
util/
CarbonIntensityClient- fetches external generation dataEnergyService- aggregates daily energy mix and clean-energy percentageChargingService- computes the best charging windowChargingController- exposes charging recommendation endpointEnergyController- exposes energy mix endpoint
src/main/java/org/qualv13/carcharging/
├── client/
│ └── CarbonIntensityClient.java
├── config/
│ ├── RestClientConfig.java
│ └── WebConfig.java
├── controller/
│ ├── ChargingController.java
│ └── EnergyController.java
├── model/
│ ├── dto/
│ │ ├── ChargingWindowDto.java
│ │ └── DailyMixDto.java
│ └── external/
│ ├── CarbonApiResponse.java
│ ├── FuelMix.java
│ └── GenerationData.java
├── service/
│ ├── ChargingService.java
│ └── EnergyService.java
├── util/
│ └── EnergyConstants.java
└── CarChargingApplication.java
- Java 17
- Maven 3.9+
- Docker
git clone https://github.com/qualv13/CarCharging.git
cd CarCharging
mvn spring-boot:runThe application starts as a standard Spring Boot service on port 8080 unless overridden.
git clone https://github.com/qualv13/CarCharging.git
cd CarCharging
docker build -t carcharging .
docker run -p 8080:8080 carcharging- Builds with
maven:3.9.6-eclipse-temurin-17 - Runs on
eclipse-temurin:17-jre-alpine - Exposes port
8080
curl "http://localhost:8080/api/charging/best-window?hours=3"{
"startTime": "2025-12-01T02:30Z",
"endTime": "2025-12-01T05:30Z",
"cleanEnergyPercent": 78.4
}curl "http://localhost:8080/api/energy/mix"[
{
"date": "2025-11-30",
"cleanEnergyPercent": 58.69791666666666,
"dailyMix": {
"hydro": 0.0,
"other": 0.0,
"biomass": 9.78958333333333,
"imports": 10.422916666666666,
"gas": 30.872916666666665,
"solar": 2.3854166666666665,
"coal": 0.0,
"nuclear": 13.022916666666665,
"wind": 33.49999999999999
}
}
]const response = await fetch("http://localhost:8080/api/charging/best-window?hours=2");
const data = await response.json();
console.log(data.startTime, data.endTime, data.cleanEnergyPercent);| Method | Endpoint | Description |
|---|---|---|
| GET | /api/charging/best-window?hours={n} |
Returns the cleanest charging window for 1-6 hours |
| GET | /api/energy/mix |
Returns forecasted daily energy mix and clean-energy percentage |
hoursmust be between 1 and 6- the service evaluates the next 48 hours
- the algorithm uses half-hour forecast slots from the external API
If the requested duration cannot be computed from available future data, the service throws an error.
The charging recommendation logic is simple
- Fetches generation data from today through the next 3 days
- Filters intervals to the next 48 hours
- Converts requested hours into 30-minute slots
- Computes clean-energy percentage per slot
- Uses a sliding window to find the highest average clean-energy period
int slotsNeeded = hours * 2;
for (int i = 0; i < sortedData.size(); i++) {
currentWindowSum += cleanPercentage(sortedData.get(i));
if (i >= slotsNeeded) {
currentWindowSum -= cleanPercentage(sortedData.get(i - slotsNeeded));
}
if (i >= slotsNeeded - 1 && currentWindowSum > maxCleanSum) {
maxCleanSum = currentWindowSum;
bestStartIndex = i - slotsNeeded + 1;
}
}This is a good example of applying a classic sliding-window optimization pattern to a real-world sustainability use case.
The verified repository only includes:
spring.application.name=CarChargingThat means the application is intentionally lightweight and relies primarily on code-level defaults and external API access.
- Default runtime port is Spring Boot's standard
8080 - Endpoints are annotated with
@CrossOrigin(origins = "*") - No database is required
This simplicity is a strength for a focused API utility service.
18 tests, run on every push by CI.
| Class | Covers | Tests |
|---|---|---|
ChargingServiceTest |
the sliding window over half-hourly slots, including ties and short horizons | 10 |
EnergyServiceTest |
daily mix aggregation with a mocked client | 6 |
CarChargingApplicationTests |
the Spring context loads | 1 |
EnergyServiceIntegrationTest |
the real NESO API, end to end | 1 |
mvn test # everything
mvn test -DexcludedGroups=integration # what CI runsThe last one is tagged integration and left out of CI on purpose. It calls
the live NESO Carbon Intensity API, so an outage upstream would fail a build
that has nothing wrong with it. Worth running by hand when the client or the
response shape changes.
mvn clean packagejava -jar target/*.jardocker build -t carcharging .
docker run -p 8080:8080 carcharging- Add response caching for external API calls
- Add OpenAPI/Swagger docs
- Add validation/error response standardization
- Add rate limiting and resilience patterns
- Add CI pipeline and container publishing
- Add observability metrics
Contributions are welcome.
- GitHub profile: qualv13
- Repository: qualv13/CarCharging
- Frontend repo: qualv13/nextjs-render
- Live preview: nextjs-render-fuqh.onrender.com