Java data model, Spring MVC API mappings and RestTemplate clients for OCPI 2.2.1.
This library exists mostly for better developer experience and ergonomics.
Many OCPI projects already exist, but most fall into one of two groups:
- Attempts at an OpenAPI specification.
- Direct language-specific models and implementations.
Both approaches can be useful. Both also created friction here.
Even before asking whether an OpenAPI document is correct and complete with respect to the official OCPI specification, the generated Java code tends to be noisy and verbose.
Example: OCPI defines request headers that appear on many endpoints. Generated controller interfaces usually expose each header as a separate method parameter on every endpoint. The result is cumbersome, repetitive, and hard to scan.
The same problem appears in the core domain models. Generated Java models are often flat and repetitive, with little code sharing, inheritance, or hand-tuned structure. It proved hard to configure the generator enough to make the generated core compact and maintainable.
Existing Java implementations often make questionable data modeling choices or strong assumptions about application structure. Some feel more like frameworks than model/API libraries: useful when their assumptions match your application, awkward when they do not.
Validation annotations and validation boundary decisions can be just as problematic, especially when PATCH semantics, nested objects, and required OCPI strings are modeled too loosely or too aggressively.
This project stays closer to a library: compact OCPI models and endpoint contracts, while application behavior and module implementation choices stay with the consuming Spring Boot application.
- Dense, compact data modeling. To keep the Java and wire representations close together, models intentionally use snake_case property names that match OCPI JSON fields instead of standard Java camelCase names. Lombok hides the boilerplate.
- OCPI request headers are bundled into one POJO and injected into endpoint methods.
- OCPI pagination parameters are bundled into one POJO and injected into endpoint methods.
- Each OCPI module exposes Java interfaces for the relevant API endpoints. Applications choose which role/module interfaces to implement.
The goal is to keep software code plumbing out of application code where a compact library abstraction can carry it instead. For more detail on these tradeoffs, see README-DESIGN.md.
For example, Tariff is modeled like this:
@Data
public class Tariff {
@NotEmpty @Size(max = 2) String country_code;
@NotEmpty @Size(max = 3) String party_id;
@NotEmpty @Size(max = 36) String id;
@NotEmpty @Size(max = 3) String currency;
TariffType type;
@Valid List<DisplayText> tariff_alt_text;
@Size(max = 255) String tariff_alt_url;
@Valid Price min_price;
@Valid Price max_price;
@NotEmpty @Valid List<TariffElement> elements;
Instant start_date_time;
Instant end_date_time;
@Valid EnergyMix energy_mix;
@NotNull Instant last_updated;
}Data model classes can be found in package com.github.stevecommunity.ocpi.v221.model.
The sender interface for the Tariffs module stays similarly compact:
@SecurityRequirement(name = OCPI_AUTH_SCHEME)
@RequestMapping(value = OcpiApi.TARIFFS_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
public interface TariffsSenderApi extends OcpiApi.Tariffs.Sender {
@GetMapping
default ResponseEntity<OcpiResponse<List<Tariff>>> getTariffs(
@Parameter(hidden = true) @Valid OcpiRequestHeaders headers,
@Valid @ParameterObject OcpiRequestParameters params
) {
throw new RuntimeException("Not implemented");
}
}An application can implement only the API it needs:
@RestController
public class TariffSenderController implements TariffsSenderApi {
@Override
public ResponseEntity<OcpiResponse<List<Tariff>>> getTariffs(OcpiRequestHeaders headers,
OcpiRequestParameters params) {
// TODO
}
}API interfaces can be found in package com.github.stevecommunity.ocpi.v221.web.api.
The client side uses one compact OcpiClient around RestTemplate.
It wraps OCPI authorization, request/correlation ID generation, routing headers, response unwrapping, and pagination/query plumbing.
Client methods expect the full module endpoint root discovered through OCPI versions/version-details, not just the remote system root.
Callback-style methods that receive a URL from the remote party expect that complete URL directly.
RestTemplate restTemplate = new RestTemplate();
OcpiClient client = OcpiClientBuilder
.create(restTemplate, "my-ocpi-token")
.from("DE", "MSP")
.to("DE", "CPO")
.build();
Cdr cdr = client.getCdr("https://cpo.example.com/ocpi/2.2.1/cdrs", "cdr-123");Client classes can be found in package com.github.stevecommunity.ocpi.v221.web.client.
This library does not try to protect implementors from every possible mistake. It provides compact, accurate building blocks for OCPI models and endpoint contracts, while leaving application-level decisions, persistence, authorization, business rules, and operational safeguards to the consuming application.