Variables, Data Types, and Type Casting

8 min read

Every variable in Java has a type that you must declare. This is the single biggest day-one difference from JavaScript, where a variable can hold any value at any time. In Java, int passed = 28; says "this slot holds whole numbers, forever." Try to put a string in it and the compiler refuses to build. That strictness feels heavy for ten seconds and pays off for the next ten years — types are why IntelliJ can rename a method across 300 test files safely, and why your test code fails at compile time instead of at 3am in production.

Declaring a variable — the shape

The shape is always Type name = value;:

String testName = "Login Test";
int statusCode = 200;
double responseTime = 1.25;
boolean isPassed = true;

Read each one as: "this name holds this type, and its starting value is …". Once declared, the type cannot change. testName = 42; is a compile error.

In JavaScript you'd write let testName = "Login Test"; — the type is inferred, and you can later reassign testName = 42 without complaint. Java forbids this.

Primitive types — values stored directly

Java has eight primitive types. These hold the value itself in memory, not a pointer to an object. The four you'll use most in QA code:

int retries = 3;             // whole number
double price = 29.99;        // decimal number
boolean isLoggedIn = true;   // true or false
char grade = 'A';            // single character — note SINGLE quotes

The other four are corner cases:

long timestamp = 1715000000L;   // big whole number — note the L suffix
float score = 85.5f;            // less-precise decimal — note the f suffix
short hour = 23;                // small integer — rarely used
byte flag = 1;                  // tiny integer — rarely used

A few rules that matter:

  • long literals need an L suffix because Java assumes whole-number literals are int by default. Without the L, 1715000000 is fine, but 1715000000000 overflows the int range and won't compile.
  • float literals need an f suffix because Java assumes decimal literals are double. Forget the f and you'll see error: incompatible types: possible lossy conversion from double to float.
  • char uses single quotes ('A'). String uses double quotes ("A"). They are different types — 'A' is a 16-bit character, "A" is a one-character String object.

Reference types — String is the one that matters

Anything that isn't one of the eight primitives is a reference type — the variable stores a reference (a pointer) to an object somewhere in the heap. The reference type you'll use every day is String:

String url = "https://staging.myapp.com";
String env = "staging";
String userAgent = null;   // a String reference can hold null

Note the capital SString is a class, not a primitive. (Lowercase string is not a Java type and won't compile.) We'll cover Strings properly in chapter 8; for now, just know that text values go in a String.

var — let Java infer the type

Java 10 added the var keyword. It looks like JavaScript's let/const, but it's actually different: the type is still fixed at compile time — the compiler just figures it out from the right-hand side.

var count = 5;                     // inferred as int
var url = "https://x.com";         // inferred as String
var passed = true;                 // inferred as boolean
 
count = "five";  // ERROR — count is locked to int even though we used var

var is convenient for long generic types you'll meet later (Map<String, List<TestResult>>), but for simple QA code an explicit type usually reads better. A reviewer can tell at a glance that int retries holds a number; with var retries they have to chase the value to the right.

Naming conventions

Java has strong, almost-universal conventions:

  • camelCase for variables and methods: testCaseName, expectedResult, findElementById.
  • PascalCase for class names: LoginPage, TestRunner, ApiClient.
  • UPPER_SNAKE_CASE for constants: MAX_RETRIES, BASE_URL, DEFAULT_TIMEOUT.

These aren't rules the compiler enforces, but every IntelliJ inspection and code review will flag them if you break them. Lean into the conventions early.

Constants — final means "can't change"

When a value should never change after assignment, declare it final:

final int MAX_RETRIES = 3;
final String BASE_URL = "https://staging.myapp.com";
final double SLA_THRESHOLD = 2.0;
 
MAX_RETRIES = 5;   // ERROR — cannot assign a value to final variable MAX_RETRIES

final is Java's equivalent of JavaScript's const. Use it for any value that is configuration: timeouts, base URLs, retry counts. The compiler enforces immutability so a future maintainer can't accidentally reassign it halfway through a test.

Type casting — converting between types

Sometimes you need to move a value between types. There are two flavours:

Widening (small to bigger) is automatic and safe:

int retries = 3;
double retriesAsDouble = retries;   // int → double, no cast needed
System.out.println(retriesAsDouble); // 3.0

Narrowing (bigger to smaller) loses data and requires an explicit cast:

double responseTime = 1.85;
int responseTimeAsInt = (int) responseTime;  // truncates — does NOT round
System.out.println(responseTimeAsInt);       // 1, not 2

The (int) in front is the cast operator. Note that casting to int truncates (drops the decimals) — it doesn't round. To round properly, use Math.round:

double rt = 1.85;
long rounded = Math.round(rt);  // 2

String ⇄ number conversion

Reading from a CSV, an API response, or a config file usually gives you values as Strings. To use them as numbers you must parse:

String statusFromCsv = "200";
int status = Integer.parseInt(statusFromCsv);   // String → int
 
String priceFromCsv = "29.99";
double price = Double.parseDouble(priceFromCsv); // String → double

The reverse — turning a number into a String — is even simpler:

int code = 200;
String asString = String.valueOf(code);  // preferred
String alsoAsString = "" + code;         // works, a bit hacky

Integer.parseInt("not a number") throws a NumberFormatException at runtime — exception handling lives in chapter 7. For now, only call parseInt on values you know are numeric.

A QA configuration block

A real-world snippet showing every variable type pulling its weight:

public class TestConfig {
    public static void main(String[] args) {
        final String BASE_URL = "https://staging.myapp.com";
        final int MAX_RETRIES = 3;
        final double SLA_THRESHOLD = 2.0;
        final boolean IS_PRODUCTION = false;
 
        String testEnv = "staging";
        int currentRetry = 0;
        double lastResponseTime = 1.85;
        boolean isPassing = lastResponseTime < SLA_THRESHOLD;
 
        System.out.println("Env:           " + testEnv);
        System.out.println("Base URL:      " + BASE_URL);
        System.out.println("Max retries:   " + MAX_RETRIES);
        System.out.println("Last response: " + lastResponseTime + "s");
        System.out.println("SLA met?       " + isPassing);
    }
}

Output:

Env:           staging
Base URL:      https://staging.myapp.com
Max retries:   3
Last response: 1.85s
SLA met?       true

The eight primitives at a glance

The takeaway: in QA code you'll use int, double, boolean, and String about 95% of the time. The other types exist; you can mostly ignore them until a specific situation demands them.

⚠️ Common mistakes

  • Lowercase string instead of String. string name = "Alice"; does not compile. Java's text type is the class String, capital S. Same trap with Integer vs integer, Double vs double — the wrapper classes are PascalCase.
  • Forgetting the L or f suffix on numeric literals. long t = 1715000000000; fails because the literal is an int first and overflows. Fix: 1715000000000L. float score = 85.5; fails because 85.5 is a double. Fix: 85.5f.
  • Expecting (int) 1.85 to round to 2. Casting truncates — you get 1. For rounding, use Math.round(1.85) (returns a long), or (int) Math.round(1.85) if you need an int.

🎯 Practice task

Build a typed configuration block. 20-30 minutes.

  1. Create a file LoadTestConfig.java (and a class with the matching name).
  2. Inside main, declare these variables with correct types:
    • a String for the API base URL
    • a String for the environment name (e.g. "staging")
    • an int for max concurrent users
    • a double for the p95 latency SLA in milliseconds (e.g. 250.0)
    • a boolean for whether dry-run mode is on
    • a final int constant for default retries
  3. Print each value with System.out.println and a label, e.g. "Env: " + env.
  4. Read a value as a String — String maxUsersInput = "150"; — and convert it to an int with Integer.parseInt. Print both the original string and the parsed int.
  5. Read a decimal as a String — String slaInput = "2.5"; — and convert it to a double with Double.parseDouble. Cast it to an int and print both: notice the cast truncates 2.5 to 2.
  6. Compile and run with javac LoadTestConfig.java && java LoadTestConfig. Confirm the output matches your declarations.
  7. Stretch: try assigning the wrong type on purpose — int users = "150"; — and read the compiler error. Type errors at compile time are the whole point of static typing; getting comfortable reading those errors is half the skill of using a typed language.

You can now declare and convert every type you'll need for the next several chapters. The next lesson kicks off chapter 2 with control flow — if, switch, and the operators you use to build them.

// tip to track lessons you complete and pick up where you left off across devices.