private ValidationResult buildRecordsAndAssignOffsets(LongRef offsetCounter,
                                                          long logAppendTime,
                                                          RecordBatch firstBatch,
                                                          List<Record> validatedRecords,
                                                          int uncompressedSizeInBytes) {
        long startNanos = time.nanoseconds();
        int estimatedSize = AbstractRecords.estimateSizeInBytes(toMagic, offsetCounter.value, targetCompression.type(),
            validatedRecords);
        // The current implementation of BufferSupplier is naive and works best when the buffer size
        // cardinality is low, so don't use it here
        ByteBuffer buffer = ByteBuffer.allocate(estimatedSize);
        MemoryRecordsBuilder builder = MemoryRecords.builder(buffer, toMagic, targetCompression,
            timestampType, offsetCounter.value, logAppendTime, firstBatch.producerId(),
            firstBatch.producerEpoch(), firstBatch.baseSequence(), firstBatch.isTransactional(),
            partitionLeaderEpoch);

        for (Record record : validatedRecords)
            builder.appendWithOffset(offsetCounter.value++, record);

        MemoryRecords records = builder.build();

        RecordsInfo info = builder.info();

        // This is not strictly correct, it represents the number of records where in-place assignment is not possible
        // instead of the number of records that were converted. It will over-count cases where the source and target are
        // message format V0 or if the inner offsets are not consecutive. This is OK since the impact is the same: we have
        // to rebuild the records (including recompression if enabled).
        int conversionCount = builder.numRecords();
        RecordValidationStats recordValidationStats = new RecordValidationStats(
            uncompressedSizeInBytes + builder.uncompressedBytesWritten(), conversionCount,
            time.nanoseconds() - startNanos);

        return new ValidationResult(
            logAppendTime,
            records,
            info.maxTimestamp,
            info.shallowOffsetOfMaxTimestamp,
            true,
            recordValidationStats);
    }
We cannot do in place assignment in one of the following situations:
1. Source and target compression codec are different
2. When the target magic is not equal to batches' magic, meaning format conversion is needed.
3. When the target magic is equal to V0, meaning absolute offsets need to be re-assigned.
storage/src/main/java/org/apache/kafka/storage/internals/log/LogValidator.java