While IBM i modernizations promise that decades-old business logic can easily communicate with new Java-based microservices, implementing this can pose several challenges. The challenges mainly lie in the interface layer, where data must be converted from one type to another.
By Joydip Kanjilal
This isn't just about simple syntax errors; it also involves complex, subtle type-system problems that arise from fundamental differences in how RPG represents data versus how Java and other modern standards, such as JSON, represent it.
In this article, we discuss seven common type-mapping errors, along with practical tips to ensure your integration works as expected. An RPG and Java code example will accompany each trap.
Data Type Mismatch: Why it Fails
Data type incompatibility is a major challenge in modernizing IBM i applications when migrating legacy RPG modules to Java. This is because RPG behaves differently than object-oriented languages like Java when working with binary memory layouts, decimals, and text indicators.
Java supports a wide variety of data types, such as BigDecimal, String, and LocalDateTime. JSON can be used to represent objects, arrays, strings, numbers, booleans, and null. Each field crossing the border has its own rule. When accessing Db2 for i via JDBC, the JTOpen JDBC driver reads columns of tables and converts values for you. Values of DECIMAL and NUMERIC columns come as BigDecimal, values of CHAR and VARCHAR columns come as Java strings decoded from the column CCSID, and values of DATE and TIMESTAMP columns come as java.sql.Date and java.sql.Timestamp.
With the help of JTOpen ProgramCall to an RPG program, you can specify each parameter yourself. You can use classes like AS400PackedDecimal, AS400ZonedDecimal, and AS400Text, along with the parameter lengths.
Trap 1: Precision problem – Decimal or Numeric Data to BigDecimal in Java
It is a common mistake to assume that standard floating-point types or simple string parsing will preserve financial data accuracy. The way RPG handles decimals - which are fixed-scale integer representations by nature - is much stricter compared to the numeric types available in Java. Hence, simply converting a decimal number to a float or double in Java can introduce rounding errors that didn't exist on IBM i.
Let's look at an example. When you're using Java to connect to your Db2 for i using JDBC technology, the JDBC driver will automatically handle the conversion for DECIMAL and NUMERIC fields. So you can use the getBigDecimal() method without converting the value to a string first, and then map it later.
You should not pass data about a money type through an intermediate integer type. If your RPG field is text, treat it as a string and parse it into BigDecimal in Java immediately after you receive the data from RPG.
If Java calls an RPG program using JTOpen, the conversion is done by a class like AS400PackedDecimal or AS400ZonedDecimal, which returns a BigDecimal directly.
AS400PackedDecimal packed = new AS400PackedDecimal(9, 2);
BigDecimal amount = (BigDecimal)
packed.toObject(parm.getOutputData());
A string is a good choice if the original data was already in text format, i.e., when sending the data as part of a JSON message as mentioned in Trap 6.
Let us illustrate this with a code example. Consider the following code snippet that adds the value 0.10 in a loop inside a double. The sum of the added values is 0.9999999999999999.
class Main {
public static void main(String[] args) {
double d = 0.0;
for (int i = 0; i < 10; i++)
d += 0.10;
System.out.println(d);
}
}
However, if you do the same in RPG, the value will be 1.00. If you run the same program using BigDecimal as shown in the code snippet given below, the output will be 1.00.
import java.math.BigDecimal;
class Main {
public static void main(String[] args) {
BigDecimal bigDecimal = BigDecimal.ZERO;
for (int i = 0; i < 10; i++)
bigDecimal = bigDecimal.add(new BigDecimal("0.10"));
System.out.println(bigDecimal);
}
}
The following RPG program adds 0.10 ten times in a packed decimal field. The code snippet demonstrates that packed decimal arithmetic is exact base-10 arithmetic, similar to BigDecimal arithmetic. Note that this program does not demonstrate a mapping between the two types.
dcl-s bigDecimal packed(31:2) inz(0);
dcl-s i int(10);
for i = 0 to 9;
bigDecimal += 0.10;
endfor;
dsply %char(bigDecimal);
*inlr = *on;
return;
When you execute the preceding piece of code, it will print 1.00.
You should apply the following rules before you send a BigDecimal back to RPG:
- Round the value to the scale of the target field using a specific rounding mode
- You should also reject the value when it would require more than p-s integer digits.
The following piece of code shows how this can be achieved:
import java.math.BigDecimal;
import java.math.RoundingMode;
static BigDecimal fitDecimal(BigDecimal v, int p, int s,
RoundingMode mode) {
BigDecimal r = v.setScale(s, mode);
if (r.precision() - r.scale() > p - s) {
throw new ArithmeticException
("Error occurred: overflow for decimal value(" + p + "," + s + "): " + v);
}
return r;
}
Trap 2: Semantic Problem – Numeric Identifiers vs Business Identifiers
RPG programs often represent data as a string even though it is semantically numeric (such as an account number, i.e., 012345612345, or a product code such as 0012345. When RPG programs transfer this data to Java or JSON, it is often represented as a general string.
RPG programs assume the data is space-padded and contains leading zeros because of its fixed-width format. When consumers view the data, they see 12345612345 and 12345, respectively.
Numeric keys don't all need this. A surrogate key, for example, an order sequence number that has no formatting restrictions, can be safely mapped as an "int" or "long" value and then checked against a range. For business identifier keys where the key format matters to people or other systems (e.g., fixed-width, leading zero, or check digit), map them as strings.
For those identifier fields, you should create a fixed-width string representation of each field'sRPG value prior to the value leaving the IBM i. You can then define that field as a string in your OpenAPI documentation and use a pattern for it (e.g., "^[0-9]{7}$") to indicate that the field has a particular format.
When you need to provide a system with a numeric identifier, such as an index key, make sure to verify the number of digits in the identifier, as well as the types of digits contained in the identifier, both when sending the identifier to the system and when receiving the identifier back from the system.
The numeric field in RPG should be transformed into a zero-padded string of the specified length before it leaves IBM i.
Refer to the following piece of RPG code:
dcl-s custNo zoned(7:0) inz(1234);
dcl-s custId char(7);
custId = %editc(custNo : 'X');
In the preceding piece of code, custNo stores the number 1234. When you convert this number to text using %editc, the leading zeros will be preserved. So, custId will be 0001234.
Note that if your source identifier is a character representation of 1234 without any leading zeros, the created identifier will not be the same as the original identifier. Hence, you should use this approach only when the zero-filled representation of the identifier is the correct form.
The following piece of code shows what happens when the same identifier is converted to a number:
public class Main {
public static void main(String[] args) {
String custId = "0001234"; // assume that this has been sent by RPG
long asNumber = Long.parseLong(custId);
String rebuilt = String.format("%07d", asNumber);
System.out.println(custId); // this will print 0001234
System.out.println(asNumber); // this will print 1234
System.out.println(rebuilt); // and, this will print 0001234
}
}
The String object contains 0001234, while the Long variable contains the value 1234. Note that the String.format method adds the zeros back into the String only because it knows that the width of the String should be 7. If you use a consumer without knowing the width of the String, or if a JSON parser reads 1234 as a number, then the original format of the String is lost.
Trap 3: Date and Timestamp data
In RPG, date and timestamp data, and the Db2 for i DATE/TIMESTAMP columns, do not contain time zone information. The time zone is determined by the location where the row was created or the application that created it.
RPG takes advantage of specific internal formats for both date and timestamp data. These formats depend heavily on the system's locale and the user's time zone, which may vary. In Java, you should use either ZonedDateTime or Instant, since raw formatted date strings often cause unintentional time zone shifts and "off-by-one-day" errors in calculations.
To solve this, you should always avoid sending raw formatted strings of dates/times and instead use the ISO 8601 format (e.g., YYYY-MM-DDThh:mm:ssZ) on all systems. Hence, once you have retrieved a date/time from RPG, you should convert it to either a java.time.Instant or ZonedDateTime and map the UTC offset properly when doing it.
Assume that an order arrives at midnight (12:00 am) on January 1st, 2026, and it is saved to a DB2 database in Chicago, Illinois. The DB2 system will store the date and time as 2026-01-01-00.00.00.000000.
Now, assume that you also have a Java application that operates in UTC and reads this value as the instant 2026-01-01T00:00Z. Interestingly, a reporting tool converts this instant back to Chicago local time, but it reports it as occurring at 6:00 PM on December 31st, 2025. So, when you generate the year-end revenue report, it will contain an error because the order was placed the year before.
To fix the issue, you should read the timestamp as a LocalDateTime and apply the zone yourself, as shown in the code snippet below.
import java.time.DateTimeException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.List;
static Instant toInstant(LocalDateTime local, ZoneId zone) {
List<ZoneOffset> offsets =
zone.getRules().getValidOffsets(local);
if (offsets.isEmpty()) {
throw new DateTimeException(local + " does not exist in " + zone);
}
if (offsets.size() > 1) {
throw new DateTimeException(local + " is ambiguous in " + zone);
}
return local.atOffset(offsets.get(0)).toInstant();
}
ZoneId plantZone = ZoneId.of("America/Chicago");
// 2026-01-01T00:00, exactly as stored
LocalDateTime local = rs.getTimestamp("ORDTS").toLocalDateTime();
// 2026-01-01T06:00:00Z, the true instant
Instant orderedAt = toInstant(local, plantZone);
Because of daylight saving time, two situations can occur. In one case, when clocks in Chicago fall back on November 1, 2026, the hour from 1:00 AM until 2:00 AM occurs twice. Similarly, when clocks turn forward, 2:30 AM will never exist. Java handles both situations without throwing exceptions. In the event of an overlap, atZone() chooses the first offset. In the event of a gap, atZone() moves the time forward by the amount of time that was lost due to the gap. Because neither event throws an exception, the code snippet above checks getValidOffsets() first; if an organization needs to differentiate between these two instants, it should either store UTC or store the offset adjacent to the timestamp.
When adding new columns, you should record UTC on the IBM i side. The CURRENT TIMEZONE special register provides the offset of the job between local time and UTC. Db2 for i can derive it from the system value called QUTCOFFSET.
dcl-s orderTsUtc timestamp;
exec sql set :orderTsUtc = current timestamp - current timezone;
if sqlcode <> 0;
// handle the SQL error
endif;
Trap 4: Character Encoding and CCSID
Java and IBM i store text differently. Although Java strings are Unicode, IBM i systems primarily use character set IDs (CCSID), including but not limited to EBCDIC, UTF-8, and other localized variants.
Developers often think converting the string field will fix the encoding problem, but they forget the data structure may already contain multi-byte characters that were incorrectly interpreted or truncated.
To fix this issue, specify the CCSID of every character field, then determine when the one-time conversion occurs. RPG will automatically convert when assigning to fields having different CCSIDs. Similarly, the JDBC driver will automatically convert from the column CCSID to the Java String. For JTOpen ProgramCall, you can perform the conversion usually via AS400Text with the CCSID of the receiving field. You should not use getBytes() or new String() for a value that the driver or RPG has previously converted, as a subsequent conversion may result in damage to the value.
The JDBC driver converts each CHAR and VARCHAR column from the column CCSID to a Java String, so never call getBytes() and new String() on the result again. With a JTOpen ProgramCall, AS400Text converts parameter bytes with the CCSID you pass, so pass the CCSID of the RPG parameter explicitly instead of relying on a default.
When your application requires support for non-English character sets, i.e., Chinese, Arabic, etc., make sure you use an explicit UTF-8 encoding on each of the fields that cross the boundaries between systems.
The following piece of code demonstrates an RPG program that converts a UTF-16 value to a CCSID 37 field:
ctl-opt ccsidcvt(*excp : *list);
dcl-s nameIn varucs2(50) ccsid(1200)
inz(u'004A0061006E00E90020674E'); // 'Jané ' plus U+674E
dcl-s nameOut char(50) ccsid(37);
dcl-s rc int(10) inz(0);
monitor;
nameOut = nameIn; //This is an implicit conversion: CCSID 1200 to 37
on-error *program;
// U+674E has no CCSID 37 equivalent, so we should reject the value
nameOut = *blanks;
rc = -1;
endmon;
Here is the corresponding Java code:
import java.nio.charset.Charset;
String name = "Jané 李";
Charset ccsid37 = Charset.forName("IBM037");
if (!ccsid37.newEncoder().canEncode(name)) {
throw new IllegalArgumentException("Name has characters outside CCSID 37: " + name);
}
In the preceding piece of Java code, the illegal value is rejected before the call to ensure that it never reaches RPG.
Trap 5: The Truncation Problem
Fixed-length fields in RPG, like CHAR(20), always use the full specified length, and the unused part is padded with blank spaces. Note that this is called padding and not truncation, nor do fixed-length RPG character fields usually end with a null character. It should also be noted that JSON serialization will not strip off the padding on its own. Therefore, if you read a Java String from a CHAR column, the trailing blanks will still be there. Truncation is another issue altogether. Truncation occurs when a value exceeds the maximum size of the field that is being populated.
The solution is to treat each fixed-size field as a variable-size string of the maximum possible length. The stripTrailing() method removes all trailing Unicode whitespace (including tabs and line breaks) and not just the blanks RPG added; therefore, you should only use stripTrailing() when your contract states that trailing whitespace will never have meaning. When you want to remove only blanks, use replaceAll(" +$", ""). Trim() and Strip() are both unsuitable for use on business data, since leading blanks may have meaning in legacy data.
In RPG, a char(50) field always stores 50 characters. So, if you store the text "Joydip", it pads the field with 44 blanks. However, in Java, the text "Joydip" and the text "Joydip" plus 44 blanks are different strings.
In the following code, the comparison ignores trailing blanks, so when it finds a match, %TRIMR assigns the name Joydip, without the padding, to nameOut. Hence, the following RPG code prints the name Joydip because it ignores trailing blanks.
dcl-s custName char(50) inz('Joydip');
dcl-s nameOut varchar(50);
if custName = 'Joydip';
nameOut = %trimr(custName);
endif;
Now, consider the following piece of Java code:
String raw = rs.getString("CUSTNAME");
boolean match = raw.equals("Joydip");
String name = raw.stripTrailing();
boolean fixed = name.equals("Joydip");
In the code above, match will be false because the trailing spaces haven't been stripped. Once you've stripped the trailing spaces, the value of the fixed variable will be true.
Trap 6: JSON Serialization
Modern APIs are based on JSON, i.e., they use JSON to transmit data. If an RPG field is specified as a required numeric or character field (such as char (10)), the middleware cannot view this field as null unless the application logic specifically states that "unknown" status exists. Also, bad mappings will permit a Java serialization library (such as Jackson) to convert data types (e.g., converting an empty string "" into a numeric value 0).
The four states of JSON blur in RPG and SQL: a property is not available, a property value is explicitly null, a string is empty, and a value is zero. However, each state has a different meaning, and the API contract must explain which ones are allowed for each field. A field in RPG cannot be null, while a column in a Db2 database can be; however, the SQL null indicator does not convert to JSON null, so your code must handle this mapping.
To solve this issue, you should do the following:
- Handle Nullability: In your integration layer, map RPG required fields to non-nullable data types in Java, and only provide a way for the field to contain a null value when there are business rules that permit it.
- Avoid Coercion: When you create your JSON schema contract (OpenAPI), you should define the types as strictly as possible to avoid coercion of generic types. For example, if there is a possibility that a numeric field will not exist, instead of allowing for a null value, you can use an Optional wrapper (java.util.Optional) in Java.
The following RPG program reads an optionally null column. Since the null indicator variable (discountNi) receives -1 when Db2 returns NULL, the RPG program will pass the value plus an indicator to Java as opposed to 0.00. If the null indicator variable were not used, then the SELECT statement would fail with SQLCODE -305.
dcl-pi *n;
custNo zoned(7:0) const;
discount packed(5:2);
discountNull ind;
rc int(10);
end-pi;
dcl-s discountNi int(5);
exec sql
select discount into :discount :discountNi
from custmast
where custno = :custNo;
rc = sqlcode;
select;
when sqlcode = 0;
// -1 means SQL NULL; discount is undefined in that case
discountNull = (discountNi = -1);
other;
// Here, 100 relates to customer not found, anything else is a SQL error.
// Neither is a NULL discount, so the caller checks rc.
discountNull = *off;
endsl;
return;
The following code snippet shows how you can use Java to turn the RPG results into the outgoing value.
if (rc == 100) {
throw new NoSuchElementException("Customer not found: " + custId);
}
if (rc != 0) {
throw new IllegalStateException("SQLCODE " + rc + " reading customer " + custId);
}
BigDecimal discount = discountNull ? null : discountValue;
Customer customer = new Customer(custId, balance, discount);
The following Java code sends money as a JSON string, allows missing values to be sent as null, and rejects empty strings and missing properties.
import java.math.BigDecimal;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.cfg.CoercionAction;
import com.fasterxml.jackson.databind.cfg.CoercionInputShape;
public class CustomerJson {
record Customer(
String custId,
@JsonFormat(shape = JsonFormat.Shape.STRING) BigDecimal balance,
@JsonFormat(shape = JsonFormat.Shape.STRING) BigDecimal discount) {
}
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
mapper.coercionConfigDefaults()
.setCoercion(CoercionInputShape.EmptyString, CoercionAction.Fail);
mapper.enable(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES);
// Java to JSON
Customer customer =
new Customer("0001234", new BigDecimal("1234.50"), null);
System.out.println(mapper.writeValueAsString(customer));
// JSON to Java: zero, explicit null, empty string, missing property
String[] inputs = {
"{\"custId\":\"0001234\",\"balance\":\"1234.50\",\"discount\":\"0.00\"}",
"{\"custId\":\"0001234\",\"balance\":\"1234.50\",\"discount\":null}",
"{\"custId\":\"0001234\",\"balance\":\"1234.50\",\"discount\":\"\"}",
"{\"custId\":\"0001234\",\"balance\":\"1234.50\"}"
};
for (String json : inputs) {
try {
System.out.println(mapper.readValue(json, Customer.class));
} catch (JsonProcessingException e) {
System.out.println(e.getClass().getSimpleName() + ": "
+ e.getOriginalMessage());
}
}
}
}
In the preceding piece of code, the customer ID will contain leading zeros, and the balance will be stored as the string "1234.50". Note that both zero and an explicit null are valid, but they have different values. An empty string and a missing property are both invalid. The customer ID will retain its leading zero, and the balance value is passed as the string "1234.50". FAIL_ON_MISSING_CREATOR_PROPERTIES is used on constructor-based types (records). To enforce required properties for classes with setters, use a validator.
Trap 7: Skipping Round-Trip Testing
Often, developers only perform a single-direction test of data mapping: RPG -> inadequate Java. To ensure complete integration, perform a round-trip test by sending data from System A to System B, then verifying that when System B sends the confirmation or modified data back to System A, the original data formatting is preserved.
To solve this, you can implement a required round-trip test harness which should:
- Use the canonical test data (Gold Standard).
- Send the data via JDBC or JTOpen
- Read the data and validate if the original data and the data returned are the same.
You should convert the JSON/Java object that you have received into a format that is compatible with RPG and run all validation checks on each step of the process so that you do not lose or damage any data when converting types.
The following JUnit 5 test demonstrates a round trip that needs no IBM i; instead, it converts a name into CCSID 37, converts the bytes back, and compares the result with the original. Next, we convert those bytes back to Java. Finally, we compare the original name to the converted name. To make this a realistic JUnit 5 test, you should replace the conversion calls with a call to your RPG program using JTOpen ProgramCall, or a JDBC stored procedure call.
The following code snippet shows a JUnit test that demonstrates a character-conversion round trip that needs no IBM i. While this piece of code provides a quick way to demonstrate character loss, it does not call RPG or Db2 for i.
import java.nio.charset.Charset;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class RoundTripTest {
@Test
void nameSurvivesRoundTrip() {
Charset ccsid37 = Charset.forName("IBM037");
String sent = "Jané 李";
byte[] bytes = sent.getBytes(ccsid37);
String back = new String(bytes, ccsid37);
assertEquals(sent, back);
}
}
The test fails because the returned name contains a substitution character instead of "李," since CCSID 37 has no equivalent for this character.
Here's another test that uses JDBC with the JTOpen driver against a test table on IBM I. It writes rows of data, reads the data back, and checks each trap in turn.
create table jktest.typetest (
id char(7) not null,
amount decimal(9,2) not null,
disc decimal(5,2),
name char(10) ccsid 37,
ts timestamp(6) not null
)
import java.math.BigDecimal;
import java.sql.*;
import java.time.LocalDateTime;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class Db2RoundTripTest {
// JTOpen driver on the classpath: jdbc:as400://<system>
private Connection connect() throws SQLException { /* ... */ }
private Connection connect() throws SQLException {
return
DriverManager.getConnection("jdbc:as400://<system>", "<user>", "<password>");
}
private static void insert(Connection con, String id,
String amount, String disc, String name,
LocalDateTime ts) throws SQLException {
String sql = "insert into jktest.typetest values(?,?,?,?,?)";
try (PreparedStatement ps = con.prepareStatement(sql)) {
ps.setString(1, id);
ps.setBigDecimal(2, new BigDecimal(amount));
ps.setBigDecimal(3, disc == null ? null
: new
BigDecimal(disc));
ps.setString(4, name);
ps.setTimestamp(5, Timestamp.valueOf(ts));
ps.executeUpdate();
}
}
private static ResultSet select(Connection con, String id)
throws SQLException {
ResultSet rs = con.createStatement().executeQuery(
"select * from jktest.typetest where id = '" + id + "'");
assertTrue(rs.next());
return rs;
}
@Test
void valuesSurviveRoundTrip() throws Exception {
LocalDateTime ts =
LocalDateTime.of(2026, 1, 1, 0, 0, 0, 123_456_789);
try (Connection con = connect()) {
con.createStatement()
.executeUpdate("delete from jktest.typetest");
insert(con, "0001234", "1234.50", null, "Jane", ts);
ResultSet rs = select(con, "0001234");
assertEquals("0001234", rs.getString("ID"));
assertEquals(new BigDecimal("1234.50"),
rs.getBigDecimal("AMOUNT"));
assertNull(rs.getBigDecimal("DISC")); // null, not 0.00
assertTrue(rs.wasNull());
assertEquals(
LocalDateTime.of(2026, 1, 1, 0, 0, 0, 123_456_000),
rs.getTimestamp("TS").toLocalDateTime());
insert(con, "0000004", "1.00", "0.00", "Jané 李", ts);
rs = select(con, "0000004");
assertEquals(new BigDecimal("0.00"),
rs.getBigDecimal("DISC")); // zero, not null
assertFalse(rs.wasNull());
assertNotEquals("Jané 李", rs.getString("NAME"));
// Decimal limit: DECIMAL(9,2) holds 9999999.99 at most
assertThrows(SQLException.class, () -> insert(con,
"0000002", "10000000.00", null, "Jane", ts));
// Overlength: 11 characters into CHAR(10)
assertThrows(SQLException.class, () -> insert(con,
"0000003", "1.00", null, "ABCDEFGHIJK", ts));
}
}
}
Note that each of the standalone Java examples was successfully compiled and executed on OpenJDK 21. For the JSON example, Jackson 2.16.1 was used. The results in each comment reflect the actual observed result.
Mapping Reference
The following table summarizes the mappings discussed in this article.
| IBM i definition | Java type | Wire representation | Validation rule |
|---|---|---|---|
| packed(p:s) or DECIMAL(p,s) | BigDecimal | JSON string, such as "1234.50" | Round to scale s with a named RoundingMode; at most p-s integer digits |
| zoned(p:s) or NUMERIC(p,s) | BigDecimal | JSON string | Same as packed |
| Business identifier held in a zoned, packed, or char field | String | JSON string with a pattern, such as ^[0-9]{7}$ | Exact length and allowed characters; keep leading zeros |
| int(5), int(10), int(20) or SMALLINT, INTEGER, BIGINT | short, int, long | JSON number | Range check |
| float(8) or DOUBLE | double | JSON number | Never use for money; compare with a tolerance |
| date or DATE | LocalDate | "2026-01-01" | Valid calendar date; define how sentinel dates are handled |
| timestamp or TIMESTAMP(p) | LocalDateTime; Instant or OffsetDateTime for events | "2026-01-01T00:00:00", or with an offset or Z for events | At most p fractional digits; reject gaps; define a rule for overlaps |
| char(n) or CHAR(n) | String | JSON string | Length within n, counted in the field's units; padding rule stated in the contract |
| varchar(n) ccsid(*utf8) or CCSID 1208 | String | JSON string | Length within n bytes after UTF-8 encoding |
| Nullable column with a null indicator | Nullable reference type | null, or an absent property | Contract states what missing, null, empty string, and zero each mean |
Takeaways
- The "traps" mentioned in this article are based on a hidden assumption about type, length, encoding, and/or time zone.
- Map packed and zoned fields directly to BigDecimal type; use the fields’ precision, scale, rounding, and overflow specifications.
- When you map to float or double, you lose the ability to maintain exactness at each stage of processing.
- All identifier values that are represented as numbers should be treated as text.
- Additionally, all of these identifier values should have their format validated against a regular expression pattern defined in your OpenAPI contract.
- Db2 dates can be read as LocalDates, while Db2 timestamps can be read as LocalDateTimes, as they do not store time zone information. You should use LocalDate for dates, and LocalDateTime for timestamps that do not include a time zone. You should only use Instant or an offset-based type when you need to represent events on the timeline.
- You can either attach the writing process time zone to each date/timestamp, or store them in UTC format, which will keep an order placed at midnight on January 1st in the correct year.
- Tag each character column with the actual CCSID and reject characters that are not supported by the target CCSID in Java before the call.
- Use stripTrailing() rather than trim() to remove the spaces from fixed-length char fields.
- You should remember that padding is not the same as truncation. Include the padding rules in your contract, and make sure you check the length of the data you receive.
- Verify the length of the string before returning a value. RPG will truncate a longer value without indicating it.
- For all optional fields, you should specify missing, explicit null, empty string, zero, and enforce these definitions through runtime validation.
- For any column that can be null, provide an indicator of its status; transmit financial data using JSON strings; do not allow empty strings in numeric columns.
- Provide evidence of all mappings with a round-trip test, since one-way tests don't show whether truncation, rounding, or character loss occurs.
Business users want new applications now. Market and regulatory pressures require faster application updates and delivery into production. Your IBM i developers may be approaching retirement, and you see no sure way to fill their positions with experienced developers. In addition, you may be caught between maintaining your existing applications and the uncertainty of moving to something new.
IT managers hoping to find new IBM i talent are discovering that the pool of experienced RPG programmers and operators or administrators with intimate knowledge of the operating system and the applications that run on it is small. This begs the question: How will you manage the platform that supports such a big part of your business? This guide offers strategies and software suggestions to help you plan IT staffing and resources and smooth the transition after your AS/400 talent retires. Read on to learn:
LATEST COMMENTS
MC Press Online