IBM RPG und Java: Datentypen im Vergleich – präzise Mappings, echte Migrationsrisiken und Best Practices für IBM i
Ein praxisnaher Guide für Modernisierungsteams, RPG-Entwickler, Java-Entwickler, Architektinnen, Tester und neugierige IBM-i-Praktiker.

Praxisguide für IBM-i-Modernisierung und RPG/Java-Integration
RPG und Java kennen Zahlen, Texte, Datumswerte und Wahrheitswerte. Die gefährlichen Fehler entstehen aber selten bei der Syntax. Sie entstehen dort, wo ein technisch mögliches Mapping fachlich falsch ist.
1. Kurzfassung: Der wichtigste Unterschied
RPG-Datentypen sind eng mit klassischer Geschäftsdatenverarbeitung, Db2 for i, extern beschriebenen Dateien, DDS und festen Felddefinitionen verbunden. Java-Datentypen leben in einer objektorientierten, JVM-basierten Welt. Dort unterscheidet man zwischen primitiven Typen wie int, long oder double und Objekttypen wie String, BigDecimal, LocalDate, Instant oder eigenen Records.
Der fachliche Zweck entscheidet über den Java-Typ, nicht die optische Ähnlichkeit zum RPG-Feld. Ein
packed(9:2) kann ein Preis, ein Gewicht, ein Rabatt oder ein Kontosaldo sein. Eine Kundennummer mit führenden Nullen ist fachlich oft ein Schlüssel, keine Zahl. Ein Timestamp kann lokale Buchungszeit oder technischer Ereigniszeitpunkt sein. Genau diese Unterschiede müssen im Mapping sichtbar werden.Die größten Risiken in echten Projekten liegen bei vier Klassikern: Packed Decimal wird zu double, Timestamps werden ohne Zeitzonenbewusstsein übernommen, CCSID-Konvertierungen werden erst im Produktivsystem entdeckt und alphanumerische IDs verlieren führende Nullen. Jeder dieser Fehler ist technisch leicht erklärbar – und fachlich teuer.
2. Warum Datentypen auf IBM i so wichtig sind
Auf IBM i stecken Datentypen selten nur in lokalen Variablen. Sie sind Teil von physischen Dateien, SQL-Tabellen, DDS-Beschreibungen, extern beschriebenen Datenstrukturen, RPG-Programmen, CL-Abläufen, SQL-Prozeduren und Schnittstellen. Ein Feld ist nicht einfach „eine Zahl“, sondern möglicherweise ein Artikelbestand, ein Kundenschlüssel, ein Betrag oder ein historisches Alpha-Feld, dessen führende Nullen seit Jahren Teil des Geschäftsprozesses sind.
Wer Java-Services vor bestehende IBM-i-Daten stellt, muss deshalb zwei Sichten trennen: Was ist technisch möglich und was ist fachlich sinnvoll? Technisch kann man einen numerischen Schlüssel als long lesen. Fachlich kann das trotzdem riskant sein, wenn dieser Schlüssel in Belegen, EDI-Nachrichten oder externen APIs mit führenden Nullen auftritt.
„Es kompiliert“ ist bei Typmigration kein Qualitätskriterium. Entscheidend ist, ob Werte nach Import, Berechnung, JSON-Serialisierung, Datenbank-Update und Rückgabe an RPG noch dieselbe Bedeutung haben.
3. RPG und Java im Direktvergleich
Die folgende Tabelle ist bewusst praxisorientiert. Sie zeigt keine absolute Speicherwahrheit für Java-Objekte, sondern beantwortet die wichtigere Frage: Welcher Java-Typ erhält die fachliche Bedeutung des RPG-Feldes am besten?
| RPG / IBM-i-Konzept | Technisch möglich in Java | Fachlich meist empfohlen | Wichtig bei Migration |
|---|---|---|---|
PACKED, z. B. packed(9:2) |
double, BigDecimal, String |
BigDecimal |
Für kaufmännische Werte niemals pauschal double. Scale und Rundungsmodus explizit testen. |
ZONED |
int, long, BigDecimal, String |
Abhängig vom Zweck | Prüfen, ob gerechnet wird oder ob führende Nullen, Anzeigeformat oder Codecharakter wichtig sind. |
INT(5) |
short, int |
Meist int |
short spart selten spürbar, macht Code aber oft unhandlicher. |
INT(10) |
int |
int |
Grenzwerte und SQL-Mapping testen. |
INT(20) |
long |
long |
Gut für große Zähler und technische Sequenzen; IDs fachlich gesondert prüfen. |
UNS |
int/long mit Unsigned-Methoden oder BigInteger |
Kontextabhängig | Java hat keine eigenen unsigned Primitive im gleichen Sinne. Besonders bei 64 Bit nicht stillschweigend casten. |
FLOAT(4), FLOAT(8) |
float, double |
double für Näherungen, nicht für Geld |
Geeignet für Messwerte, Simulationen, technische Näherungen. Für Centbeträge riskant. |
DATE |
String, java.sql.Date, LocalDate |
LocalDate |
Datum ohne Uhrzeit und ohne Zeitzone. Format nicht mit fachlichem Typ verwechseln. |
TIME |
String, LocalTime |
LocalTime |
Uhrzeit ohne Datum. Nicht als globaler Ereigniszeitpunkt missbrauchen. |
TIMESTAMP |
LocalDateTime, Instant, OffsetDateTime, String |
LocalDateTime für lokale Fachzeit, Instant für technische Events |
Zeitzonenentscheidung dokumentieren. Sonst entstehen Sommerzeit- und Standortfehler. |
CHAR, VARCHAR |
String |
String plus klare Regeln |
Trimmen, Padding, führende Leerzeichen, CCSID und Unicode bewusst behandeln. |
GRAPHIC, UCS-2 |
String |
String |
Java arbeitet mit UTF-16-Code-Units. Zeichenlänge und Byte-Länge sind nicht dasselbe. |
IND / Indicator |
boolean |
boolean oder fachlicher Status |
Ein einzelnes Flag ist okay. Mehrere abhängige Flags sprechen oft für ein enum. |
| Binärdaten, BLOB, Byte-Felder | byte[], ByteBuffer, InputStream |
Kontextabhängig | Nie versehentlich als Text interpretieren. Encoding ist kein Reparaturzauber. |
| Pointer | Referenzen, Handles, Wrapper-Objekte | Neues API- oder Objektmodell | Pointer-Logik nicht nachbauen. Besser fachliche Parameterobjekte und klare Schnittstellen schneiden. |
4. Zahlen: Integer, Packed, Zoned, Float
Bei Zahlen muss zuerst klar sein, welche Art von Zahl gemeint ist: exakter Dezimalwert, technische Ganzzahl, fachlicher Schlüssel oder Näherungswert. Diese Unterscheidung ist wichtiger als die reine Feldlänge.
Integer: technische Ganzzahlen
RPG kennt Integer-Varianten mit unterschiedlicher Größe. Java kennt die primitiven Typen byte, short, int und long. In moderner Java-Geschäftslogik wird für normale Zähler häufig int verwendet, für große Werte long.
| RPG | Java | Typische Verwendung | Testidee |
|---|---|---|---|
int(5) |
short oder int |
Kleine technische Werte. | Minimalwert, Maximalwert und negative Werte prüfen. |
int(10) |
int |
Standard-Ganzzahl für Zähler, Statuswerte, Mengen ohne Dezimalstellen. | SQL-Insert/Update mit Grenzwerten testen. |
int(20) |
long |
Große Zähler, Sequenzen, technische IDs. | JSON-Serialisierung und JavaScript-Consumer prüfen, falls sehr große Werte übertragen werden. |
uns(...) |
long, unsigned Hilfsmethoden, ggf. BigInteger |
Positive Werte ohne Vorzeichen. | Werte oberhalb des signed Bereichs explizit testen. |
Float und Double: technisch möglich, fachlich oft riskant
Fließkommazahlen sind schnell und für Messwerte, Simulationen oder technische Näherungen sinnvoll. Für kaufmännische Werte sind sie meistens die falsche Wahl.
double kann viele Dezimalwerte nicht exakt darstellen. Für kaufmännische Werte ist BigDecimal die robustere Wahl.double sum = 0.1 + 0.2;
System.out.println(sum); // typischerweise nicht exakt 0.3
BigDecimal price = new BigDecimal("0.10");
BigDecimal tax = new BigDecimal("0.20");
System.out.println(price.add(tax)); // exakt 0.30
5. Packed Decimal und BigDecimal
Packed Decimal ist in RPG eine sehr typische Wahl für kaufmännische Werte. Java hat dafür keinen primitiven Datentyp. Die wichtigste Entsprechung ist java.math.BigDecimal.
Für Geldbeträge, Preise, Rabatte, Steuern und exakte Mengen mit Nachkommastellen:
BigDecimal. Für technische Näherungen: double. Für reine Zähler: int oder long. Für IDs mit führenden Nullen: häufig String oder ein eigenes Value Object.**free
dcl-s price packed(9:2) inz(1299.95);
dcl-s quantity packed(5:0) inz(3);
dcl-s total packed(11:2);
total = price * quantity;
import java.math.BigDecimal;
import java.math.RoundingMode;
BigDecimal price = new BigDecimal("1299.95");
BigDecimal quantity = new BigDecimal("3");
BigDecimal total = price.multiply(quantity)
.setScale(2, RoundingMode.HALF_UP);
Die folgenden Tests sind bewusst klein gehalten. Sie sollen nicht das gesamte Fachmodell abdecken, sondern typische Migrationsannahmen absichern: Scale, führende Nullen, Zeitbezug und Unicode-Verhalten.
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.math.BigDecimal;
import org.junit.jupiter.api.Test;
class DecimalMappingTest {
@Test
void keepsScaleForPackedAmount() {
BigDecimal amount = new BigDecimal("9999999.99").setScale(2);
assertEquals(new BigDecimal("9999999.99"), amount);
assertEquals(2, amount.scale());
}
}
new BigDecimal(0.1) übernimmt bereits die Ungenauigkeit des double-Literals. Für konstante Dezimalwerte besser Strings oder BigDecimal.valueOf(...) verwenden – und bei fachlichen Beträgen die Scale explizit prüfen.6. Datum, Uhrzeit und Zeitstempel
RPG kennt eigene Datentypen für Datum, Uhrzeit und Zeitstempel. Java bietet seit Java 8 mit java.time eine moderne API. Wichtig ist: LocalDate, LocalTime, LocalDateTime, Instant und OffsetDateTime sind fachliche Typen. Sie beantworten unterschiedliche Fragen.
| Fachliche Frage | RPG | Java | Einordnung |
|---|---|---|---|
| Welcher Kalendertag? | date |
LocalDate |
Gut für Geburtstag, Lieferdatum, Buchungsdatum. |
| Welche Uhrzeit am Tag? | time |
LocalTime |
Gut für Öffnungszeit oder Schichtbeginn. |
| Welche lokale Fachzeit? | timestamp |
LocalDateTime |
Nur verwenden, wenn Zeitzone fachlich wirklich keine Rolle spielt oder separat festgelegt ist. |
| Welcher technische Zeitpunkt? | timestamp plus Kontext |
Instant |
Ideal für Logs, Events, Audit und technische Synchronisation. |
| Welche Zeit mit Offset im API-Vertrag? | abhängig vom Design | OffsetDateTime |
Nützlich für REST-APIs, wenn der Offset Teil der übertragenen Bedeutung sein soll. |
Ein RPG-
timestamp wird in Java als LocalDateTime übernommen und später als globaler Zeitpunkt interpretiert. Das funktioniert bis zum ersten Standortwechsel, Sommerzeitfall oder externen Consumer in einer anderen Zeitzone.import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
LocalDate invoiceDate = LocalDate.of(2026, 6, 9);
LocalDateTime localBookingTime = LocalDateTime.of(2026, 6, 9, 14, 30);
Instant technicalEventTime = Instant.now();
OffsetDateTime apiTimestamp = OffsetDateTime.now();
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.time.Instant;
import java.time.OffsetDateTime;
import org.junit.jupiter.api.Test;
class TimestampMappingTest {
@Test
void apiTimestampKeepsOffsetInformation() {
OffsetDateTime value = OffsetDateTime.parse("2026-06-09T14:30:00+02:00");
Instant instant = value.toInstant();
assertEquals("2026-06-09T12:30:00Z", instant.toString());
}
}
7. Text, String, Unicode und CCSID
Text ist in Integrationsprojekten oft gefährlicher als Zahlen. RPG- und Db2-for-i-Felder können mit unterschiedlichen CCSIDs, fester oder variabler Länge, Padding und historischen EBCDIC-Kontexten arbeiten. Java arbeitet mit String als Unicode-orientiertem Objekttyp.
Wenn Umlaute, Sonderzeichen oder externe JSON/XML-Schnittstellen im Spiel sind, muss der komplette Weg getestet werden: Db2 for i, JDBC/JTOpen, Job-CCSID, API-Encoding und Rückweg nach RPG. Ein Test mit
ABC123 beweist hier fast nichts.| RPG / IBM i | Java | Technisch möglich | Fachlich zu klären |
|---|---|---|---|
char(n) |
String |
Direktes Mapping auf Text. | Feste Länge, Padding, Trimming und führende Leerzeichen. |
varchar(n) |
String |
Variable Länge. | Parameterübergabe, maximale Länge und Validierung. |
| Numerisch aussehende ID | String oder Value Object |
long wäre oft möglich. |
Führende Nullen, Prüfziffern, externe Darstellung. |
graphic |
String |
Textmapping. | Double-Byte-Zeichen und Konvertierung testen. |
ucs2 |
String |
Unicode-nahe Speicherung. | Nicht blind mit moderner Unicode-Code-Point-Logik gleichsetzen. |
String text = "tiny-tool.de 🚀";
int utf16Units = text.length();
int codePoints = text.codePointCount(0, text.length());
System.out.println("UTF-16 code units: " + utf16Units);
System.out.println("Unicode code points: " + codePoints);
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
class CustomerNumberTest {
@Test
void keepsLeadingZeros() {
CustomerNumber number = new CustomerNumber("000123");
assertEquals("000123", number.value());
}
}
8. Binärdaten, Pointer und Referenzen
Binärdaten sollten in Java als Binärdaten behandelt werden: byte[], ByteBuffer, InputStream oder je nach Framework geeignete Streaming-Typen. Der klassische Fehler besteht darin, Binärdaten versehentlich durch Textkonvertierung zu schicken.
Pointer sind ein anderes Thema. RPG kann mit Pointern arbeiten, Java abstrahiert Speicherzugriffe über Referenzen und Objektmodelle. Eine direkte mechanische Übersetzung ist meistens kein gutes Ziel.
Pointer-Logik ist ein gutes Warnsignal. Dort lohnt sich fast immer ein kleiner Architektur-Schnitt: klare DTOs, saubere Parameterobjekte und eine explizite Schnittstelle statt „Pointer-Denken in Java nachbauen“.
9. Typische Migrationsfallen
Die folgenden Fehler sieht man in RPG-Java-Projekten besonders häufig. Sie entstehen gerade dann, wenn Teams „nur schnell“ ein Feld durchreichen wollen.
| Fehler | Technisch möglich? | Warum fachlich riskant? | Besser |
|---|---|---|---|
packed pauschal zu double |
Ja | Rundungs- und Vergleichsfehler bei Dezimalwerten. | BigDecimal, Scale und Rundungsmodus testen. |
| Führende Nullen als Zahl behandeln | Ja | 000123 wird zu 123; Belege, APIs oder EDI können brechen. |
String oder CustomerNumber-Value-Object. |
LocalDateTime für technische Events |
Ja | Kein eindeutiger Zeitpunkt ohne Zone oder Offset. | Instant oder OffsetDateTime. |
| CCSID ignorieren | Leider ja | Umlaute, Sonderzeichen und externe Schnittstellen brechen erst spät. | End-to-End-Encoding-Tests mit echten Beispieldaten. |
| RPG-Feldlänge direkt als Java-String-Limit nehmen | Teilweise | Byte-Länge, UTF-16-Code-Units, Code Points und fachliche Länge unterscheiden sich. | Technische Länge und fachliche Validierung getrennt dokumentieren. |
| Indicator anonym übernehmen | Ja | flag1, flag2 und flag3 erklären keine Geschäftsregel. |
Sprechende Booleans oder enum für Statusmodell. |
Zoned Decimal automatisch als int |
Ja | Nachkommastellen, führende Nullen oder Darstellungslogik gehen verloren. | Vorher klären: Zahl, Betrag, Code oder Text? |
10. Value Objects und Domain-Modellierung
Java-Modernisierung ist nicht nur Typmapping. Sie ist auch die Chance, fachliche Regeln sichtbar zu machen. Ein Betrag ist nicht einfach irgendein BigDecimal. Eine Kundennummer ist nicht einfach ein String. Kleine Value Objects verhindern, dass dieselbe Validierungslogik an zehn Stellen auftaucht – oder an neun Stellen fehlt.
import java.math.BigDecimal;
import java.math.RoundingMode;
public record Money(BigDecimal amount) {
public Money {
if (amount == null) {
throw new IllegalArgumentException("amount must not be null");
}
amount = amount.setScale(2, RoundingMode.HALF_UP);
}
public Money add(Money other) {
return new Money(this.amount.add(other.amount));
}
}
public record CustomerNumber(String value) {
public CustomerNumber {
if (value == null || !value.matches("\\d{6}")) {
throw new IllegalArgumentException("customer number must have exactly 6 digits");
}
}
}
public enum OrderStatus {
OPEN,
RELEASED,
SHIPPED,
CANCELLED
}
Nicht jedes Feld braucht ein eigenes Objekt. Aber Werte mit Geschäftsregeln, externer Darstellung oder hohem Fehlerrisiko sind gute Kandidaten: Geld, Kundennummern, Artikelnummern, Mengen, Währungen, Statuswerte und technische Ereigniszeitpunkte.
11. Best Practices und Testideen
- Fachliche Bedeutung zuerst klären: Preis, Menge, Schlüssel, Status, Datum und Text sind unterschiedliche Dinge – auch wenn sie technisch ähnlich aussehen.
- Dezimalwerte konsequent behandeln: Für Geld, Preise und kaufmännische Werte in Java bevorzugt
BigDecimalverwenden. - Skalierung dokumentieren: Bei
packed(9:2)ist die:2fachlich wichtig, nicht nur Dekoration. - Datum und Zeit sauber modellieren:
LocalDatefür Datum,Instantfür technische Zeitpunkte,OffsetDateTimefür API-Zeitpunkte mit Offset. - CCSID und Unicode testen: Nicht nur mit
ABC123, sondern mitÄÖÜ,ß, Akzenten, Sonderzeichen und echten Produktionsbeispielen. - IDs nicht blind numerisch machen: Wenn führende Nullen, Prüfziffern oder externe Darstellung wichtig sind, ist
Stringoder ein Value Object oft richtiger. - Primitive Obsession vermeiden: Wichtige Fachwerte dürfen eigene Typen oder Records bekommen.
- Mapping zentralisieren: RPG/Db2-zu-Java-Konvertierungen gehören nicht verstreut in Controller, Services und SQL-Hilfsklassen.
- Tests mit Grenzwerten bauen: Maximalwerte, Nullwerte, negative Werte, Rundungen, leere Strings, Padding, ungültige Daten und Zeitzonenfälle testen.
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.math.BigDecimal;
import org.junit.jupiter.api.Test;
class MoneyTest {
@Test
void normalizesScaleToTwoDecimals() {
Money money = new Money(new BigDecimal("12.3"));
assertEquals(new BigDecimal("12.30"), money.amount());
}
@Test
void addsAmountsWithStableScale() {
Money result = new Money(new BigDecimal("0.10"))
.add(new Money(new BigDecimal("0.20")));
assertEquals(new BigDecimal("0.30"), result.amount());
assertEquals(2, result.amount().scale());
}
}
Ein guter Migrationstest enthält nicht nur Durchschnittswerte, sondern absichtlich unangenehme Daten: maximale Packed-Werte, negative Beträge,
0.00, 0.10, führende Nullen, leere und blankgefüllte Texte, Umlaute, Sonderzeichen, alte Statuswerte, ungültige Daten und Timestamps rund um Sommerzeitwechsel.Glossar
Kurze Einordnung zentraler Begriffe aus RPG, Java und IBM-i-Modernisierung.
- Packed Decimal
- Ein kompakter Dezimaltyp in RPG, häufig für kaufmännische Werte mit definierter Anzahl von Stellen und Nachkommastellen.
- BigDecimal
- Java-Klasse für unveränderliche Dezimalzahlen mit beliebiger Präzision. Besonders wichtig für Geldbeträge und exakte Dezimalwerte.
- Zoned Decimal
- Dezimaldarstellung, bei der Ziffern zoniert gespeichert werden. Fachlich kann daraus eine Zahl, ein Betrag, ein Code oder ein formatierter Wert entstehen.
- CCSID
- Coded Character Set Identifier. Auf IBM i wichtig für Zeichensatzinterpretation, Konvertierung und korrekte Darstellung von Text.
- UTF-16-Code-Unit
- 16-Bit-Einheit in der Java-Zeichendarstellung. Ein sichtbares Unicode-Zeichen kann aus einer oder mehreren solchen Einheiten bestehen.
- LocalDate
- Java-Typ für ein Datum ohne Uhrzeit und ohne Zeitzone, etwa ein Lieferdatum oder Geburtsdatum.
- Instant
- Java-Typ für einen eindeutigen technischen Zeitpunkt auf der Zeitachse. Besonders geeignet für Logs, Events und technische Zeitstempel.
- Record
- Java-Sprachkonstrukt für kompakte, unveränderliche Datenklassen. Praktisch für kleine Value Objects und DTOs.
- Primitive Type
- Ein eingebauter Java-Basistyp wie
int,long,doubleoderboolean. - Value Object
- Ein kleines fachliches Objekt, das einen Wert mit Regeln kapselt, zum Beispiel Geldbetrag, Kundennummer, Artikelnummer oder Status.
Fazit
RPG und Java können hervorragend zusammenarbeiten – aber Datentypen müssen bewusst übersetzt werden. Die beste Entsprechung ist nicht immer der ähnlichste technische Typ, sondern der Typ, der die fachliche Bedeutung erhält.
Für IBM-i-Modernisierung heißt das: Packed Decimal gehört meistens zu BigDecimal, echte Datumswerte zu LocalDate, technische Zeitpunkte eher zu Instant, Texte zu String mit sauberem CCSID- und Unicode-Bewusstsein und IDs mit führenden Nullen nicht automatisch zu Zahlen. Wer diese Entscheidungen sauber trifft, vermeidet Rundungsfehler, Zeichensatzdramen, kaputte Schlüssel und instabile Schnittstellen.
Quellen und weiterführende Dokumentation
- IBM Documentation: Determining equivalent SQL and ILE RPG data types
- IBM Documentation: Numbers / decimal, packed and zoned data types
- IBM Documentation: Date, Time and Timestamp data types
- IBM Documentation: Variable-Length Character, Graphic and UCS-2 formats
- IBM Documentation: Character Data Type
- IBM Support: IBM Toolbox for Java JDBC Driver connection properties
- Oracle Java Tutorials: Primitive Data Types
- Oracle Java SE 21 API: BigDecimal
- Oracle Java SE 21 API: java.time Package Summary
- Oracle Java SE 21 API: Character and Unicode code points
- Oracle Java Language Guide: Records



tiny-tool.de