EthicsComputerCourses
Join training +91 90532 10052

Legacy Java You Will Meet at Work

Java · Lesson 42 of 44 Advanced 6 min read Updated

Your first Java job will probably not start with a fresh project. Many Indian IT services companies, banks and insurers run applications written ten or fifteen years ago, and plenty of them still run on Java 8. You will be asked to fix a bug in a 2,000-line class full of Vector, Hashtable, SimpleDateFormat and casts, or to help upgrade it to a supported Java version.

This lesson is a field guide to that code. You will learn to recognise the older styles and APIs, understand the traps they hide, convert between old and new types at the edges, and upgrade a project step by step without breaking it. The rest of this course teaches the modern way; this lesson helps you live alongside the old way.

Recognising pre-generics and Java 1.0 code

Generics arrived in Java 5 (2004), lambdas and streams in Java 8 (2014). Code older than that looks like this:

Java
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

public class OldStyle {
    public static void main(String[] args) {
        // Raw types: no generics, everything is Object
        List names = new ArrayList();
        names.add("Aarav");
        names.add(42);                                  // compiles: nothing stops it
        Iterator it = names.iterator();
        while (it.hasNext()) {
            try {
                String s = (String) it.next();          // cast needed, fails at run time
                System.out.println("Name: " + s);
            } catch (ClassCastException e) {
                System.out.println("ClassCastException: a number was hiding in the list");
            }
        }

        // Vector, Hashtable and Enumeration (Java 1.0 collections)
        Vector<String> cities = new Vector<String>();
        cities.addElement("Rohtak");
        cities.addElement("Kochi");
        Enumeration<String> e = cities.elements();
        while (e.hasMoreElements()) {
            System.out.println("City: " + e.nextElement());
        }
        Hashtable<String, Integer> pins = new Hashtable<String, Integer>();
        pins.put("Rohtak", 124001);
        try {
            pins.put("Unknown", null);
        } catch (NullPointerException ex) {
            System.out.println("Hashtable rejects null values");
        }

        // Anonymous Comparator and explicit types: pre-Java 8 sorting
        List<String> team = new ArrayList<String>();
        Collections.addAll(team, "Meera", "Isha", "Kabir");
        Collections.sort(team, new Comparator<String>() {
            @Override
            public int compare(String a, String b) {
                return a.length() - b.length();
            }
        });
        System.out.println("Sorted by length: " + team);

        // Boxing with the deprecated constructor
        Integer old = new Integer(5);
        System.out.println("new Integer(5) = " + old);
    }
}
Output
Name: Aarav
ClassCastException: a number was hiding in the list
City: Rohtak
City: Kochi
Hashtable rejects null values
Sorted by length: [Isha, Meera, Kabir]
new Integer(5) = 5

A quick translation table for what you see here:

OldModern replacementWhy
List names = new ArrayList() (raw type)List<String> names = new ArrayList<>()the compiler catches wrong types
VectorArrayListVector synchronises every call, which is slow and rarely enough for real thread safety
HashtableHashMap, or ConcurrentHashMap for threadssame reason; Hashtable also rejects null
Enumeration, elements(), keys()Iterator or for-eachshorter and supports removal
StackArrayDequesee Collections in Depth
StringBufferStringBuilderno needless synchronisation
anonymous ComparatorComparator.comparing(...) or a lambdaone readable line
new Integer(5), new Double(x)Integer.valueOf(5) or autoboxingthe constructors are deprecated for removal

The compiler already knows about most of this. Compiling the file with javac -Xlint:all OldStyle.java lists every problem (shortened):

Output
OldStyle.java:13: warning: [rawtypes] found raw type: List
        List names = new ArrayList();
        ^
OldStyle.java:15: warning: [unchecked] unchecked call to add(E) as a member of the raw type List
        names.add(42);                                  // compiles: nothing stops it
                 ^
OldStyle.java:54: warning: [removal] Integer(int) in Integer has been deprecated and marked for removal
        Integer old = new Integer(5);
                      ^

Reading compiler warnings is the fastest way to map an old codebase. Turn them on in Maven or Gradle and fix them gradually, one area at a time.

Traps hiding in older code

Some bugs are typical of old code because the modern APIs were designed to avoid them:

Java
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;

public class Traps {
    public static void main(String[] args) {
        // 1. Comparing Integer objects with ==
        Integer a = 100, b = 100, c = 1000, d = 1000;
        System.out.println("100 == 100: " + (a == b) + ", 1000 == 1000: " + (c == d)
                + ", equals: " + c.equals(d));

        // 2. Arrays.asList is a fixed-size view of the array
        String[] arr = {"Pune", "Agra"};
        List<String> view = Arrays.asList(arr);
        view.set(0, "Kochi");
        System.out.println("Array changed too: " + arr[0]);
        try {
            view.add("Jaipur");
        } catch (UnsupportedOperationException e) {
            System.out.println("Arrays.asList cannot grow");
        }

        // 3. Legacy dates: build 15 August 2026 the old way
        TimeZone ist = TimeZone.getTimeZone("Asia/Kolkata");
        Calendar cal = Calendar.getInstance(ist);
        cal.clear();
        cal.setTimeZone(ist);
        cal.set(2026, Calendar.AUGUST, 15);              // Calendar.AUGUST is 7
        Date oldDate = cal.getTime();
        SimpleDateFormat oldFormat = new SimpleDateFormat("dd-MM-yyyy");
        oldFormat.setTimeZone(ist);
        System.out.println("Calendar.AUGUST = " + Calendar.AUGUST + ", formatted: " + oldFormat.format(oldDate));

        // 4. Converting at the boundary: Date -> Instant -> LocalDate, and back
        Instant instant = oldDate.toInstant();
        LocalDate modern = instant.atZone(ZoneId.of("Asia/Kolkata")).toLocalDate();
        System.out.println("As LocalDate: " + modern + ", day: " + modern.getDayOfWeek());
        System.out.println("Formatted the new way: "
                + modern.format(DateTimeFormatter.ofPattern("dd-MM-yyyy")));
        Date backAgain = Date.from(instant);
        System.out.println("Round trip equal? " + backAgain.equals(oldDate));
        java.sql.Date forJdbc = java.sql.Date.valueOf(modern);
        System.out.println("java.sql.Date: " + forJdbc + ", back: " + forJdbc.toLocalDate());
    }
}
Output
100 == 100: true, 1000 == 1000: false, equals: true
Array changed too: Kochi
Arrays.asList cannot grow
Calendar.AUGUST = 7, formatted: 15-08-2026
As LocalDate: 2026-08-15, day: SATURDAY
Formatted the new way: 15-08-2026
Round trip equal? true
java.sql.Date: 2026-08-15, back: 2026-08-15
  1. == on Integer compares object references. Java caches boxed values from -128 to 127, so small numbers appear to work in testing and large amounts fail in production. Always use equals, or compare int values.
  2. Arrays.asList returns a fixed-size list backed by the array: changes flow both ways, and add throws. Modern code uses List.of(...) (unmodifiable) or new ArrayList<>(List.of(...)).
  3. Calendar months start at 0 (Calendar.AUGUST is 7), and SimpleDateFormat is not thread-safe, so a shared static formatter in a web application can silently produce wrong dates under load. DateTimeFormatter is immutable and safe to share. The Date & Time lesson covers java.time in depth.
  4. Convert at the boundary. You often cannot change an old API that takes or returns Date. Convert once where you call it: date.toInstant(), Date.from(instant), java.sql.Date.valueOf(localDate) and sqlDate.toLocalDate(). Everything inside your new code then uses java.time.

Upgrading an old project

Java 8 is old but still widely deployed, and the LTS releases after it are 11, 17, 21 and 25. Upgrading brings faster garbage collectors, records, text blocks, pattern matching, virtual threads and security fixes. Teams usually upgrade like this:

  1. Get a green build and tests first. Without tests, you cannot tell whether the upgrade broke something; add characterisation tests that pin down today's behaviour.
  2. Change only the JDK first, not the code style. Build and run with the new JDK while still compiling for the old level, then raise maven.compiler.release or the Gradle toolchain.
  3. Fix what the JDK removed. Java 11 removed the Java EE and CORBA modules (JEP 320), so code using JAXB (javax.xml.bind) or JAX-WS must add those libraries as normal dependencies. Java 15 removed the Nashorn JavaScript engine (JEP 372). Java 17 strongly encapsulated JDK internals (JEP 403), so old libraries that reached into sun.* classes must be updated. Java 24 permanently disabled the Security Manager (JEP 486).
  4. Run jdeps (it ships with the JDK) on your JAR to list dependencies on JDK internals, and update old libraries, which is often the biggest part of the work.
  5. Move from javax.* to jakarta.* when upgrading frameworks: Jakarta EE 9 renamed the enterprise packages, so Spring Boot 3 and later use jakarta.servlet, jakarta.persistence and so on.
  6. Modernise gradually. Replace raw types, old collections and Date when you touch a file for another reason, following the "leave the code a little better than you found it" rule. Big-bang rewrites of working systems are risky.

You will also meet methods such as finalize() (deprecated for removal since Java 18, JEP 421; use try-with-resources or java.lang.ref.Cleaner) and Thread subclasses that override run() (prefer an ExecutorService or virtual threads).

Common mistakes

  • Rewriting everything at once. Upgrade the JDK first, keep behaviour the same, then modernise in small steps.
  • Mixing Date and LocalDate throughout new code. Convert once at the edge of the legacy API.
  • "Fixing" Integer == bugs by testing with small numbers. Values from -128 to 127 hide the bug; test with large values.
  • Assuming Vector makes code thread-safe. Each call is synchronised, but check-then-act sequences still race.
  • Suppressing warnings instead of reading them. @SuppressWarnings("unchecked") belongs only on code you have checked by hand.

Key points

  • Raw types, Vector, Hashtable, Enumeration, Stack, StringBuffer and anonymous comparators mark pre-Java 8 code; each has a modern replacement.
  • javac -Xlint:all lists raw types, unchecked calls and APIs deprecated for removal.
  • Compare boxed numbers with equals; Arrays.asList is fixed-size; Calendar months start at 0; SimpleDateFormat is not thread-safe.
  • Convert Date to Instant and LocalDate at the boundary and use java.time inside.
  • Upgrade in steps: tests, then the JDK, then removed modules and libraries, then javax to jakarta, then gradual modernisation.

Exercise

Modernise this Java 1.4-style fee report without changing its output. Use generics, Map.of, a sorted map, streams or a for-each loop, and no casts:

Java
Hashtable fees = new Hashtable();
fees.put("Rohan", new Integer(12000));
fees.put("Ananya", new Integer(0));
fees.put("Kabir", new Integer(4500));
fees.put("Meera", new Integer(8000));

Vector due = new Vector();
Enumeration keys = fees.keys();
while (keys.hasMoreElements()) {
    String name = (String) keys.nextElement();
    Integer amount = (Integer) fees.get(name);
    if (amount.intValue() > 0) {
        due.addElement(name);
    }
}
Collections.sort(due, new Comparator() {
    public int compare(Object a, Object b) {
        return ((String) a).compareTo((String) b);
    }
});

StringBuffer sb = new StringBuffer();
int total = 0;
for (int i = 0; i < due.size(); i++) {
    String name = (String) due.elementAt(i);
    int amount = ((Integer) fees.get(name)).intValue();
    sb.append(name).append(": Rs ").append(amount).append("\n");
    total += amount;
}
sb.append("Total due: Rs ").append(total);
System.out.println(sb.toString());
Show solution
Java
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;

public class FeeReport {
    public static void main(String[] args) {
        Map<String, Integer> fees = Map.of(
                "Rohan", 12000, "Ananya", 0, "Kabir", 4500, "Meera", 8000);

        Map<String, Integer> due = fees.entrySet().stream()
                .filter(e -> e.getValue() > 0)
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
                        (x, y) -> x, TreeMap::new));

        String lines = due.entrySet().stream()
                .map(e -> e.getKey() + ": Rs " + e.getValue())
                .collect(Collectors.joining("\n"));
        int total = due.values().stream().mapToInt(Integer::intValue).sum();

        System.out.println(lines + "\nTotal due: Rs " + total);
    }
}
Output
Kabir: Rs 4500
Meera: Rs 8000
Rohan: Rs 12000
Total due: Rs 24500

Both versions print exactly the same report, which you should check by running them side by side before deleting the old code. The TreeMap replaces the manual sort, the generic types remove every cast, and the old version's four compiler warnings disappear.

Quick Quiz

1. Why can Integer a = 1000, b = 1000; a == b be false?

2. What is the modern replacement for Hashtable in single-threaded code?

3. You must call an old method that returns java.util.Date. What is the best approach?

4. A project moves from Java 8 to 11 and fails because javax.xml.bind is missing. Why?

Learn Java with a mentor

Free lessons get you started. Our Java, C & C++ Programming training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call