Instance field holds Database operation results

ID

apex.correctness.stateful_db_result

Severity

high

Remediation Complexity

medium

Remediation Risk

low

Remediation Effort

medium

Resource

Resource Management

Language

Apex

Description

Reports an instance field typed Database.SaveResult, UpsertResult, DeleteResult or a collection of them.

Rationale

A Database.*Result is not a small status code: it carries the errors raised for the row it describes, and each error holds the field names and the record it came from. Parked in an instance field of a Database.Stateful batch, the whole set is serialized between chunks and grows with every batch that fails, until the job dies on the serialization limit with a message that names the batch rather than the field filling it. The same field in an ordinary class keeps records alive for the length of the transaction and pushes it toward the heap limit.

The following code illustrates the pattern detected by this rule:

global class OrderLoadBatch implements Database.Batchable<SObject>, Database.Stateful {

    // FLAGGED: Instance field holds Database operation results
    private List<Database.SaveResult> saveResults = new List<Database.SaveResult>();

    // FLAGGED: Instance field holds Database operation results
    global Database.UpsertResult[] upsertResults;

    // FLAGGED: Instance field holds Database operation results
    public Database.DeleteResult lastDelete;

    private Integer failureCount = 0;

    private List<Id> failedIds = new List<Id>();

    private static final String QUERY = 'SELECT Id FROM Order';

    global Database.QueryLocator start(Database.BatchableContext bc) {
        return Database.getQueryLocator(QUERY);
    }

    global void execute(Database.BatchableContext bc, List<SObject> scope) {
        List<Database.SaveResult> results = Database.update(scope, false);
        for (Database.SaveResult r : results) {
            if (!r.isSuccess()) {
                failureCount++;
            }
        }
    }

    global void finish(Database.BatchableContext bc) {
        System.debug(failureCount);
    }
}

Remediation

Consume the results where they are produced: loop over them immediately, keep only what the rest of the job needs - a failure count, the failed ids, a message string - and let the results go out of scope. Where the detail must survive the transaction, write it to a custom logging object or publish a platform event, which outlives a rollback.

Configuration

This detector does not need any configuration.

© 2026 Xygeni. All rights reserved