Java and Spring Boot · Beginner

Modern Java in one program

3 min read

Why it matters

Java appeared in more than half of the Software Engineer job descriptions we read, and in three quarters of the junior ones. Much older Java code is verbose; modern Java is compact and safer. Knowing both styles, and preferring the modern one, is what reviewers and interviewers look for.

The concept

  • Records are immutable data carriers: record Order(String id, BigDecimal amount) {} gives you a constructor, accessors (order.id()), equals, hashCode and toString. A compact constructor can validate fields.

  • Enums represent a fixed set of values, and the compiler checks that a switch covers them all.

  • Collections: List.of, Map.of create immutable collections. ArrayList, HashMap and TreeMap (sorted keys) are the everyday mutable ones.

  • Streams express transformations: filter, map, collect, groupingBy. Use them for clear pipelines and plain loops when a loop is clearer.

  • Optional makes "may be missing" explicit in return types, instead of returning null.

  • BigDecimal for money. double cannot represent most decimal amounts exactly.

  • Exceptions: throw IllegalArgumentException for bad input; catch only what you can handle.

Worked example: orders

Orders.java
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.*;

public class Orders {
    enum Status { PLACED, SHIPPED, DELIVERED, CANCELLED }

    record Order(String id, String city, Status status, BigDecimal amount) {
        Order {
            if (amount.signum() < 0) throw new IllegalArgumentException("amount must not be negative");
        }
    }

    static Optional<Order> largest(List<Order> orders, String city) {
        return orders.stream()
                .filter(o -> o.city().equals(city))
                .max(Comparator.comparing(Order::amount));
    }

    public static void main(String[] args) {
        List<Order> orders = List.of(
                new Order("A1", "Pune", Status.DELIVERED, new BigDecimal("1250.00")),
                new Order("A2", "Delhi", Status.CANCELLED, new BigDecimal("499.00")),
                new Order("A3", "Pune", Status.SHIPPED, new BigDecimal("3100.50")),
                new Order("A4", "Delhi", Status.DELIVERED, new BigDecimal("780.00")));

        Map<String, BigDecimal> revenueByCity = orders.stream()
                .filter(o -> o.status() != Status.CANCELLED)
                .collect(Collectors.groupingBy(Order::city, TreeMap::new,
                        Collectors.reducing(BigDecimal.ZERO, Order::amount, BigDecimal::add)));
        System.out.println(revenueByCity);

        System.out.println(largest(orders, "Pune").map(Order::id).orElse("none"));
        System.out.println(largest(orders, "Mumbai").map(Order::id).orElse("none"));

        String label = switch (orders.get(1).status()) {
            case PLACED, SHIPPED -> "in progress";
            case DELIVERED -> "done";
            case CANCELLED -> "cancelled";
        };
        System.out.println(label);

        try {
            new Order("A5", "Pune", Status.PLACED, new BigDecimal("-1"));
        } catch (IllegalArgumentException e) {
            System.out.println("rejected: " + e.getMessage());
        }
    }
}

Java 11 and later can run a single source file directly:

shellsession
$ java Orders.java
{Delhi=780.00, Pune=4350.50}
A3
none
cancelled
rejected: amount must not be negative

Points to notice:

  • groupingBy with TreeMap::new gives a sorted map, so the output order is stable.

  • BigDecimal keeps amounts exact. With double, 0.1 + 0.2 is 0.30000000000000004, and such errors add up in totals.

  • largest returns Optional<Order>, so the caller must decide what "not found" means (orElse("none")).

  • The switch expression has no default. If someone adds a new status, the compiler reports the missing case.

Common mistakes

  • Comparing strings or BigDecimal with ==. Use equals for strings, and compareTo for BigDecimal (new BigDecimal("1.0").equals(new BigDecimal("1.00")) is false).

  • Returning null for "not found". Return Optional or an empty collection.

  • Mutating shared collections. List.of is immutable; copy into a new ArrayList if you need to change it.

  • Catching Exception everywhere. Catch specific exceptions where you can recover, and let the rest propagate.

Interview angle

Common questions: the difference between == and equals, why hashCode must be consistent with equals, ArrayList versus LinkedList, how HashMap works (buckets, hashing, resizing), checked versus unchecked exceptions, and what records and Optional are for. Be ready to write a stream pipeline and then the same logic as a loop.

Practice task

  1. Add a LocalDate placedOn field and print revenue per month for delivered orders.

  2. Write a method that returns the top two cities by number of non-cancelled orders, first with streams and then with a loop.

Further reading