Standalone Java library that parses official DTB (Deutscher Tennis Bund) ranking PDFs into typed, validated Java models.
Supported disciplines: Herren · Damen · Junioren · Juniorinnen
Lieber laut scheitern als still falsche Daten liefern. (Fail loud rather than silently deliver wrong data.)
Every line that cannot be parsed or validated becomes a ParseIssue — never silently-wrong data.
- Java 25
- Maven (the
mvnwwrapper is included)
Release artifacts are published to Maven Central. SNAPSHOT builds (from main) are available via GitHub Packages.
<dependency>
<groupId>de.hdawg.slice</groupId>
<artifactId>slice</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>For SNAPSHOT builds, add the GitHub Packages repository to your pom.xml:
<repositories>
<repository>
<id>github</id>
<url>https://maven.pkg.github.com/hwesselmann/slice</url>
</repository>
</repositories>./mvnw clean packageRun the test suite:
./mvnw testimport de.hdawg.tennis.slice.Slice;
import api.de.hdawg.tennis.slice.ParseResult;
import api.de.hdawg.tennis.slice.RankingList;
import api.de.hdawg.tennis.slice.RankingEntry;
import validate.de.hdawg.tennis.slice.ParseMode;
import java.nio.file.Path;
// LENIENT (default): unparseable rows are recorded as ParseIssues and skipped
Slice slice = Slice.builder().build();
// STRICT: throws IllegalStateException on the first ERROR-severity issue
Slice strict = Slice.builder().mode(ParseMode.STRICT).build();
ParseResult result = slice.parse(Path.of("DTB-Herren-Rangliste_20260101.pdf"));
RankingList ranking = result.list();
System.out.
println(ranking.discipline()); // HERREN
System.out.
println(ranking.stichtag()); // 2025-12-31
System.out.
println(ranking.validFrom()); // 2026-01-09
System.out.
println(ranking.pointsThreshold()); // 603 (null for juniors)
System.out.
println(ranking.birthYears()); // null (YearRange for juniors)
for(
RankingEntry entry :ranking.
entries()){
System.out.
printf("%d %-20s %-15s %s %s %s %s%n",
entry.rank(),
entry.
lastName(),
entry.
firstName(),
entry.
dtbId(),
entry.
association(),
entry.
club(),
entry.
score()
);
}
// Review any issues (WARNING or ERROR)
result.
issues().
forEach(issue ->
System.err.
printf("[%s] page %d — %s%n",issue.severity(),issue.
page(),issue.
message())
);| Method | Description |
|---|---|
Slice.builder() |
Returns a Builder |
builder.mode(ParseMode) |
LENIENT (default) or STRICT |
slice.parse(Path pdf) |
Parses the PDF; returns ParseResult |
| Field | Type | Description |
|---|---|---|
list() |
RankingList |
The parsed ranking |
issues() |
List<ParseIssue> |
All warnings and errors encountered |
| Field | Type | Description |
|---|---|---|
discipline() |
Discipline |
HERREN, DAMEN, JUNIOREN, JUNIORINNEN |
stichtag() |
LocalDate |
Reference date of the ranking (required; LocalDate.MIN if missing and LENIENT) |
validFrom() |
LocalDate |
Validity date; LocalDate.MIN if absent |
pointsThreshold() |
@Nullable Integer |
Points cutoff (Herren/Damen only; null for juniors) |
birthYears() |
@Nullable YearRange |
Birth-year range (juniors only; null for Herren/Damen) |
entries() |
List<RankingEntry> |
Ranked entries, in ranking order |
| Field | Type | Description |
|---|---|---|
rank() |
int |
Rank (1-based) |
lastName() |
String |
Last name |
firstName() |
String |
First name |
nationality() |
@Nullable String |
IOC/DOSB three-letter code (e.g. GER, SUI) |
dtbId() |
String |
8-digit DTB player ID |
association() |
String |
Regional tennis association (VBD) abbreviation |
club() |
String |
Club name |
score() |
Score |
See Score variants below |
A sealed interface with four implementations:
| Type | Source PDF token | Meaning |
|---|---|---|
Score.Points(BigDecimal value) |
e.g. 1234,5 |
Numeric DTB points |
Score.International.ATP |
(no token — senior Herren without DTB points) | Ranked via ATP |
Score.International.WTA |
(no token — senior Damen without DTB points) | Ranked via WTA |
Score.ProtectedRanking.INSTANCE |
PR |
Protected ranking (keeps position without current points) |
Score.Projected.INSTANCE |
Einst. |
Projected entry (placed despite insufficient wins) |
The number of seniors at the top of a Herren/Damen list who carry International scores instead of DTB points varies each year and is not validated against a fixed threshold.
Use a pattern-matching switch to handle all cases:
String display = switch (entry.score()) {
case Score.Points p -> p.value().toPlainString();
case Score.International i -> i.name(); // "ATP" or "WTA"
case Score.ProtectedRanking r -> "PR";
case Score.Projected p -> "Einst.";
};| Field | Type | Description |
|---|---|---|
severity() |
Severity |
WARNING or ERROR |
page() |
int |
PDF page number (1-based) |
rawLine() |
String |
The raw text of the offending line |
message() |
String |
Human-readable description (German) |
| Mode | ERROR issue | WARNING issue |
|---|---|---|
LENIENT |
Row/field skipped; issue recorded | Issue recorded; entry included |
STRICT |
IllegalStateException thrown immediately |
Issue recorded; entry included |
All packages are @NullMarked (JSpecify). Fields that may be absent are annotated @Nullable — there are no sentinel values.