java 98 lines · 3 tabs

Grouping and Summarizing Transactions with Java Stream Collectors

Shared by codesnips Aug 2026
3 tabs
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;

import Transaction.Category;

public class Runner {

    public static void main(String[] args) {
        var txns = List.of(
                new Transaction("alice", Category.GROCERIES, new BigDecimal("42.50"), LocalDate.of(2024, 1, 3)),
                new Transaction("alice", Category.RENT, new BigDecimal("1200.00"), LocalDate.of(2024, 1, 1)),
                new Transaction("bob", Category.GROCERIES, new BigDecimal("18.99"), LocalDate.of(2024, 1, 4)),
                new Transaction("bob", Category.TRANSPORT, new BigDecimal("7.25"), LocalDate.of(2024, 1, 5)),
                new Transaction("alice", Category.GROCERIES, new BigDecimal("63.10"), LocalDate.of(2024, 1, 7)));

        var report = new TransactionReport();

        System.out.println("Total by category: " + report.totalByCategory(txns));
        System.out.println("Count by account:  " + report.countByAccount(txns));
        System.out.println("Category summary:  " + report.categorySummary(txns));

        report.largestPerAccount(txns).forEach((account, top) ->
                top.ifPresent(t -> System.out.printf("%s largest: %s%n", account, t.amount())));
    }
}
3 files · java Explain with highlit

This snippet demonstrates how to turn a flat list of financial transactions into a structured report using the java.util.stream.Collectors API, avoiding manual loops and mutable accumulators. The domain is modeled in Transaction as an immutable record carrying an account, a category, an amount as BigDecimal, and a timestamp. Using BigDecimal rather than double is deliberate: monetary sums must not accumulate floating-point error, which rules out the primitive summingDouble collectors in favor of a reducing approach.

The reporting logic lives in TransactionReport, where each method composes a different collector. totalByCategory uses groupingBy with a downstream collector built from Collectors.reducing, seeded with BigDecimal.ZERO and folding amounts with BigDecimal::add; this keeps the sum exact across the whole group. countByAccount shows the simplest downstream, Collectors.counting, producing a Map<String, Long>. topCategoriesByAccount nests two groupingBy calls to build a Map<String, Map<Category, BigDecimal>>, proving that downstream collectors compose arbitrarily deep.

categorySummary combines several statistics in one pass with Collectors.teeing, merging a total and a count into a CategoryStat record. Because teeing runs both downstream collectors over the same elements and merges their results, the stream is traversed once rather than repeatedly filtered. largestPerAccount uses maxBy with a comparator on the amount, wrapping the winner in an Optional to handle empty groups safely.

The key idea is that collectors are values that can be nested and combined, so a complex aggregation is expressed declaratively instead of as imperative bookkeeping. This scales better than hand-rolled maps: there is no null-checking computeIfAbsent, no accidental mutation, and the intent reads directly from the method names. A pitfall worth noting is that groupingBy returns a HashMap with no ordering guarantee; when ordered output matters a TreeMap supplier or LinkedHashMap should be supplied explicitly. The Runner tab wires sample data together and prints the reports, showing how the pieces fit in a small program.


Related snips

ruby
class Money
  include Comparable

  attr_reader :amount, :currency

  def initialize(amount, currency = 'USD')

Value objects for domain modeling

ruby value-objects domain-driven-design
by Sarah Mitchell 2 tabs
graphql
type User {
    id: ID!
    name: String!
    email: String!
    posts: [Post!]!
    createdAt: String!

GraphQL API with Spring Boot

java graphql spring-boot
by David Kumar 3 tabs
java
package com.example.starter.config;

import com.example.starter.properties.CustomProperties;
import com.example.starter.service.CustomService;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;

Custom Spring Boot starters

java spring-boot starter
by David Kumar 4 tabs
java
package com.example.demo.config;

import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.kafka.common.serialization.StringSerializer;

Messaging with Apache Kafka

java kafka messaging
by David Kumar 3 tabs
ruby
class ReportQuery
  SQL = <<~SQL.freeze
    SELECT date_trunc('day', events.created_at) AS day,
           count(*) AS total,
           count(*) FILTER (WHERE events.kind = 'purchase') AS purchases
    FROM events

Safe Raw SQL with exec_query + Binds

rails activerecord sql
by codesnips 2 tabs
ruby
class Tag < ApplicationRecord
  has_many :taggings, dependent: :destroy

  scope :top, ->(limit = 20) {
    joins(:taggings)
      .group(Arel.sql("tags.id"))

Memory-Safe “top tags” aggregation with pluck + group

rails activerecord performance
by codesnips 3 tabs

Share this code

Here's the card — post it anywhere.

Grouping and Summarizing Transactions with Java Stream Collectors — share card
Link copied