// Tutorial //

Understanding Data Types in Java

Published on September 29, 2022
Default avatar
By Toli
Developer and author at DigitalOcean.
Understanding Data Types in Java

The author selected the Free and Open Source Fund to receive a donation as part of the Write for DOnations program.

Introduction

Java is a statically typed programming language. This means that when you create a variable, you must also specify its data type, which is the type of information it stores. This is in contrast to dynamically typed languages, such as PHP. With dynamically typed languages, you don’t have to specify the data type of a variable, which may seem like a relief.

However, knowing the data types and using them appropriately allows developers to optimize their code because each data type has specific resource requirements. Also, if you specify one data type and try to store a different type, such as by mistake, you won’t be able to compile the code. Thus, with statically typed languages, you can detect errors even before any testing.

Java has two data types: primitive and reference (also known as non-primitive). In this tutorial, you will use variables to store and use information in a Java program to learn about some of the commonly used data types in Java. This is not an exhaustive overview of all data types, but this guide will help you become familiar with what options are available to you in Java.

Prerequisites

To follow this tutorial, you will need:

Primitive Types

Java primitive types are the simplest and most basic data types in Java. They represent raw values such as numbers and characters. The most frequently used primitive data types are int (integers), boolean (boolean values), and char (characters). You can find the rest at the official Java data types documentation.

Integers

Integers are both negative and positive whole numbers. In Java, you’ll use int to store them. int can accommodate large enough numbers for most purposes: from -2,147,483,648 to 2,147,483,647.

Let’s look at how int is used in an example:

int theAnswer = 42;

Primitive types always start with a lowercase letter (int). Java syntax rules require that you first specify the data type (int) and then its name (theAnswer). After that, you assign the value 42 with the equals sign (=) to the variable.

Regardless of its data type, you use a variable by directly specifying its name without prepending any special characters. This is because Java can recognize it as a variable.

Note: The name of the variable theAnswer and all other variables in this tutorial are written in Camel case. Even though there is no strict requirement to use it, this is the accepted naming convention in Java.

Once you have declared the variable, you can use it by referring to it in a method like so:

int theAnswer = 42;
System.out.println("The answer to all questions is " + theAnswer);

In the second line, you print theAnswer to the console using the built-in method println from the package System.out. This is the simplest way to test a variable to ensure it is declared as expected.

To see this code in action, use the Java Shell tool. After installing Java, open a terminal or command prompt on your local computer and type jshell:

  1. jshell

Your output will look similar to the following:

Output
| Welcome to JShell -- Version 11.0.16 | For an introduction type: /help intro jshell>

You can paste the code examples from this tutorial into the console. Once you are done, you can exit jshell by typing /exit.

To declare and use int, paste the following lines into the jshell console:

  1. int theAnswer = 42;
  2. System.out.println("The answer to all questions is " + theAnswer);

You will see the following output:

Output
theAnswer ==> 42 The answer to all questions is 42

This output confirms that you set the int variable theAnswer correctly to 42 (theAnswer ==> 42). You also successfully used theAnswer by passing it to a method, and the method produced the expected variable value.

Boolean

Boolean values are true or false. In Java, you’ll use boolean to store them. For example, let’s create a boolean variable defining whether or not Java is fun:

boolean isJavaFun = true;

You define the variable isJavaFun as true. The alternative boolean value is false.

Using the above variable, you can print the sentence Java is fun: true like this:

  1. boolean isJavaFun = true;
  2. System.out.println("Java is fun: " + isJavaFun);

Running these lines in jshell will produce the following output:

Output
isJavaFun ==> true Java is fun: true

Similar to the int example, the method println will print the argument provided in the parentheses. The plus sign (+) concatenates or joins the string "Java is fun: " with the variable isJavaFun so that in reality, it is just one argument — the string, Java is fun: true.

Characters

To store a single alphanumeric character, you’ll use char. For example:

char firstLetter = 'a';

Notice that the letter a is surrounded by single quotes. Single quotes can be used only for char values. Double quotes are used for strings, as you’ll learn later.

char does not seem like a particularly useful type because it’s not likely that you will need a variable assigned to a single character. However, char is used as the building block for character string classes such as String, which are basically a collection of char values.

As you have seen in this section, the declaration and use of primitive type variables is straightforward because they represent simple values such as integers. These values are ready to be used and do not require additional operations such as creating objects, invoking methods, and so on.

Reference Types

In the first tutorial in this series, How To Write Your First Program in Java, you learned that Java code is organized in classes and that these classes are used as templates to create objects. When such objects are assigned to variables, you are pointing or referring to these objects. In these cases, the variables are classified as reference types. These variables are also known as non-primitive because primitive type variables cannot point to objects.

Objects are powerful because they have advanced properties and are able to act when you trigger their methods. However, without variables pointing to them, these objects are inaccessible and practically unusable. That’s why reference type variables are essential to Java and object-oriented programming as a whole.

Note: Reference types point to objects created from classes. To avoid confusion, the reference type and the created object will be of the same class in the following examples.

However, in complex programs, this is rarely the case. In Java, an interface is a group of requirements for a specific behavior, and these requirements can be satisfied by one or more classes. A class that satisfies the requirements of an interface is said to implement this interface. Thus, in complex programs, it’s common to declare a variable with the reference type of an interface. This way, you specify the behavior that your variable should exhibit without tying it to a concrete implementation of this behavior. This allows you to easily change which implementation your variable points to without having to change the way the variable is used. This complex concept is part of a more advanced topic about inheritance and polymorphism, which will be a separate tutorial in our Java series.

While there are only a few primitive types, reference types are practically unlimited because there is no limit to the number of classes (and interfaces), and each class stands for a reference type. There are many built-in classes in Java that provide essential functionality. The most frequently used ones are found in the core java.lang package. You’ll review some of them in this section.

The String Class

The String class represents a combination of characters that make up a string. To declare a String, or any other reference type variable, you first specify its type followed by its name. After that, you assign a value to it with the equals sign. So far, it is similar to working with primitive types. However, reference types point to objects, so you have to create an object if there hasn’t been one created yet. Here is an example:

String hello = new String("Hello");

hello is the name of the variable with reference type String. You assign it to a new String object. The new String object is created with the new keyword along with the name of the class — String in this case. The class String starts with a capital letter. By convention, all classes and therefore reference types start with a capital letter.

Each class has a special method called a constructor that is used for creating new objects. You can invoke this constructor by adding parentheses (()) at the end of the class name. The constructor may accept parameters, as in the example above, where the parameter "Hello" is applied to the constructor for String.

To confirm the hello variable behaves as expected, pass it again to the println method like this:

  1. String hello = new String("Hello");
  2. System.out.println(hello);

Running these lines in jshell will produce the following output:

Output
hello ==> "Hello" Hello

This time, the output confirms that the variable hello is set to Hello. After that, the same Hello is printed on a new line, confirming that the method println() has processed it.

Wrapper Classes

In the previous section, you worked with the String reference type, which is frequently used. Other popular reference types are the so-called wrappers for primitive types. A wrapper class wraps or contains primitive data, hence its name. All primitive types have wrapper counterparts, and here are some examples:

  • Integer: To wrap int values.
  • Character: To wrap char values.
  • Boolean: To wrap boolean values.

These wrappers exist so that you can upgrade a simple primitive value to a powerful object. Each wrapper has ready-to-use methods related to the values it is designed to store.

As an example, you’ll explore Integer. In the previous section, you created a String object with the new keyword. However, some classes provide, and even encourage, the use of special methods for acquiring objects from them, and Integer is one of them. In the case of Integer, using a special method is mostly about resource optimization, but in other cases, it could be about simplifying the construction of complex objects.

In the following example, you create an Integer variable called theAnswer with the value 42 using the valueOf method:

  1. Integer theAnswer = Integer.valueOf(42);
  2. System.out.println(theAnswer);

In jshell, you’ll get the following output:

Output
theAnswer ==> 42 42

By invoking the Integer method valueOf(42), you instruct Java to give you an object with this value. Behind the scenes, Java will check whether there is already an object with such a value in its cache. If there is, the object will be linked to theAnswer variable. If there isn’t, a new object will be created for the theAnswer variable.

Many built-in classes provide such methods for performance reasons, and their use is recommended, if not compulsory. In the case of Integer, you could still create an object with the new keyword, but you will get a deprecation warning.

In addition to String and wrappers, there are also other useful built-in reference types, which you can find at the java.lang package summary. To fully understand some of these more advanced reference types, additional explanation or prior knowledge is required. That’s why we’ll cover some of them in our next tutorials from the Java series.

Literals

Literals represent fixed values that can be used directly in the code and thus can be assigned both to primitive and reference types. There are a few types of literals, which can be categorized as follows.

Primitive Type Literals

You’ve already used a few literals in the section on primitive types. For each primitive type, there is a literal, such as the ones from our examples: 42, 'a', and true. Integers such as 42 are integer literals. Similarly, characters such as 'a' are character literals, and true and false are boolean literals.

Primitive types literals can also be used to create values for reference types. The int literal was used in creating an Integer object with the code Integer.valueOf(42). There is also a shorthand for that, and you can assign the value directly like this:

Integer theAnswer = 42;

42 is an integer literal, just as any whole number, and you can assign it directly to the theAnswer variable without any additional statements. It’s common to see an Integer declared like this because it’s convenient.

This shorthand approach also works for other primitive types literals and their counterpart reference types such as Boolean, for example:

Boolean isFun = true;

true is the literal, which is directly assigned to the isFun variable of type Boolean. There is also a false literal, which you can assign the same way.

The String Literal

There is also a special literal for the String reference type, and it is recognized by the double quotes surrounding its value. In this example, it is "Hello, World!":

String helloWorld = "Hello, World!";

Using literals is simpler and shorter, and that’s many programmers prefer it. However, you can still declare a String variable with a new String object, as you’ve already done in the section for reference types.

The Null Literal

There is one more important literal: null, which represents the absence of a value or the non-existence of an object. Null allows you to create a reference type and point it to null instead of pointing it to an object. null can be used for all reference types but not for any primitive types.

There is one caveat with the null literal: you can declare variables with it, but you cannot use these variables until you reassign a suitable, non-null value. If you try to use a reference type variable with a null value, you will get an error. Here is an example:

  1. String initiallyNullString = null;
  2. System.out.println("The class name is: " + initiallyNullString.getClass());

When you try to run this code in jshell, you will see an error similar to the following:

Output
initiallyNullString ==> null | Exception java.lang.NullPointerException | at (#4:1)

Depending on your operating system and Java version, your output may differ.

The error java.lang.NullPointerException is thrown because you are trying to invoke the String method getClass() (which returns the name of the class) on the variable initiallyNullString (which points to a null object).

Note: For simplicity, we are calling java.lang.NullPointerException an error even though it’s technically an exception. For more on exceptions and errors, check the tutorial, Exception Handling in Java.

To address the error, you have to reassign the initiallyNullString value like this:

  1. String initiallyNullString = null;
  2. initiallyNullString = "not null any longer";
  3. System.out.println("The class name is: " + initiallyNullString.getClass());

The new, fixed code will print the following output:

Output
initiallyNullString ==> null initiallyNullString ==> "not null any longer" The class name is: class java.lang.String

The above output shows how initiallyNullString is first null, then it becomes a new String object containing "not null any longer". Next, when the method getClass() is invoked on the instantiated object, you get java.lang.String, in which String is the class name and java.lang is its package. Finally, a full, meaningful message is printed: "The class name is: class java.lang.String".

Such null value declarations are more common for legacy code. They have been used to create a variable first and then later to assign its real value, usually going through some logic that determines the latter. However, since Java version 8 there is a new reference type called Optional, which is more suitable for cases in which null has been used before.

Local Variable Type Inference

Until now, you’ve been using some of the common data types in Java to define variables. However, Java 10 introduced a new feature called local variable type inference, which allows you to use the keyword var in front of a new variable. With this feature, Java will infer (that is, guess automatically) the data type from the local context. Type inference is controversial since it contrasts the previously explained verbosity of defining variables. The advantages and disadvantages of such a feature are disputable, but the fact is that other statically typed languages, such as C++, support type inference.

In any case, type inference cannot completely replace the use of data types because it works only with local variables, which are variables inside a method. Let’s look at an example with var:

  1. var hello = "Hello";
  2. System.out.println(hello);

You declare the variable hello with the var keyword in order to instruct Java to detect its data type. After that, you print it to the console in the usual way to confirm it works as expected:

Ouput
hello ==> "Hello" Hello

This example will work as long as your Java installation (more specifically, the JDK) is above version 10. The var keyword is not supported on older versions.

Type inference happens during the compilation process — that is, when you compile the code. The compilation process turns plain text source code into machine code and applies various optimizations, including type inference. This ensures that the correct amount of system memory is available for the type inferred variables. Thus, the machine code that you run after compiling is fully optimized, as if you have manually specified all the data types.

In this example, the var keyword works because the variable is local, and the var data type works only with local variables. Local variables are defined inside methods and are accessible only inside the methods, which is why they’re called “local”.

To show that var can only be used for local variables, try placing it outside the main method, like so:

  1. public class Hello {
  2. var hello = "Hello";
  3. public static void main(String[] args) {
  4. // example code
  5. }
  6. }

When you paste the above code in jshell, you will get the following error:

Output
| Error: | 'var' is not allowed here | var hello = "Hello"; | ^-^

var is not allowed there because hello is outside a method and it is no longer considered local. Thus, type inference does not work for non-local variables because the context cannot be used reliably to detect the data type.

While using var can be challenging and is not required, you will likely come across it so it is useful to know about it.

Reserved Keywords

When declaring variables in Java, there is one more important rule to know. There are reserved keywords that you cannot use for variables names. For example, you cannot declare a primitive of type int and name it new like this:

  1. int new = 1;

If you try this example, you will get compilation errors because new is a reserved keyword.

Output
| Error: | '.class' expected | int new = 1; | ^ | Error: | <identifier> expected | int new = 1; | ^ | Error: | '(' or '[' expected | int new = 1; | ^ | Error: | unexpected type | required: value | found: class | int new = 1; | ^--^ | Error: | missing return statement | int new = 1; | ^----------^

The keyword new is used for creating new objects and Java is not expecting it at this position. In the list of errors in the previous output, the first part is the most important:

Output
| Error: | '.class' expected | int new = 1; | ^

The error '.class' expected means that when you use the new keyword, Java expects that a class will follow. At this point, Java is not able to interpret the statement and the rest of the errors follow.

The rest of the reserved keywords, such as abstract, continue, default, for, and break, also have specific meanings in Java and cannot be used for variables names. The full list of the reserved keywords can be found on the Java Language Keywords page. Even if you don’t remember all the reserved keywords, you can use compilation errors to identify the issue.

Conclusion

In this tutorial, you learned about primitive and reference data types in Java, which is a complex but essential topic. Take your time to practice it and go through the examples more than once. Try changing some of the data types and values. Pay attention to when errors are thrown and when they are not in order to develop a sense for successful code execution.

If you’ve enjoyed this tutorial and our broader community, consider checking out our DigitalOcean products which can also help you achieve your development goals.

Learn more here


Tutorial Series: How To Code in Java

Java is a mature and well-designed programming language with a wide range of uses. One of its unique benefits is that it is cross-platform: once you create a Java program, you can run it on many operating systems, including servers (Linux/Unix), desktop (Windows, macOS, Linux), and mobile operating systems (Android, iOS).

About the authors
Default avatar
Toli

author

Developer and author at DigitalOcean.

Default avatar
Technical Editor

Still looking for an answer?

Was this helpful?
Leave a comment

This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!