DEV Community

Cover image for Bridging the Gap: Database Connectivity in C# (ADO.NET) vs. Java (JDBC)
Abdullah Al Sayed
Abdullah Al Sayed

Posted on

Bridging the Gap: Database Connectivity in C# (ADO.NET) vs. Java (JDBC)

Every real application needs to talk to a database — whether that's reading patient records, saving user orders, or fetching product listings. Two of the most popular ways to do this are C# with ADO.NET (connecting to SQL Server) and Java with JDBC (connecting to MySQL).

In this post, we'll walk through both approaches side by side using real working code. By the end, you'll understand exactly what each line does, why it's there, and how the two technologies compare. No fluff — just clear, practical explanation.

Who is this for? Developers who know basic C# or Java but haven't done database work yet, or anyone curious about how ADO.NET and JDBC compare under the hood.


Part 1 — C# ADO.NET: Reading from SQL Server

ADO.NET is Microsoft's built-in library for connecting .NET applications to databases. In this example, we connect to a local SQL Server instance and read all records from a Doctors table in a hospital management database.

The Full Code

using System;
using Microsoft.Data.SqlClient;

class Program
{
    static void Main(string[] args)
    {
        string connectionString =
            "Server=Localhost;Database=test_db;" +
            "Trusted_Connection=True;TrustServerCertificate=True";
        string query = "SELECT * FROM Doctors";
        try
        {
            using (SqlConnection connection = new SqlConnection(connectionString))
            {
                connection.Open();
                Console.WriteLine("Connected");
                SqlCommand command = new SqlCommand(query, connection);
                SqlDataReader reader = command.ExecuteReader();
                while (reader.Read())
                {
                    Console.WriteLine(
                        reader["Id"] + " " +
                        reader["Name"] + " " +
                        reader["Department"]);
                }
                reader.Close();
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Line-by-Line Explanation

Step 1 — Import the right library

using Microsoft.Data.SqlClient brings in the NuGet package that gives us all our database tools — SqlConnection, SqlCommand, SqlDataReader, and more. Think of it as opening your toolbox before you start work.

Step 2 — The Connection String

The connection string tells ADO.NET where to find the database and how to authenticate. Breaking it apart:

Part What it means
Server=Localhost Machine name + SQL Server named instance
Database=Test_db The specific database to connect to
Trusted_Connection=True Use Windows Authentication (no username/password)
TrustServerCertificate=True Skip SSL validation — fine locally, remove in production

Step 3 — using (SqlConnection ...)

The using block is one of the most important patterns in C#. It means the connection will be automatically closed and cleaned up when the block ends — even if an error occurs. This prevents connection leaks, which can silently cripple your application over time.

Step 4 — connection.Open()

This is what actually establishes the network connection to SQL Server. Until this line runs, nothing has been sent across the wire. If the server is unreachable or credentials are wrong, this is where you'll see an exception.

Step 5 — Creating and running the query

SqlCommand wraps our SQL query and ties it to our open connection. Calling ExecuteReader() sends the SELECT to SQL Server and returns a SqlDataReader — a forward-only stream of results, like a cursor pointing at the data.

Step 6 — Reading results row by row

reader.Read() moves the cursor forward one row and returns true if there's data, or false when we've reached the end. Inside the loop, we access column values by name using reader["ColumnName"] syntax — clean, readable, and intuitive.

Step 7 — Try/Catch for error handling

Database operations can fail for many reasons: network issues, bad SQL, wrong credentials, locked tables. Wrapping everything in a try/catch means we handle errors gracefully instead of crashing. In production, you'd want to log these properly rather than just printing them.

Key Takeaway: The ADO.NET pattern is: open connection → create command → execute → read results → close. The using block handles cleanup automatically.


Part 2 — Java JDBC: Connecting to MySQL

JDBC (Java Database Connectivity) is Java's standard API for connecting to relational databases. It's been part of the Java platform for decades and works with virtually any database through driver packages. In this example, we connect to a local MySQL database.

The Full Code

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

public class Main {
    public static void main(String[] args) {
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            Connection connection = DriverManager.getConnection(
                "jdbc:mysql://127.0.0.1:3306/java_practice", "root", "");
            System.out.println("Connected with mysql: " + connection.isValid(0));
        }
        catch (SQLException e) {
            e.printStackTrace();
        }
        catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Line-by-Line Explanation

Step 1 — Import JDBC classes

We import three things: DriverManager (creates connections), Connection (represents our live connection), and SQLException (thrown when something goes wrong). These all live in java.sql, which is built into the JDK — no extra library needed.

Step 2 — Class.forName(...): Loading the driver

This is the most Java-specific part of the example. Class.forName("com.mysql.cj.jdbc.Driver") dynamically loads the MySQL JDBC driver at runtime. It tells Java: "We're connecting to MySQL, here's the driver to use."

If the MySQL Connector/J JAR isn't on your classpath, this line throws ClassNotFoundException — which is why we catch it separately.

Note: In JDBC 4.0+, this line is technically optional because drivers auto-register themselves. But it's good practice to include it for clarity and compatibility.

Step 3 — DriverManager.getConnection(...): Building the connection

This is the JDBC equivalent of ADO.NET's connection.Open(). The connection string follows the JDBC URL format:

Part What it means
jdbc:mysql:// Protocol: JDBC with the MySQL driver
127.0.0.1:3306 Host (localhost) and MySQL's default port
/java_practice The database name
"root", "" Username and password — passed as separate arguments

Step 4 — connection.isValid(0)

A quick sanity check. isValid(0) pings the database to confirm the connection is alive. The argument is a timeout in seconds — 0 means no timeout. Returns true if everything is working, false otherwise.

Step 5 — Two separate catch blocks

Unlike C# which catches a single Exception, JDBC uses two distinct catches. ClassNotFoundException fires if the MySQL driver JAR is missing. SQLException fires for actual database errors. Separating them gives you much more precise error messages during debugging.

Key Takeaway: JDBC is driver-agnostic by design. Swap out the driver class name and connection URL to use PostgreSQL, Oracle, or any other database — the rest of your code stays the same.


Side-by-Side Comparison

Aspect C# ADO.NET Java JDBC
Language C# (.NET) Java
Library Microsoft.Data.SqlClient java.sql (built-in JDK)
Database SQL Server MySQL (any via drivers)
Connection new SqlConnection(connStr) DriverManager.getConnection(url, u, p)
Driver setup NuGet package, auto-registered JAR on classpath + Class.forName()
Read data SqlDataReader + reader.Read() ResultSet + rs.next()
Cleanup using block (automatic) Manual close() or try-with-resources
Error type Single Exception catch SQLException + ClassNotFoundException
Auth style Embedded in connection string Separate username/password arguments

Common Mistakes to Avoid

ADO.NET — Forgetting to close the connection
If you skip the using block and forget connection.Close(), you'll leak connections. SQL Server has a connection pool limit, and you'll eventually run out. Always use using.

JDBC — Missing the driver JAR
Getting ClassNotFoundException on the Class.forName() line? The MySQL Connector/J JAR isn't in your project. Add mysql-connector-j to your Maven/Gradle dependencies, or manually add the JAR to your classpath.

⚠️ Both — Never hardcode credentials in production
These examples hardcode server names and passwords for simplicity. In production, always load sensitive values from environment variables, a config file, or a secrets manager. Committing credentials to source control is a serious security risk.


What to Learn Next

Once you're comfortable with reading data, here's a natural progression:

  • Parameterized queries — Prevent SQL injection by using parameters instead of string concatenation. Essential for any user input.
  • INSERT, UPDATE, DELETEExecuteNonQuery() in ADO.NET and executeUpdate() in JDBC handle write operations.
  • Transactions — Group multiple operations so they all succeed or all fail together.
  • ORMs — Entity Framework Core (C#) and Hibernate (Java) let you work with database records as regular objects, hiding most of the raw boilerplate.
  • Connection pooling — Both ADO.NET and JDBC manage pools automatically, but understanding how they work under the hood will save you in production debugging.

Wrapping Up

ADO.NET and JDBC are both mature, reliable tools that follow the same fundamental pattern: get a connection → send a query → read the results → clean up. The syntax and ecosystem differ, but once you understand one, the other clicks very quickly.

The most important habits to build from day one: always close your connections, never hardcode production credentials, and always use parameterized queries before you write a single line that touches user input.

Happy coding!

Top comments (0)