Repository navigation
[Feature] Standardize JSON-RPC null parameter handling #6951
Description
Activity
One compatibility question around
eth_uninstallFilter: this proposal makes a null ID returnfalse, while an unknown but well-formed non-null ID still returns-32000until a follow-up. That leaves the malformed input looking successful while the validly shaped missing filter errors, and clients may have to absorb two wire changes to the same method. Would it be cleaner to either treat null as-32602for now, or include the unknown-ID-to-falsenormalization here and document it as one compatibility change? This also seems more accurately described as breaking for affected callers, since an error becomes a successful result.@lxcmyf Thanks. I agree with your second option: handle null and unknown filter IDs together, rather than introduce another intermediate uninstall behavior.
Concretely, in this issue:
- A null ID, and any ID that does not identify an installed filter, return
false. - Removing an existing filter still returns
true. eth_getFilterChangesandeth_getFilterLogskeep their-32000 "filter not found"for unknown IDs; onlyeth_uninstallFiltermoves.
falsehere means no filter was removed, not that removal succeeded, which is also what go-ethereum'sUninstallFilterreports on a lookup miss. This moves one method's behavior out of the follow-up list without pulling in the other parameter-validation items and without creating a dependency on #6941.One consequence worth stating in the issue rather than discovering later:
ByteArray.fromHexonly strips a0xprefix and left-pads to an even length, so it is normalization rather than validation. Once a lookup miss returnsfalse, an empty string or a non-hexadecimal string also returnsfalseinstead of-32000, because they simply fail the lookup. I will cover that in the tests and the compatibility notes, and I will not add format validation as a side effect of this change.You are also right about the classification. "No successful request starts failing" is too narrow a basis, since callers can branch on the error code or on error-versus-result. It is also inconsistent with #6941, which labels the same class of change as breaking. I will change it to:
Yes, limited to the affected null-input and filter lookup-miss responses. Successful valid calls remain unchanged.
I will update the summary, the audit scope, the design table, the compatibility section, the acceptance criteria and the follow-up list, and replace the tests that currently pin the old unknown-ID error, adding coverage for repeated removal and for the string-ID normalization above. Does that bounded scope adjustment address your concern?
- A null ID, and any ID that does not identify an installed filter, return
Thanks for the detailed investigation and the clear scope. The distinction between method-level null handling here and framework-level exception mapping in #6941 makes sense. Returning
falsewhenevereth_uninstallFilterremoves no filter also gives clients a consistent contract.This issue highlights an opportunity to capture a lightweight set of conventions for future API additions and changes. For example:
- Define how omitted parameters, explicit
null, empty values, and malformed values are handled. - Specify defaults and distinguish invalid input from valid requests that find no matching resource.
- Document the expected result/error shape and protocol-specific error codes, including any intentional compatibility deviations.
- Cover these decisions with reusable wire-level contract tests, including the behavior of different node types where applicable.
Having these expectations in the API review checklist would help catch ambiguities before release and reduce repeated endpoint-by-endpoint fixes.
I would support this as a separate follow-up, adopted incrementally for new or modified APIs. It should not block the focused changes proposed here.
- Define how omitted parameters, explicit
@halibobo1205 Thanks. I agree that a lightweight API review checklist would be useful, applied incrementally to new or modified APIs rather than requiring a retrofit of existing endpoints.
This issue can supply a few worked examples: distinguishing omitted, explicit
null, empty and malformed inputs; documenting defaults and lookup-miss results; and checking exact wire responses together with node-specific behavior across FullNode, Solidity and PBFT.Two points from this work that a checklist would benefit from stating explicitly:
- Normalization is not validation.
ByteArray.fromHexstrips a0xprefix and left-pads to an even length, so an empty or non-hexadecimal filter ID is not rejected; it simply fails the lookup and now returnsfalse. That is a consequence of preserving the existing ID handling, not a statement that those inputs conform to the schema. A convention should say, per parameter, which of the two applies, because that choice decides whether a malformed value yields-32602or the not-found result. - Invalid input and a valid request with no matching resource are different. Here a null required object parameter is invalid input and returns
-32602, while a well-formed request to uninstall an unknown filter returnsfalsebecause no filter was removed.eth_getFilterChangesandeth_getFilterLogskeep-32000for the same lookup miss, because there the caller asked for data that does not exist. Why those three differ is not obvious from the method names.
The ordering I used to decide the JSON-RPC cases here was the Execution API
requiredand schema first, then whethernullcan reasonably be read as the zero value, then go-ethereum and Besu as a reference. That is a JSON-RPC example rather than a rule for the HTTP and gRPC surfaces, which have their own conventions.For JSON-RPC specifically, I would keep envelope validation and notification classification, discussed in #6676, separate from method-level argument handling, so the checklist references those decisions instead of becoming a second place that defines competing rules.
A short checklist with a few examples and references to reusable test patterns seems like a useful first step. I can contribute the material from this work. It would not block or expand #6951; the uninstall adjustment is implemented and regression-tested on the branch.
- Normalization is not validation.
- added a parent issue
on Sep 9, 2026 - addedtopic:apirpc/http related issuerpc/http related issueand removedtopic:apirpc/http related issuerpc/http related issue
on Sep 9, 2026 @lxcmyf @halibobo1205 The PR is #6984.
The scope adjustment discussed above is in it:
eth_uninstallFilterreturnsfalsefor a null ID, an unknown ID and an ID already removed,eth_getFilterChanges/eth_getFilterLogsretain their existing-32000 "filter not found"response for unknown IDs, and the obsoleteItemNotFoundExceptiondeclaration and mapping are removed. The proposal body was updated accordingly, including the breaking-change wording and the note thatByteArray.fromHexnormalizes rather than validates, so an empty or non-hexadecimal ID now also returnsfalse. Eight lookup-miss regressions cover repeated removal and that string-ID path. Request-source restrictions, including PBFT rejection, remain unchanged.The API review checklist stays a separate follow-up; the worked examples from this issue are ready whenever that is opened.
@lxcmyf @halibobo1205 I have updated the null handling in this proposal after a recent go-ethereum change; the issue description above now reflects it.
go-ethereum v1.17.6, released on 23 September, includes ethereum/go-ethereum#35576 ("rpc: reject null for required arguments"). Parameter types with their own JSON decoding, such as hashes, addresses and quantities, already rejected
null. For the filter ID andfullTransactionObjects, however, the earlier decoder letnullreach the method as a zero value (an empty ID andfalse). Since v1.17.6 these fail parameter decoding with-32602("missing value for required argument N"). The go-ethereum alignment this proposal cited for these null inputs was based on the earlier behavior. Besu 26.9.0 also rejectsnullin these required parameter positions with its invalid-params errors ("Invalid filter params", "Invalid return complete transaction params").The updated rule: an explicit
nullin the following positions returns-32602 "invalid params", the message this proposal already uses for a nulleth_callargument.- the filter ID of
eth_uninstallFilter,eth_getFilterChangesandeth_getFilterLogs; fullTransactionObjectsofeth_getBlockByHashandeth_getBlockByNumber. The block hash or selector is validated first, following go-ethereum's argument order, and the flag is checked before any block lookup, so the result does not depend on whether the block exists.
The required object parameters of
eth_getLogs,eth_newFilter,eth_call,eth_estimateGasandbuildTransactionalready return-32602fornullin this proposal. All 10 top-level parameter positions covered by this issue will therefore reject an explicitnullwith-32602on endpoints where the methods are available. A method that is unavailable on the PBFT (or non-FullNode) endpoint keeps its current error.Unchanged:
- A non-null filter ID that does not identify an installed filter:
eth_uninstallFilterreturnsfalse;eth_getFilterChangesandeth_getFilterLogsreturn-32000 "filter not found". go-ethereum v1.17.6 and Besu behave the same way. - The seven optional DTO fields covered by this proposal continue to treat an explicit
nullas omission. Forfromin theeth_callobject, go-ethereum (a pointer field) and Besu behave the same way.
This narrows the 8 September adjustment:
nullno longer shares the lookup-miss result but is treated as invalid input, which is the first option @lxcmyf suggested and the distinction @halibobo1205 described between invalid input and a valid request that finds no matching resource.Compared with v4.8.2, the existing NPE paths will return
-32602instead of-32001. One additional behavior change is thateth_getBlockByNumberwith a non-existent block and a nullfullTransactionObjectsflag will return-32602instead ofresult: null.#6984 will be updated to this behavior shortly. Reviews of the updated PR are welcome once it is pushed; any concerns can be discussed here.
- the filter ID of
Metadata
Metadata
Assignees
Type
Projects
- StatusShow more project fieldsNo status
Summary
Some JSON-RPC methods dereference a
nullparameter without a null check, throwNullPointerException, and answer the client with jsonrpc4j's fallback error. On Java 8 the response is:{"jsonrpc":"2.0","id":1,"error":{"code":-32001,"message":null,"data":"java.lang.NullPointerException"}}This issue defines the
nullhandling of 10 parameter positions and 7 optional DTO fields:fullTransactionObjectsreturns-32602on endpoints where the method is available.nullis treated as omitted.This issue handles the listed null inputs and aligns
eth_uninstallFilterlookup-miss results for non-null IDs with go-ethereum. Other behavior of non-null inputs, HTTP status codes, gRPC and HTTP API behavior remain unchanged.This issue covers how these methods themselves check and answer a null, together with the
eth_uninstallFilterlookup-miss result; it does not touch the framework layer. The shape of the fallback response for unmapped exceptions (-32001, echoed exception class name) is a separate problem, tracked in #6941.Problem
Motivation
message: nullviolates JSON-RPC 2.0 section 5.1, which definesmessageas "A String providing a short description of the error" (nullis not a String). On JVMs where helpful NullPointerException messages are enabled, which is the default from JDK 15 onwards,messageinstead carries a diagnostic string naming internal fields and method signatures.dataechoes the Java exception class name, which clients should not depend on.-32001is registered in the public error catalog as a server-side internal error, so clients cannot tell that they passed a bad parameter.Current State
10 parameter positions have execution paths that can dereference a null argument:
eth_getLogs,eth_newFilter,eth_estimateGas,eth_call,buildTransactioneth_uninstallFilter,eth_getFilterChanges,eth_getFilterLogsfullTransactionObjects(Booleanauto-unboxing):eth_getBlockByHash,eth_getBlockByNumber7 optional DTO fields behave differently when explicitly null than when omitted:
tokenId/tokenValueonBuildArguments(return-32001),consumeUserResourcePercent/originEnergyLimit/permissionId/extraData(wrapped into-32000by the builder's catch-all), andCallArguments.from(returns-32602, whereas omitting it continues with the zero address).Audit scope: the null handling of all 52 methods on
TronJsonRpcwas checked one by one, with the result below. This issue only covers positions where a null can lead to an unhandled exception, or DTO fields whose explicit null differs from omission. These positions are covered as a whole, including the existing successfuleth_getBlockByNumberbranch for a non-existent block and a nullfullTransactionObjects; positions that already reject null are left alone; positions where null already has a definite but debatable result are behavior changes that need their own discussion. Following the discussion below, one lookup-miss behavior is additionally taken into this issue,eth_uninstallFilterreturningfalseinstead of an error, avoiding a separate intermediate policy for uninstall lookup misses. The other behavior changes stay out of scope.-32601, so the parameters never reach the business logic-32602on null (most of these null checks were added by #6828)NullPointerExceptionon nullweb3_sha3(null)returns the hash of empty input; whether the transaction index is validated depends on whether the block exists; the 4 uncle methods validate nothing; an optional block parameter that is null returns an error (-32600foreth_call) instead of being treated aslatestOn the baseline verified here, the node logs nothing for these calls, because
JsonRpcServletsetssetShouldLogInvocationErrors(false)and the fallback path has no log point of its own. develop @ 4a21592 and GreatVoyage-v4.8.2.1 are both affected; verified on Java 8 and Java 17. Reproduce:Limitations and Risks
-32001/-32000) on these paths will observe a change.Proposed Solution
Proposed Design
Basis, in priority order: the Execution API
required/ schema; whethernullcan reasonably be taken as the zero value; go-ethereum (v1.17.6) / Besu (26.9.0) behavior as a reference.The null-input results below apply on endpoints where the methods are available. Existing request-source checks retain precedence; unavailable methods keep their current errors.
-32602, messageinvalid filter request(filter methods) /invalid paramsrequired. go-ethereum v1.17.6 and Besu 26.9.0 also reject it with-32602; go-ethereum v1.17.5 and earlier decoded it as a zero value. The comparison applies to the foureth_methods; the TRON-specificbuildTransactionfollows the same required-object policy.eth_uninstallFilter/eth_getFilterChanges/eth_getFilterLogs-32602 "invalid params"nullhere with-32602; go-ethereum v1.17.5 and earlier decoded it as an empty IDeth_uninstallFilterwith a non-null ID that is unknown or already removedfalseUninstallFilterreports whether a filter was found; removing an installed filter still returnstrueeth_getFilterChanges/eth_getFilterLogswith a non-null unknown ID-32000 "filter not found", unchangedfullTransactionObjects-32602 "invalid params". The block hash or selector is validated first, and the flag is checked before any block lookup, so the result does not depend on whether the block existsnullhere with-32602; go-ethereum v1.17.5 and earlier decoded it asfalseCallArguments.from, go-ethereum (a pointer field) and Besu behave the same wayError responses keep the existing annotation mapping:
datais"{}"and theidis echoed.Key Changes
eth_getBlockByHash/eth_getBlockByNumber, the block hash or selector is validated first, thenfullTransactionObjects, before any block lookup; a null flag is no longer unboxed.@JsonSetter(nulls = Nulls.SKIP)to the 7 DTO fields; all 7 already declare non-null default initializers (0L/0/""/ the zero address), so skipping the setter on an explicit null lands exactly on the omitted-field semantics.eth_callvalidates its required transaction argument before the block parameter, soeth_call([null, null])goes from-32600to-32602.frameworkmodule:TronJsonRpc,TronJsonRpcImpl,JsonRpcApiUtil,LogFilter,BuildArguments,CallArguments. Remove the obsoleteItemNotFoundExceptiondeclaration and mapping frometh_uninstallFilter; the other filter methods retain them. Removing athrowsclause keeps existing Java binaries compatible, but source callers that specifically catch that checked exception, or implementations that still declare it, may need adjustment when recompiled. OnTronJsonRpcImpl,uninstallFilterandgetFilterChangesalso declareJsonRpcInvalidParamsException, whichTronJsonRpcalready declares for them.Impact
Compatibility
fullTransactionObjects-32001->-32602;eth_getBlockByNumberwith a non-existent block and a nullfullTransactionObjectsgoes fromresult: nullto-32602;eth_uninstallFilterreturnsfalsefor any non-null ID that does not identify an installed filter, replacing the previous-32000; explicit null DTO fields equal omission;eth_call([null, null])goes from-32600to-32602.eth_uninstallFilteranswers with an error or with a result.ByteArray.fromHexonly strips a0xprefix and left-pads to an even length, so it normalizes rather than validates. Once a lookup miss returnsfalse, an empty string or a non-hexadecimal string also returnsfalseinstead of-32000, because they simply fail the lookup. This issue does not add format validation as a side effect.Every
-32001above is jsonrpc4j's fallback for an exception without an@JsonRpcErrorsmapping. If #6941 lands first, that fallback becomes-32603 "Internal error"withoutdata, so only the observed "before" side of these rows changes; the target behavior defined by this issue is the same either way.The following remain unchanged: results of valid non-null requests other than the
eth_uninstallFilterlookup misses above, HTTP status codes, the request-source check, wildcard semantics of nulls inside a filter object, gRPC and non-JSON-RPC HTTP API behavior.Acceptance Criteria
JsonRpcServer; error assertions covercode,messageanddata, including the absence of Java exception class names.ObjectMapperdeserialization: an explicit null and an omitted field give the same result.-32602 "invalid params"from all three filter-ID methods on supported endpoints; PBFT retains-32601.fullTransactionObjectsreturns-32602 "invalid params"for existing and non-existent blocks; an invalid block hash or selector keeps its own error, and a null flag is rejected before any block is read.eth_uninstallFilterreturnsfalsefor an unknown ID and a second removal of the same ID, andtrueonly when an installed filter is removed.eth_uninstallFilterreturnsfalsefor an empty string and for a non-hexadecimal string, and no format validation is introduced.Follow-up
Outside the scope of this issue and not blocking its closure:
params,params: nullandparams: []retain existing dispatch and arity behavior and are not universally rejected: whether they are accepted depends on how many parameters the method declares. This issue covers explicitnullvalues in the argument positions and DTO fields listed above, plus theeth_uninstallFilterlookup-miss result.web3_sha3(null), unconditional transaction index validation, parameter validation for the uncle methods, and normalizing an omitted / null optional block parameter tolatest; each gets its own issue.Additional Notes
frameworkmodule (TronJsonRpc,TronJsonRpcImpland the related DTOs) and does not touch consensus, transaction execution or other core logic. [Feature]Standardize JSON-RPC error handling(revert codes, LiteNode pruned-history responses, request fields validation) #6676 also touchesJsonRpcApiUtilandTronJsonRpcImpl, and [Feature] Standardize JSON-RPC error mapping and exception boundaries #6941 also modifiesTronJsonRpcandTronJsonRpcImpl. This issue does not require [Feature]Standardize JSON-RPC error handling(revert codes, LiteNode pruned-history responses, request fields validation) #6676 or [Feature] Standardize JSON-RPC error mapping and exception boundaries #6941 to land first; whichever lands second rebases and re-runs the related regression tests.