Tencent/APIJSON · critical · NullPointerException

服务器内部错误,List<Join> 中 Join.onList[{}].getOriginKey() = null!

Error message

服务器内部错误,List<Join> 中 Join.onList[{}].getOriginKey() = null!

What it means

Internal NullPointerException: a non-null On object in join.getOnList() returns null from getOriginKey(). The executor needs originKey (e.g. "id@/User/id") to derive the vice-table where key; null means the On was built without its origin key, breaking the JOIN cache lookup.

Source

Thrown at APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java:611

							columnIndexAndJoinMap[i - 1] = curJoin;
						}

						// 如果是主表则直接用主表对应的 item,否则缓存副表数据到 childMap
						Join prevJoin = columnIndexAndJoinMap == null || i < 2 ? null : columnIndexAndJoinMap[i - 2];
						if (curJoin != prevJoin) {  // 前后字段不在同一个表对象,即便后面出现 null,也不该是主表数据,而是逻辑 bug 导致
							SQLConfig<T, M, L> viceConfig = curJoin != null && curJoin.isSQLJoin() ? curJoin.getCacheConfig() : null;
							boolean hasPK = false;
							if (viceConfig != null) {  //FIXME 只有和主表关联才能用 item,否则应该从 childMap 查其它副表数据
								List<On> onList = curJoin.getOnList();
								int size = onList == null ? 0 : onList.size();
								if (size > 0) {
									String idKey = viceConfig.getIdKey();
									String tblKey = config.gainTableKey();
									for (int j = size - 1; j >= 0; j--) {
										On on = onList.get(j);
										String ok = on == null ? null : on.getOriginKey();
										if (ok == null) {
											throw new NullPointerException("服务器内部错误,List<Join> 中 Join.onList[" + j + (on == null ? "] = null!" : ".getOriginKey() = null!"));
										}

										String k = ok.substring(0, ok.length() - 1);
										String ttk = on.getTargetTableKey();

										M target = StringUtil.equals(ttk, tblKey) ? item : (viceItem == null ? null : JSON.get(viceItem, ttk));
										Object v = target == null ? null : target.get(on.getTargetKey());
										hasPK = hasPK || (k.equals(idKey) && v != null);

										viceConfig.putWhere(k, v, true);
									}
								}
							}

							if (viceConfig == null) { // StringUtil.isEmpty(viceSql, true)) {
								Log.i(TAG, "execute viceConfig == null >> item = null; >> ");
								curItem = null;
							}

View on GitHub (pinned to 5284052872)

Solutions

  1. Always set originKey when building On in code (it encodes key + '/' + target path).
  2. Check the joiner string segment corresponding to the reported index for a missing '/key@/...' part.
  3. Match versions of APIJSON jars; if input looks correct, file an issue with the request body.

Example fix

// before
On on = new On(); on.setKey("userId");
// after
On on = new On(); on.setKey("userId"); on.setOriginKey("userId@/User/id");
Defensive patterns

Strategy: try-catch

Validate before calling

if (join != null && join.getOnList() != null) {
  for (int i = 0; i < join.getOnList().size(); i++) {
    On on = join.getOnList().get(i);
    if (on != null && on.getOriginKey() == null) throw new IllegalStateException("On[" + i + "] missing originKey");
  }
}

Try / catch

catch (NullPointerException npe) { if (npe.getMessage() != null && npe.getMessage().contains("getOriginKey() = null")) { /* rebuild join from well-formed joiner string */ } }

Prevention

When it happens

Trigger: Programmatically created On objects where setOriginKey was never called, or joiner-string parsing that failed to capture the origin key portion but still added the On.

Common situations: Custom extensions constructing On instances; reflective/deserialized Join configurations missing fields; version skew in On's constructor signature.

Related errors


AI-assisted analysis of Tencent/APIJSON@5284052872 (2026-08-14). Data as JSON: /api/errors/ab61012fefe3ea10. Report an issue: GitHub.