Data Types
Java is a strongly typed language — every variable must declare what kind of data it holds before it can hold anything. That constraint isn't bureaucracy; it's the compiler protecting you from a whole class of bugs that would otherwise only appear at runtime, on a robot, in a match.
By the end of this lesson, you will:
- Name all eight Java primitive types and describe what each stores
- Explain the difference between primitive types and reference types, and identify which category
Stringbelongs to - Choose the correct type for a given robot value — motor speed, port number, sensor flag, or dashboard label
- Perform widening and narrowing type conversions, and predict where data loss occurs
- Recognize the three most common type-related bugs in FRC code: the
float/doubleprecision trap, integer division truncation, andintoverflow
The Two Families of Types
Before diving into the eight individual primitive types, it helps to understand the fundamental split in Java's type system. Every type you'll use falls into one of two categories, and the difference between them matters when you start working with WPILib objects and sensor data.
Store a raw value directly in memory. Fast, fixed-size, always have a value (never null). Java has exactly eight of them.
- The value is the variable
- Lowercase names:
int,double,boolean… - Cannot call methods on them
- Default to zero,
false, or'\u0000'
Store a reference (memory address) to an object. Can be null. Created with new, or as string literals.
- The variable points to the value
- Capitalized names:
String,TalonFX,XboxController… - Can call methods:
str.length() - Default to
null— calling a method onnullcrashes
For most of Unit 2, you'll work with primitives. Reference types become central in Unit 4 (Object-Oriented Programming), but you'll encounter them immediately when you create a motor controller or a joystick object in Unit 5 — those are all reference types.
The Eight Primitive Types
Java has exactly eight primitive types. You won't use all of them in FRC code, but you need to know they exist and why some were designed the way they were. Click any type below to see its size, range, and — most importantly — when and why you'd reach for it on a robot.
The roboRIO runs on a dual-core ARM processor with 256 MB of RAM. That's not a lot by modern standards. While a few extra long variables won't crash the robot, understanding size is what lets you read WPILib source code and vendor library APIs confidently — you'll see methods return double instead of float, and you'll know exactly why.
String: The Reference Type You'll Use Constantly
Even though String is a reference type — not a primitive — it gets special treatment in Java and appears in almost every FRC program. You'll use it for Shuffleboard labels, fault messages, chooser options, and CAN bus names.
Three things about String that trip people up:
1. Compare with .equals(), not ==
For primitives, == compares values. For reference types including String, == compares memory addresses — whether two variables point to the exact same object in memory. This almost never does what you intend. Use .equals() to compare the actual text.
2. Strings are immutable
You cannot change a String after it's created. Every operation that looks like it modifies a string — +, .toUpperCase(), .trim() — actually creates a new String object. The original is untouched. This matters in robot code that builds log messages in a loop: constructing strings with + inside a periodic method creates a new object every 20ms. For performance-sensitive logging, WPILib has better tools (covered in Unit 6).
3. Concatenation with +
You can attach any value to a String using +. Java will automatically convert the non-string value to text. This is useful for dashboard output and debugging.
Type Casting: Moving Between Types
Sometimes you have a value of one type and need it in another. Java handles this in two ways depending on whether information might be lost in the conversion.
Widening converts a smaller type to a larger one — like pouring a cup of water into a bucket. No information is lost, so Java does it automatically without any syntax from you.
The widening chain goes: byte → short → int → long → float → double
You'll see widening conversion in WPILib every time you pass an int port number into a method that expects a double, or when math involving both types produces a double result automatically.
Narrowing converts a larger type to a smaller one — like pouring a bucket into a cup. Information might be lost, so Java requires you to write an explicit cast using parentheses. The compiler is making you acknowledge the risk.
The rule of thumb: before you cast narrowing, ask yourself "do I know for certain this value fits in the target type?" If the answer is "probably," that's not certain enough. A value that overflows an int or truncates unexpectedly can produce motor commands that send a mechanism in the wrong direction at full speed.
The Three Type Traps in FRC Code
These three mistakes are common enough that they have their own names in the 2910 shop. All three compile without errors, all three produce incorrect robot behavior, and all three are preventable by choosing types carefully.
Trap 1: Integer Division Truncation
When you divide two int values in Java, the result is also an int — the decimal portion is thrown away silently. This catches almost every programmer at least once.
Trap 2: The float / double Precision Gap
Both float and double are floating-point types, but they store very different amounts of precision. float has about 7 significant decimal digits; double has about 15. WPILib's entire API is built on double. Mixing in float introduces precision errors that are essentially invisible until you try to tune a PID controller and the gains behave strangely.
I've watched teams spend a full evening trying to tune a PID loop that refused to settle — adjusting kP, kI, kD over and over, seeing the mechanism oscillate no matter what they tried. The root cause turned out to be float constants being implicitly widened to double when passed to WPILib, introducing accumulated precision error into every feedback calculation. Switching every gain to double fixed the oscillation in one test. Use double everywhere. There is no FRC scenario where float is the right choice.
Trap 3: int Overflow
An int can store values up to about 2.1 billion. That sounds enormous, but some FRC calculations get there faster than you'd expect — particularly raw encoder tick counts, timestamps in microseconds, or accumulated error in a manual integration loop. When an int exceeds its maximum, it doesn't crash: it silently wraps around to a large negative number. The robot keeps running. The math just starts producing nonsense.
A team was logging match timestamps using an int variable counting microseconds elapsed since robot enable. During a three-hour practice session — not a match, but a long pit test — the counter overflowed. Their logging system started reporting negative timestamps, which broke the sorting in their log viewer. They spent an hour assuming the log viewer was broken before someone thought to check the counter type. A long would have held microsecond timestamps for over 292,000 years. The fix was one word.
Quick Reference: Types by Robot Role
When you're writing robot code and need to pick a type, run through this mental checklist:
- Motor output, PID gain, velocity, angle, or any measurement with a decimal →
double - CAN ID, port number, current limit (amps, whole number), loop counter →
int - Sensor state, toggle flag, "is X running," button pressed →
boolean - Dashboard label, fault message, chooser option, log entry →
String - Accumulated tick count over a long session, high-precision timestamp →
long - Everything else: default to
doublefor decimals,intfor whole numbers, and only change if you have a specific reason
WPILib and vendor libraries have already made most type decisions. TalonFX.set() takes a double. XboxController.getAButton() returns a boolean. new TalonFX(int deviceId) takes an int. When you're calling an API method, let the method signature tell you the type — your IDE will show it on hover. The decisions in this lesson are most important when you're declaring a variable to hold intermediate results or store state.
🔌 System Check
The compiler won't catch most of these — which is exactly why they make it onto robots.
- Never use
floatfor anything that feeds into WPILib or a motor controller. WPILib's entire math layer isdouble. Afloatvariable that gets widened todoublecarries its precision error with it into every calculation downstream. - Current limits must be
intordoubledepending on the vendor API, notfloat. Check the method signature. CTRE's current limit config takes adouble. REV's takes anint. Passing the wrong type causes a silent cast and the limit ends up at an unexpected value. - Watch for integer division in any calculation that produces a motor output. If the result of a division feeds into
motor.set(), make sure at least one operand is adoubleor the result will be truncated to 0 or 1. - Initialize booleans to the safe default — almost always
false. Aboolean intakeRunning = trueas a field means the intake motor starts the instant the robot enables. Safe defaults prevent pit table accidents and unexpected enable-mode behavior. - If your code does any accumulation over time (integration, tick counting, timestamps), audit whether
intis large enough. The match is 150 seconds, but your robot will run far longer in testing. Size for the worst case.
Knowledge Check
Click an answer to check your understanding.
int result = 7 / 2; and expects the answer to be 3.5. What value is actually stored in result, and why?PIDController. The constructor takes a double. Which declaration is correct, and why does the other one cause problems?if (currentMode == "teleop") { ... } where currentMode is a String. The condition never evaluates to true, even when the string contains "teleop". What is the problem?Type Audit and Correction
The following class represents a simplified arm subsystem. It compiles without errors, but it contains multiple type-related bugs that would cause incorrect behavior on a real robot. Find them all.
- List each of the five type problems. For each one, write the corrected declaration or expression and explain in one sentence what could go wrong on the robot if left uncorrected.
- The
midpointvariable calculates the center of the arm's range. With the values as written, what value does it actually produce? Show the math. - Rewrite the entire class with all five bugs corrected. Add a
// fixed:comment on each corrected line explaining the change. - Bonus: The
calculateOutput()method ignores the derivative term (ARM_KD). What additional variable would you need to calculate a proper PD controller output, and what type should it be? Write the corrected method signature and body.