private InternalScanner wrap(byte[] family, InternalScanner scanner) {
    return new InternalScanner() {

      private List<Cell> srcResult = new ArrayList<>();

      private byte[] row;

      private byte[] qualifier;

      private long timestamp;

      private long sum;

      @Override
      public boolean next(List<? super ExtendedCell> result, ScannerContext scannerContext)
        throws IOException {
        boolean moreRows = scanner.next(srcResult, scannerContext);
        if (srcResult.isEmpty()) {
          if (!moreRows && row != null) {
            // all cells in HBase are ExtendedCells, so you are fine to cast it to ExtendedCell,
            // just do not use its methods since it may change without any deprecation cycle
            result.add((ExtendedCell) createCell(row, family, qualifier, timestamp, sum));
          }
          return moreRows;
        }
        Cell firstCell = srcResult.get(0);
        // Check if there is a row change first. All the cells will come from the same row so just
        // check the first one once is enough.
        if (row == null) {
          row = CellUtil.cloneRow(firstCell);
          qualifier = CellUtil.cloneQualifier(firstCell);
        } else if (!CellUtil.matchingRows(firstCell, row)) {
          // all cells in HBase are ExtendedCells, so you are fine to cast it to ExtendedCell,
          // just do not use its methods since it may change without any deprecation cycle
          result.add((ExtendedCell) createCell(row, family, qualifier, timestamp, sum));
          row = CellUtil.cloneRow(firstCell);
          qualifier = CellUtil.cloneQualifier(firstCell);
          sum = 0;
        }
        srcResult.forEach(c -> {
          if (CellUtil.matchingQualifier(c, qualifier)) {
            sum += Bytes.toLong(c.getValueArray(), c.getValueOffset());
          } else {
            // all cells in HBase are ExtendedCells, so you are fine to cast it to ExtendedCell,
            // just do not use its methods since it may change without any deprecation cycle
            result.add((ExtendedCell) createCell(row, family, qualifier, timestamp, sum));
            qualifier = CellUtil.cloneQualifier(c);
            sum = Bytes.toLong(c.getValueArray(), c.getValueOffset());
          }
          timestamp = c.getTimestamp();
        });
        if (!moreRows) {
          // all cells in HBase are ExtendedCells, so you are fine to cast it to ExtendedCell,
          // just do not use its methods since it may change without any deprecation cycle
          result.add((ExtendedCell) createCell(row, family, qualifier, timestamp, sum));
        }
        srcResult.clear();
        return moreRows;
      }

      @Override
      public void close() throws IOException {
        scanner.close();
      }
    };
  }
An example for implementing a counter that reads is much less than writes, i.e, write heavy.
<p>
We will convert increment to put, and do aggregating when get. And of course the return value of
increment is useless then.
<p>
Notice that this is only an example so we do not handle most corner cases, for example, you must
provide a qualifier when doing a get.
hbase-examples/src/main/java/org/apache/hadoop/hbase/coprocessor/example/WriteHeavyIncrementObserver.java