If you've worked on a plain JDBC application with 10+ domain objects and 50+ fields each, you know the maintenance pain: rename a column and you're hunting through controllers, models and view layers fixing hardcoded strings. There's a cleaner approach using two underused Java features โ€” ResultSetMetaData and Reflection.

The Problem

A typical JDBC MVC project with significant data complexity leads to:

On a project with 50 fields per object across 10 objects, this becomes a synchronisation nightmare. One missed rename and you have a silent runtime bug.

The Solution: Let the Schema Drive the Mapping

Two Java features make this possible:

Step 1 โ€” Read column names dynamically

ResultSet rs = stmt.executeQuery("SELECT * FROM customers");
ResultSetMetaData meta = rs.getMetaData();
int columnCount = meta.getColumnCount();

List<String> columns = new ArrayList<>();
for (int i = 1; i <= columnCount; i++) {
    columns.add(meta.getColumnName(i));
}

Step 2 โ€” Map to object using Reflection

Customer customer = new Customer();
Class<?> clazz = customer.getClass();

while (rs.next()) {
    for (String col : columns) {
        // Convert "first_name" โ†’ "setFirstName"
        String setter = "set" + Character.toUpperCase(col.charAt(0))
                        + col.substring(1).replaceAll("_([a-z])", m -> m.group(1).toUpperCase());
        try {
            Method method = clazz.getMethod(setter, String.class);
            method.invoke(customer, rs.getString(col));
        } catch (NoSuchMethodException e) {
            // column has no matching setter โ€” skip gracefully
        }
    }
}

Step 3 โ€” Build SQL dynamically from schema

// Generic INSERT โ€” no hardcoded column names
StringBuilder sql = new StringBuilder("INSERT INTO " + tableName + " (");
StringBuilder values = new StringBuilder(" VALUES (");

for (int i = 1; i <= columnCount; i++) {
    sql.append(meta.getColumnName(i));
    values.append("?");
    if (i < columnCount) { sql.append(", "); values.append(", "); }
}
sql.append(")").append(values).append(")");

Step 4 โ€” View layer: gather all form fields generically

// Angular: send all form fields without hardcoding names
$scope.submit = function() {
    var payload = {};
    angular.forEach($scope.formFields, function(field) {
        payload[field.name] = field.value;
    });
    $http.post('/api/customer', payload);
};

The Payoff

With this pattern in place, adding a new column to a table means:

This saved months of work on a project with 15 entities and frequent schema changes. The initial investment in the generic mapping layer paid back within the first sprint.

Note: if you're using Spring, Spring Data JPA or MyBatis, these frameworks already do this for you. This pattern is most valuable in legacy JDBC-heavy codebases where introducing a full ORM isn't feasible.