Java Class File Format Versions

A compiled Java .class file starts with a fixed header (0xCAFEBABE), followed by a pair of numbers: minor_version and major_version. The pair (commonly written as major.minor, e.g., 52.0) identifies which Java platform level the bytecode targets. The JVM uses this to decide whether it can load the class. If the class was compiled for a newer platform than the JVM supports, you’ll get UnsupportedClassVersionError.

Why It Matters:

  • Backward compatibility: Newer JVMs can generally run older class files, but not the other way around.
  • Build reproducibility: Ensuring all modules target the same release avoids subtle runtime issues.
  • Tooling alignment: IDEs, build tools, containers, and CI images must agree on the target level to prevent version skew.

Quick mapping highlights:

  • Java 8 → 52.0
  • Java 11 → 55.0
  • Java 17 (LTS) → 61.0
  • Java 21 (LTS) → 65.0
  • Java 22 → 66.0, 23 → 67.0, 24 → 68.0, 25 → 69.0, 26 → 70.0, 27 → 71.0, 28 → 72.0
JDK Version Class File Format Version
1.0 45.0
1.1 45.3
1.2 46.0
1.3 47.0
1.4 48.0
5 49.0
6 50.0
7 51.0
8 52.0
9 53.0
10 54.0
11 55.0
12 56.0
13 57.0
14 58.0
15 59.0
16 60.0
17 61.0
18 62.0
19 63.0
20 64.0
21 65.0
22 66.0
23 67.0
24 68.0
25 69.0
26 70.0
27 71.0
28 72.0

Note:

  • Early JDK branding used 1.x (e.g., 1.5, 1.6) but these correspond to modern names 5, 6, etc. The table above reflects the modern naming for 5+.
  • There was no official 1.9 brand; Java 9 is simply 9 → 53.0 (already shown above).

How to check a class file’s version

  • Using javap (JDK tool):
    javap -v path/to/Some.class | find "major"
    

    Look for a line like major version: NN (e.g., 52 for Java 8). For modern compilers, minor is typically 0.

  • Reading the header directly (forensics style):

    1. Confirm magic bytes: CA FE BA BE.
    2. Next 2 bytes: minor_version.
    3. Next 2 bytes: major_version (e.g., 0x003D = 61 → Java 17).

How to compile for a specific Java level

  • Recommended (single flag):
    javac --release 21 -d out $(find src -name "*.java")
    

    --release consistently sets language features, APIs, and the class file version.

  • Legacy approach (not preferred, can mismatch APIs):

    javac -source 1.8 -target 1.8 -bootclasspath "%JAVA8_HOME%\\jre\\lib\\rt.jar" -extdirs ""
    
  • Maven (maven-compiler-plugin):
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>3.11.0</version>
      <configuration>
          <release>21</release>
      </configuration>
    </plugin>
    
  • Gradle (Groovy DSL):
    java {
      toolchain {
          languageVersion = JavaLanguageVersion.of(21)
      }
    }
    // Or explicitly set the target bytecode
    tasks.withType(JavaCompile).configureEach {
      options.release = 21
    }
    

Common failure and how to fix

  • Symptom:
    • java.lang.UnsupportedClassVersionError: … has been compiled by a more recent version of the Java Runtime.
  • Causes:
    • Running on an older JRE/JDK than the class file requires.
    • Mixed toolchains or inconsistent --release/target levels in a multi-module build.
  • Fixes:
    • Upgrade the runtime to meet the class file’s major.minor level; or
    • Recompile with an older target using --release <level> that matches your deployment runtime; and
    • Standardize toolchains via Maven/Gradle toolchains and CI images to avoid skew.

Tips and caveats

  • Prefer --release over -source/-target because it also validates against platform APIs for that release.
  • Preview features do not change the class file version; they require --enable-preview at compile and run time, but the mapping still follows the JDK’s version.
  • When publishing libraries, choose the lowest --release that matches your supported runtime matrix to maximize compatibility; consider multi-release JARs if you need newer APIs while keeping a baseline.

How do I handle database timeouts in JDBC?

In JDBC, “database timeouts” can mean a few different things, and you handle each at a different layer. The most practical approach is to set timeouts deliberately and then catch the right exception types so you can decide whether to retry, fail fast, or surface a user-friendly error.

1) Connection timeout (can’t connect / handshake takes too long)

a) DriverManager login timeout (global)

This limits how long DriverManager will wait when establishing a connection.

package org.kodejava.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class ConnectTimeoutExample {
    public static void main(String[] args) throws SQLException {
        DriverManager.setLoginTimeout(10); // seconds

        try (Connection c = DriverManager.getConnection(
                "jdbc:mysql://localhost/kodejava",
                "kodejava",
                "s3cr*t"
        )) {
            // connected
        }
    }
}

b) Driver-specific connect/socket timeouts (recommended)

Most drivers expose properties like connectTimeout and socketTimeout (names vary by vendor). These are often more reliable than setLoginTimeout.

Example pattern using connection properties:

package org.kodejava.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;

public class DriverPropertiesTimeoutExample {
    public static void main(String[] args) throws SQLException {
        Properties props = new Properties();
        props.setProperty("user", "kodejava");
        props.setProperty("password", "s3cr*t");

        // Vendor-specific keys; check your driver docs:
        props.setProperty("connectTimeout", "10000"); // ms (example)
        props.setProperty("socketTimeout", "30000");  // ms (example)

        try (Connection c = DriverManager.getConnection(
                "jdbc:mysql://localhost/kodejava",
                props
        )) {
            // ...
        }
    }
}

Rule of thumb: set both a connect timeout and a read/socket timeout, otherwise a query can hang at the network layer even if you set a query timeout.


2) Query execution timeout (a statement runs too long)

Use Statement.setQueryTimeout(int seconds) (works for Statement, PreparedStatement, CallableStatement). On timeout, drivers typically throw a SQLTimeoutException (a subclass of SQLException).

package org.kodejava.jdbc;

import java.sql.*;

public class QueryTimeoutExample {
    public static void main(String[] args) throws SQLException {
        try (Connection c = DriverManager.getConnection("jdbc:mysql://localhost/kodejava", "kodejava", "s3cr*t");
             PreparedStatement ps = c.prepareStatement("SELECT * FROM product WHERE price > ?")) {

            ps.setBigDecimal(1, new java.math.BigDecimal("100.00"));
            ps.setQueryTimeout(5); // seconds

            try (ResultSet rs = ps.executeQuery()) {
                while (rs.next()) {
                    // consume results
                }
            }
        } catch (SQLTimeoutException e) {
            // This is your “query took too long” bucket.
            throw new RuntimeException("Query timed out; consider optimizing SQL or raising timeout.", e);
        }
    }
}

Important notes:

  • setQueryTimeout is enforced by the driver, and behavior can differ:
    • Some drivers send a cancel to the server.
    • Some only time out client-side.
  • If the thread is interrupted, or you want a manual escape hatch, you can also call Statement.cancel() from another thread.

3) Lock wait / deadlock timeouts (transaction waits too long)

These are not “JDBC timeouts” per se—they’re database concurrency timeouts. They usually surface as SQLException with:

  • SQLState like 40001 (serialization failure / deadlock, DB-dependent), or
  • vendor-specific error codes/messages (e.g., lock wait timeout exceeded).

Handling strategy:

  • Rollback the transaction.
  • Retry only if you can safely retry (best is retrying the whole transaction), and keep attempts small with backoff.

4) Pool acquisition timeout (you can’t get a Connection from the pool)

If you use a pool (HikariCP, DBCP, c3p0, etc.), also set a connection acquisition/checkout timeout. Otherwise, under load you’ll see “timeouts” that are actually “all connections are busy.”

This is configured on the pool, not via JDBC calls.


5) Catching and classifying timeouts correctly

Catch the specific subtype when possible

JDBC provides SQLTimeoutException:

try {
    // execute query/update
} catch (SQLTimeoutException e) {
    // query timeout bucket
} catch (SQLException e) {
    // everything else
}

Use SQLState for broad categories

If you need portability, SQLState prefixes help:

  • 08xxx → connection exception family (network/connection problems)
  • 40xxx → transaction rollback / concurrency issues (often retryable depending on DB)
static boolean isConnectionProblem(SQLException e) {
    String state = e.getSQLState();
    return state != null && state.startsWith("08");
}

6) Retry policy (only for the right failures)

Retries are useful for transient failures (deadlocks, lock timeouts, brief network blips), but dangerous for non-idempotent operations.

A safe baseline:

  • Retry 2–3 times max
  • Use jittered backoff
  • Retry only when:
    • you can retry the entire transaction, or
    • the operation is idempotent

Sketch:

package org.kodejava.jdbc;

import java.sql.SQLException;
import java.sql.SQLTimeoutException;
import java.time.Duration;
import java.util.concurrent.ThreadLocalRandom;

public class RetrySupport {
    public static <T> T withRetry(SqlSupplier<T> work) throws SQLException {
        int maxAttempts = 3;
        SQLException last = null;

        for (int attempt = 1; attempt <= maxAttempts; attempt++) {
            try {
                return work.get();
            } catch (SQLTimeoutException e) {
                // Query timed out: retry is usually NOT helpful unless you expect transient load.
                throw e;
            } catch (SQLException e) {
                last = e;
                if (!isRetryable(e) || attempt == maxAttempts) throw e;

                sleep(backoff(attempt));
            }
        }
        throw last; // unreachable
    }

    private static boolean isRetryable(SQLException e) {
        String state = e.getSQLState();
        if (state != null && state.startsWith("08")) return true;  // connection hiccup
        if (state != null && state.startsWith("40")) return true;  // tx rollback class (DB-dependent)
        return false;
    }

    private static Duration backoff(int attempt) {
        long baseMs = 100L * (1L << (attempt - 1)); // 100, 200, 400...
        long jitter = ThreadLocalRandom.current().nextLong(0, 100);
        return Duration.ofMillis(baseMs + jitter);
    }

    private static void sleep(Duration d) {
        try {
            Thread.sleep(d.toMillis());
        } catch (InterruptedException ie) {
            Thread.currentThread().interrupt();
        }
    }

    @FunctionalInterface
    public interface SqlSupplier<T> {
        T get() throws SQLException;
    }
}

7) Practical checklist (what to set in real apps)

  1. Pool acquisition timeout (if using a pool)
  2. Connect timeout (driver property)
  3. Socket/read timeout (driver property)
  4. Query timeout (setQueryTimeout)
  5. For transactions:
    • keep transactions short
    • handle deadlocks/lock timeouts with rollback and bounded retry

How to Detect Deadlocks in JDBC?

Deadlocks in JDBC typically refer to database-level deadlocks, where two or more transactions block each other while waiting for locks on resources (e.g., rows or tables). JDBC itself doesn’t “detect” them proactively; instead, the database server signals them via exceptions. Here’s how to handle detection effectively:

1. Catch and Inspect SQLException

Wrap your JDBC operations (e.g., executeUpdate(), executeQuery()) in a try-catch block. When a deadlock occurs, the database driver throws an SQLException. Check its properties to confirm it’s a deadlock:

  • SQLState: A standard code (e.g., starts with “40” for serialization failures like deadlocks in many databases).
  • Error Code: Vendor-specific (e.g., database-dependent numbers).
  • Message: Often contains keywords like “deadlock” or “lock wait timeout”.

Example in Java:

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public void performDatabaseOperation(Connection conn) {
    try (PreparedStatement stmt = conn.prepareStatement("UPDATE table SET column = ? WHERE id = ?")) {
        stmt.setString(1, "value");
        stmt.setInt(2, 1);
        stmt.executeUpdate();
    } catch (SQLException e) {
        if (isDeadlock(e)) {
            // Handle deadlock: e.g., retry the transaction or log it
            System.out.println("Deadlock detected: " + e.getMessage());
            // Optional: retry logic here
        } else {
            throw new RuntimeException("Database error", e);
        }
    }
}

private boolean isDeadlock(SQLException e) {
    String sqlState = e.getSQLState();
    int errorCode = e.getErrorCode();

    // Common checks (adapt to your database)
    if (sqlState != null) {
        if (sqlState.startsWith("40")) { // General serialization failure (deadlock/timeout)
            return true;
        }
    }

    // Database-specific error codes
    // MySQL example: Deadlock (1213) or lock wait timeout (1205)
    if (errorCode == 1213 || errorCode == 1205) {
        return true;
    }
    // PostgreSQL example: 40P01 for deadlock
    // Oracle example: ORA-00060 (error code 60)

    return false; // Not a deadlock
}

2. Database-Specific Detection

Error codes vary by database—always check your DBMS docs for exact values:

  • MySQL: Error code 1213 (deadlock) or 1205 (lock wait timeout). SQLState “40001” or “HY000”.
  • PostgreSQL: SQLState “40P01”.
  • Oracle: Error code 60 (ORA-00060).
  • SQL Server: Error code 1205.

If using Spring Data JPA (from your project stack), exceptions are wrapped in DataAccessException subclasses like ConcurrencyFailureException. You can catch those for higher-level handling.

3. Prevention and Retry Strategies

  • Use Transactions Wisely: Keep them short, use appropriate isolation levels (e.g., READ_COMMITTED via conn.setTransactionIsolation(...)).
  • Retry Logic: For transient deadlocks, retry the entire transaction (e.g., 2-3 times with exponential backoff). Ensure operations are idempotent.
  • Monitoring: Enable database logging or use tools like Java’s ThreadMXBean for thread-level deadlocks (unrelated to DB), but for DB deadlocks, rely on JDBC exceptions.