Skip to content

Java

Java Logo

People complain about Java too verbose, too enterprise y, too slow. But here's the truth: it runs on billions of devices, powers enterprise systems, and underlies Android. Working in security means you'll hit Java in web apps, Android apps, enterprise systems, and security tools.

Why Java Matters for Security:

Enterprise Dominance: Most Fortune 500 companies run Java applications. Spring Framework, Hibernate, enterprise Java stacks they're everywhere. When you're testing enterprise environments, you're testing Java applications.

Android Security: Android apps are primarily Java (or Kotlin, which compiles to the JVM). Understanding Java helps you understand Android security, reverse engineer apps, and find vulnerabilities.

Web Application Security: Java web applications have unique security characteristics: - Deserialization vulnerabilities (especially with Java's serialization mechanism) - Enterprise frameworks (Spring, Struts) have their own security patterns - The JVM's security model affects how attacks work

This Cheatsheet's Focus:

Java is big the language and ecosystem are massive. We'll focus on: - Fundamentals (just enough to read and write Java) - Security relevant features (serialization, reflection, class loading) - Common vulnerabilities and how to avoid them - Practical examples for security work

You don't need to become a Java architect, but understanding Java will make you a better security professional.

Table of Contents

Part 1: Your First Java Program

1. Setting Up Your Environment-(JDK)

Before you can write Java code, you need the Java Development Kit (JDK). Not the JRE (Java Runtime Environment) you need the full JDK which includes the compiler.

What's in the JDK: - javac: The Java compiler (converts .java to .class bytecode) - java: The JVM runtime (executes bytecode) - Standard library: The massive collection of built in classes - Tools: jar, javadoc, debugging tools, etc.

Which JDK to Use:

Recommendation: Use an open source build of the JDK. Eclipse Temurin (formerly AdoptOpenJDK) is popular and well supported. Download the latest LTS (Long-Term Support) version JDK 17 or JDK 21 are good choices.

Why Not Oracle JDK? Oracle JDK has licensing restrictions for commercial use. For learning and security work, open source distributions are fine.

Installation Options: - Linux/macOS: Use SDKMAN! (curl -s "https://get.sdkman.io" | bash) to easily install and switch between Java versions - Windows: Download the installer from Eclipse Temurin or use Chocolatey (choco install temurin17) - Package Managers: Most Linux distributions include OpenJDK in their repositories

Verify Installation:

java -version    # Should show version info
javac -version   # Should show compiler version

If both commands work, you're ready to go.

2. Hello, World!: The Classic Entry Point

In Java, all code must reside inside a class. The entry point for any Java application is a main method with a specific signature.

HelloWorld.java

// A class is a blueprint for creating objects. 
// Every Java application has at least one class.
public class HelloWorld {

    // This is the main method. The JVM looks for this exact signature to start the program.
    // `public`: It can be called from anywhere.
    // `static`: It belongs to the class, not an instance of the class.
    // `void`: It doesn't return any value.
    // `main`: The name of the method.
    // `String[] args`: An array of strings for command line arguments.
    public static void main(String[] args) {
        // `System.out.println()` prints a line of text to the console.
        System.out.println("Hello, World!");
    }
}

3. Compiling and Running (javac &-java)

Java is a compiled language. You first compile your .java source file into .class bytecode, and then the JVM executes the bytecode.

  1. Save your code in a file named HelloWorld.java. The filename must match the public class name.
  2. Compile the code using javac:
    javac HelloWorld.java
    
    This will create a HelloWorld.class file.
  3. Run the compiled code using java:
    java HelloWorld
    
    (Note: You don't include the .class extension when running).

Output:

Hello, World!

Part 2: Java Language Fundamentals

4. Variables and Primitive Data Types

Java has two kinds of types: primitive types and reference types (objects).

Primitive Types: These are the most basic data types.

  • byte: 8-bit integer.
  • short: 16-bit integer.
  • int: 32-bit integer (most common).
  • long: 64-bit integer.
  • float: 32-bit floating point number.
  • double: 64-bit floating point number (most common for decimals).
  • boolean: true or false.
  • char: A single 16-bit Unicode character.
public class Variables {
    public static void main(String[] args) {
        // Declaration: type name;
        int score;
        // Initialization: name = value;
        score = 100;

        // Declaration and initialization
        double price = 19.99;
        boolean isAdmin = false;
        char initial = 'A';

        // Since Java 10, you can use `var` for local variable type inference.
        var playerName = "Gopher"; // The compiler infers this is a String

        System.out.println("Player: " + playerName);
        System.out.println("Score: " + score);
    }
}

5. Operators

Java has standard operators you'd expect from C-like languages, with some important differences from C/C++ regarding pointers and memory operations (Java doesn't have those that's intentional).

Operator Categories: - Arithmetic: +, -, *, /, % (modulus) Basic math operations - Comparison: ==, !=, >, <, >=, <= Compare values (but be careful with == for objects!) - Logical: && (AND), || (OR), ! (NOT) Boolean logic - Assignment: =, +=, -=, etc. Store values in variables - Bitwise: &, |, ^, ~, <<, >> Manipulate individual bits (less common in Java than C)

6. Control Flow (if, for, while,-switch)

public class ControlFlow {
    public static void main(String[] args) {
        // if else if else
        int score = 85;
        if (score >= 90) {
            System.out.println("Excellent");
        } else if (score >= 80) {
            System.out.println("Good");
        } else {
            System.out.println("Keep trying");
        }

        // for loop
        for (int i = 0; i < 5; i++) {
            System.out.print(i + " ");
        }
        System.out.println();

        // while loop
        int count = 0;
        while (count < 5) {
            System.out.print(count + " ");
            count++;
        }
        System.out.println();

        // switch statement (works with byte, short, char, int, enums, String)
        String level = "Admin";
        switch (level) {
            case "Admin":
                System.out.println("Full access");
                break;
            case "User":
                System.out.println("Limited access");
                break;
            default:
                System.out.println("No access");
                break;
        }
    }
}

7. Arrays

An array is a fixed size container that holds elements of the same type.

public class ArraysExample {
    public static void main(String[] args) {
        // Declare and initialize an array of integers
        int[] numbers = {10, 20, 30, 40, 50};

        // Access an element by its index (0-based)
        System.out.println("The first element is: " + numbers[0]);

        // Get the length of the array
        System.out.println("The array length is: " + numbers.length);

        // Iterate over an array using an enhanced for loop
        for (int number : numbers) {
            System.out.print(number + " ");
        }
        System.out.println();
    }
}

Part 3: Object Oriented Programming (The Heart of-Java)

Java is fundamentally object oriented. Everything revolves around classes and objects.

8. Classes and Objects

  • A Class is a blueprint or template for creating objects.
  • An Object is an instance of a class.
// This is the blueprint for a User
public class User {
    // Fields (or instance variables) store the state of the object
    String username;
    boolean isActive;
}

public class App {
    public static void main(String[] args) {
        // Create an object (an instance of the User class)
        User user1 = new User();
        user1.username = "alice";
        user1.isActive = true;

        User user2 = new User();
        user2.username = "bob";

        System.out.println("User 1: " + user1.username);
        System.out.println("User 2: " + user2.username);
    }
}

9. Constructors

A constructor is a special method that is called when an object is created. It's used to initialize the object's state.

public class User {
    String username;
    String email;

    // This is a constructor. It has the same name as the class.
    public User(String username, String email) {
        // Use the `this` keyword to refer to the current object's fields
        this.username = username;
        this.email = email;
    }
}

// In another file...
// Now we can create a user in one line
User user1 = new User("alice", "alice@example.com");

10. Methods, Fields, and this

  • Fields are variables inside a class (state).
  • Methods are functions inside a class (behavior).
  • The this keyword is a reference to the current object.
public class BankAccount {
    private double balance; // field

    public BankAccount(double initialBalance) {
        this.balance = initialBalance;
    }

    // A method to deposit money
    public void deposit(double amount) {
        if (amount > 0) {
            this.balance += amount;
        }
    }

    // A method to get the current balance
    public double getBalance() {
        return this.balance;
    }
}

11. Access Modifiers (public, private,-etc.)

Access modifiers control the visibility of fields, methods, and classes.

  • public: Accessible from anywhere.
  • private: Only accessible within the same class. This is key for encapsulation—hiding the internal state of an object and exposing it only through public methods.
  • protected: Accessible within the same package and by subclasses.
  • default (no modifier): Accessible only within the same package.

Best Practice: Make fields private and provide public methods (getters and setters) to access or change them. This gives you control over your object's state.

12. static vs. Instance Members

  • Instance members (fields and methods) belong to an object. Each object has its own copy.
  • static members belong to the class itself. There is only one copy, shared by all objects of that class. You don't need to create an object to access them.
public class MathUtil {
    public static final double PI = 3.14159; // A static constant

    public static int add(int a, int b) { // A static method
        return a + b;
    }
}

// In another file...
int sum = MathUtil.add(5, 10);
System.out.println("Pi is: " + MathUtil.PI);

13. Inheritance-(extends)

Inheritance allows a class (the subclass) to inherit fields and methods from another class (the superclass).

// Superclass
public class Animal {
    public void eat() {
        System.out.println("This animal eats food.");
    }
}

// Subclass
public class Dog extends Animal {
    // The Dog class automatically has the eat() method.

    // We can also override methods
    @Override
    public void eat() {
        System.out.println("The dog eats kibble.");
    }

    public void bark() {
        System.out.println("Woof!");
    }
}

14. Polymorphism and Abstract Classes

Polymorphism means "many forms." It allows you to use a superclass reference to refer to a subclass object.

An Abstract Class is a class that cannot be instantiated. It's meant to be subclassed. It can have abstract methods (methods without a body) that subclasses must implement.

abstract class Shape { // Cannot do `new Shape()`
    // An abstract method no body
    public abstract double getArea();

    public void printDescription() {
        System.out.println("This is a shape.");
    }
}

class Circle extends Shape {
    private double radius;
    // ... constructor ...

    @Override
    public double getArea() {
        return Math.PI * radius * radius;
    }
}

// In main...
Shape myShape = new Circle(10.0); // Polymorphism!
System.out.println("Area: " + myShape.getArea()); // Calls the Circle's getArea() method
myShape.printDescription(); // Calls the Shape's method

15. Interfaces-(implements)

An Interface is a completely abstract type. It can only contain method signatures and constants. A class implements an interface, promising to provide an implementation for all of its methods.

A class can extend only one superclass, but it can implements multiple interfaces.

// An interface defining a behavior
interface Loggable {
    String getLogMessage();
}

class User implements Loggable {
    private String username;
    // ...

    @Override
    public String getLogMessage() {
        return "User logged in: " + this.username;
    }
}

class SystemEvent implements Loggable {
    private String event;
    // ...

    @Override
    public String getLogMessage() {
        return "System event: " + this.event;
    }
}

// A method that can work with any object that is Loggable
public void writeToLog(Loggable item) {
    String message = item.getLogMessage();
    // ... write message to a log file ...
}

Part 4: The Java Standard Library

16. The String Class

Strings in Java are objects, and they are immutable. Once a String object is created, it cannot be changed.

String greeting = "Hello";
String name = "World";

// This does not change the original string. It creates a new one.
String message = greeting + ", " + name + "!"; // "Hello, World!"

System.out.println(message.length());      // 13
System.out.println(message.toUpperCase()); // "HELLO, WORLD!"
System.out.println(message.substring(0, 5)); // "Hello"

17. The Collections Framework (List, Set,-Map)

This is Java's equivalent of data structures like Python lists and dictionaries.

  • List: An ordered collection. Allows duplicates. The most common implementation is ArrayList.
  • Set: An unordered collection of unique elements. The most common implementation is HashSet.
  • Map: A collection of key value pairs. Keys must be unique. The most common implementation is HashMap.
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class CollectionsExample {
    public static void main(String[] args) {
        // List
        List<String> names = new ArrayList<>();
        names.add("Alice");
        names.add("Bob");
        names.add("Alice"); // Duplicates are allowed
        System.out.println("First name: " + names.get(0));

        // Map
        Map<String, Integer> scores = new HashMap<>();
        scores.put("Alice", 100);
        scores.put("Bob", 95);
        System.out.println("Bob's score: " + scores.get("Bob"));

        // Iterating
        for (String name : names) {
            System.out.println("Name: " + name);
        }

        for (Map.Entry<String, Integer> entry : scores.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
    }
}

18. Exception Handling (try catch-finally)

Java uses exceptions to handle errors. When an error occurs, an Exception object is "thrown."

  • Checked Exceptions: Exceptions that the compiler forces you to handle (e.g., IOException). You must either catch them with try catch or declare that your method throws them.
  • Unchecked Exceptions (RuntimeExceptions): Exceptions that you are not required to handle, often indicating a programming error (e.g., NullPointerException, ArrayIndexOutOfBoundsException).
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

public class ExceptionExample {
    public static void main(String[] args) {
        try {
            readFile("non_existent_file.txt");
        } catch (FileNotFoundException e) {
            System.err.println("Error: File could not be found.");
            e.printStackTrace(); // Prints the full error stack trace
        }
    }

    public static void readFile(String fileName) throws FileNotFoundException {
        File file = new File(fileName);
        Scanner scanner = null;
        try {
            scanner = new Scanner(file);
            while (scanner.hasNextLine()) {
                System.out.println(scanner.nextLine());
            }
        } finally {
            // The `finally` block always executes, whether an exception occurred or not.
            // It's perfect for cleanup, like closing resources.
            if (scanner != null) {
                scanner.close();
            }
        }
    }
}

19. File I/O with NIO

While older java.io classes exist, the modern way to handle files is with the java.nio.file package (NIO New I/O).

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;

public class FileIOExample {
    public static void main(String[] args) throws IOException {
        Path path = Paths.get("my file.txt");

        // Write to a file
        String content = "Hello from Java NIO!";
        Files.write(path, content.getBytes());

        // Read all lines from a file
        List<String> lines = Files.readAllLines(path);
        System.out.println(lines);
    }
}

Part 5: Building and Managing Projects

20. Understanding JAR Files

A JAR (Java Archive) file is the standard way to package a Java application. It's essentially a ZIP file containing your compiled .class files, resources (like images or config files), and a manifest file.

An executable JAR is one that can be run directly using java -jar myapp.jar. It requires a Main-Class attribute in its manifest file to specify the entry point.

21. Introduction to Maven

Maven is a powerful build automation and dependency management tool. It uses a pom.xml file to define the project.

  • Dependency Management: Maven automatically downloads the libraries (JARs) your project needs from a central repository.
  • Standard Lifecycle: It defines a standard lifecycle for building code: validate, compile, test, package, install, deploy.

pom.xml example:

<project xmlns="http://maven.apache.org/POM/4.0.0" ...>
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.mycompany.app</groupId>
  <artifactId>my app</artifactId>
  <version>1.0-SNAPSHOT</version>

  <properties>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
  </properties>

  <dependencies>
    <!-- This adds the JUnit 5 testing library to our project -->
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit jupiter api</artifactId>
      <version>5.8.2</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

Common Commands: * mvn compile: Compiles the source code. * mvn test: Runs the tests. * mvn package: Compiles, tests, and packages the code into a JAR file in the target directory.

22. Introduction to Gradle

Gradle is a more modern alternative to Maven. It uses a Groovy or Kotlin based DSL for its build scripts (build.gradle), which are often more concise and flexible than Maven's XML.

build.gradle (Groovy DSL) example:

plugins {
    id 'java'
}

group 'com.mycompany.app'
version '1.0-SNAPSHOT'

sourceCompatibility = '17'

repositories {
    mavenCentral()
}

dependencies {
    testImplementation 'org.junit.jupiter:junit jupiter api:5.8.2'
    testRuntimeOnly 'org.junit.jupiter:junit jupiter engine:5.8.2'
}

test {
    useJUnitPlatform()
}

Common Commands: * ./gradlew build: Compiles, tests, and builds the project. * ./gradlew test: Runs the tests.

Part 6: Java Security

Java's enterprise nature means it's often used in high stakes applications, making security paramount. Here are some of the most critical vulnerabilities and how to prevent them.

23. Input Validation

Never trust data that comes from outside your application. This includes user form data, API requests, query parameters, etc. Always validate it.

  • Allow listing: Only accept known good values (e.g., if (currency.equals("USD") || currency.equals("EUR"))).
  • Format/Type: Ensure the data is in the expected format (e.g., using regex for a UUID) and can be parsed to the correct type.
  • Range: Check that numbers fall within an expected range.

24. Preventing SQL Injection with JDBC

SQL Injection is a classic attack where an attacker manipulates a database query by injecting malicious SQL. It happens when you build queries by concatenating strings.

WRONG WAY (Vulnerable):

String username = request.getParameter("username"); // Attacker provides: ' OR 1=1 --
String query = "SELECT * FROM users WHERE username = '" + username + "'";
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(query); // The query becomes: SELECT * FROM users WHERE username = '' OR 1=1 --

RIGHT WAY (Secure):

Always use PreparedStatement. It separates the query logic from the data, making injection impossible.

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

// ...

String username = request.getParameter("username");

// The `?` is a placeholder for the data.
String query = "SELECT * FROM users WHERE username = ?";

PreparedStatement pstmt = connection.prepareStatement(query);

// Set the parameter value safely. The driver handles escaping.
pstmt.setString(1, username);

ResultSet rs = pstmt.executeQuery();

25. The Dangers of Insecure Deserialization

This is one of the most dangerous vulnerability classes in Java. Deserialization is the process of turning a stream of bytes back into an object. If an attacker can control the byte stream, they can potentially instantiate arbitrary classes in your application, leading to Remote Code Execution (RCE).

The Culprit: java.io.ObjectInputStream.

Rule of thumb: Do not deserialize untrusted data with ObjectInputStream.

Safer Alternatives: * Use a data only format like JSON. Libraries like Jackson or Gson are the standard for this. They deserialize data into simple Java objects (POJOs) and don't have the same RCE risks.

// Using Jackson to safely deserialize JSON
import com.fasterxml.jackson.databind.ObjectMapper;

// ...

String jsonData = request.getReader().lines().collect(Collectors.joining());
ObjectMapper mapper = new ObjectMapper();

// This is safe because it only maps data to the fields of the User class.
// It cannot instantiate arbitrary classes or execute code.
User user = mapper.readValue(jsonData, User.class);

26. Preventing XML External Entity-(XXE)

If your application parses XML, it may be vulnerable to XXE. An attacker can embed entities in the XML that reference external files or URLs. The XML parser might then access these resources, leading to information disclosure or Server Side Request Forgery (SSRF).

WRONG WAY (Vulnerable):

// A default XML parser is often vulnerable
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(untrustedInputStream); // Attacker's XML is parsed

RIGHT WAY (Secure):

Explicitly disable the features that allow XXE.

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.XMLConstants;

// ...

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

// This is the most important part: disable external entities.
dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
dbf.setFeature("http://apache.org/xml/features/disallow doctype decl", true);
dbf.setFeature("http://xml.org/sax/features/external general entities", false);
dbf.setFeature("http://xml.org/sax/features/external parameter entities", false);

DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(untrustedInputStream); // Now it's safe

27. Secure Command Execution

Executing OS commands based on user input is risky. Avoid it if possible. If you must, use ProcessBuilder and never pass user input directly to a shell.

WRONG WAY (Vulnerable):

// This is vulnerable if `filename` contains special characters like `;` or `&`
Runtime.getRuntime().exec("/bin/sh -c ls " + filename);

RIGHT WAY (Secure):

Pass the command and its arguments as a list. This avoids shell interpretation.

import java.io.IOException;

// ...

String filename = "some;file.txt"; // Example of tricky input

// The filename is passed as a single, safe argument.
// The shell is not involved.
ProcessBuilder pb = new ProcessBuilder("ls", "-la", filename);

try {
    Process process = pb.start();
    // ... handle process output ...
} catch (IOException e) {
    e.printStackTrace();
}

28. Cryptography-(JCA/JCE)

Java has a powerful crypto architecture (JCA/JCE). Always use well vetted libraries and standard algorithms.

  • Password Hashing: Don't roll your own. Use a library like Spring Security or Bouncy Castle which provide a BCryptPasswordEncoder.
  • Random Numbers: For any security purpose (keys, tokens, salts), always use java.security.SecureRandom.
import java.security.SecureRandom;

// ...

SecureRandom random = new SecureRandom();
byte[] values = new byte[20];
random.nextBytes(values); // Fills the array with cryptographically strong random bytes

Part 7: Cookbook

29. Cookbook: Command Line File Hasher

This tool calculates the SHA-256 hash of a file provided as a command line argument.

import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class FileHasher {
    public static void main(String[] args) throws IOException, NoSuchAlgorithmException {
        if (args.length != 1) {
            System.err.println("Usage: java FileHasher <file path>");
            System.exit(1);
        }

        Path filePath = Paths.get(args[0]);
        if (!Files.exists(filePath)) {
            System.err.println("Error: File not found at " + filePath);
            System.exit(1);
        }

        MessageDigest sha256 = MessageDigest.getInstance("SHA-256");

        try (FileInputStream fis = new FileInputStream(filePath.toFile())) {
            byte[] buffer = new byte[8192];
            int bytesRead;
            while ((bytesRead = fis.read(buffer)) != -1) {
                sha256.update(buffer, 0, bytesRead);
            }
        }

        byte[] hashBytes = sha256.digest();

        // Convert byte array to hex string
        StringBuilder hexString = new StringBuilder();
        for (byte b : hashBytes) {
            hexString.append(String.format("%02x", b));
        }

        System.out.println(hexString.toString() + "  " + filePath.getFileName());
    }
}

30. Cookbook: Basic REST API with Javalin

Javalin is a very lightweight and simple web framework for Java. This example shows a minimal API for managing users.

First, add the dependency to your pom.xml (Maven) or build.gradle (Gradle).

Maven pom.xml:

<dependencies>
    <dependency>
        <groupId>io.javalin</groupId>
        <artifactId>javalin</artifactId>
        <version>5.3.2</version>
    </dependency>
</dependencies>

API Code:

import io.javalin.Javalin;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

public class UserApi {

    static class User {
        public String name;
        public String email;
    }

    public static void main(String[] args) {
        // Use a thread safe map as a simple in memory database
        ConcurrentHashMap<Integer, User> users = new ConcurrentHashMap<>();
        AtomicInteger lastId = new AtomicInteger();

        Javalin app = Javalin.create().start(7070);

        // GET /users List all users
        app.get("/users", ctx -> {
            ctx.json(users);
        });

        // GET /users/{id} Get a specific user
        app.get("/users/{id}", ctx -> {
            int id = Integer.parseInt(ctx.pathParam("id"));
            User user = users.get(id);
            if (user != null) {
                ctx.json(user);
            } else {
                ctx.status(404).result("User not found");
            }
        });

        // POST /users Create a new user
        app.post("/users", ctx -> {
            User newUser = ctx.bodyAsClass(User.class);
            int id = lastId.incrementAndGet();
            users.put(id, newUser);
            ctx.status(201).result("User created with ID: " + id);
        });
    }
}

31. Cookbook: Secure Database Client with JDBC

This example connects to an in memory H2 database and performs secure CRUD operations.

Maven pom.xml:

<dependencies>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <version>2.1.214</version>
    </dependency>
</dependencies>

JDBC Code:

import java.sql.*;

public class DatabaseClient {
    private static final String DB_URL = "jdbc:h2:mem:testdb"; // In memory database

    public static void main(String[] args) throws SQLException {
        try (Connection conn = DriverManager.getConnection(DB_URL)) {
            System.out.println("Connected to H2 database.");

            // 1. Create Table
            try (Statement stmt = conn.createStatement()) {
                stmt.execute("CREATE TABLE employees (id INT PRIMARY KEY, name VARCHAR(255))");
                System.out.println("Table created.");
            }

            // 2. Create (Insert) Data Securely
            String insertSql = "INSERT INTO employees (id, name) VALUES (?, ?)";
            try (PreparedStatement pstmt = conn.prepareStatement(insertSql)) {
                pstmt.setInt(1, 1);
                pstmt.setString(2, "Alice");
                pstmt.executeUpdate();

                pstmt.setInt(1, 2);
                pstmt.setString(2, "Bob");
                pstmt.executeUpdate();
                System.out.println("Inserted 2 records.");
            }

            // 3. Read (Select) Data Securely
            String selectSql = "SELECT id, name FROM employees WHERE id = ?";
            try (PreparedStatement pstmt = conn.prepareStatement(selectSql)) {
                pstmt.setInt(1, 1);
                try (ResultSet rs = pstmt.executeQuery()) {
                    if (rs.next()) {
                        System.out.println("Found employee: ID=" + rs.getInt("id") + ", Name=" + rs.getString("name"));
                    }
                }
            }

            // 4. Update Data Securely
            String updateSql = "UPDATE employees SET name = ? WHERE id = ?";
            try (PreparedStatement pstmt = conn.prepareStatement(updateSql)) {
                pstmt.setString(1, "Robert");
                pstmt.setInt(2, 2);
                int rowsAffected = pstmt.executeUpdate();
                System.out.println("Updated " + rowsAffected + " record(s).");
            }

            // 5. Delete Data Securely
            String deleteSql = "DELETE FROM employees WHERE id = ?";
            try (PreparedStatement pstmt = conn.prepareStatement(deleteSql)) {
                pstmt.setInt(1, 1);
                int rowsAffected = pstmt.executeUpdate();
                System.out.println("Deleted " + rowsAffected + " record(s).");
            }
        }
    }
}

Sources