Build a Binary Data Encoder in Java
Build a CLI tool that converts numbers between bases and encrypts text with XOR.
Introduction
30 Second Summary
Every file, message, and number on your computer is ultimately stored as patterns of ones and zeros. Understanding how data gets translated between human-readable formats and binary is one of the most fundamental skills in programming.
In this project, you will build a command-line Data Encoder in pure Java that manually converts numbers between decimal, binary, hexadecimal, and octal. You'll write every conversion algorithm by hand, without relying on built-in library methods.
What You'll Build
A pure Java CLI application that converts between number bases, encodes text into binary using bitwise operations, and demonstrates how Java handles type limits at the bit level.
By the end of this project, you'll have:
- A working CLI tool that converts decimal numbers to binary, hexadecimal, and octal (and back) using hand-written algorithms.
- A bitwise text encoder that translates any message into its binary representation and reconstructs it using shift and mask operations.
- A Type Danger Zone demo that deliberately triggers overflow, narrowing casts, and floating point precision loss so you can see exactly how Java handles type boundaries.
- Secret Mission: a XOR cipher that encrypts and decrypts messages using bitwise operations.
Are there any prerequisites?
This project assumes you know basic Java syntax including variables, methods, and classes. You do not need experience with bitwise operations or number base conversions. The project teaches those from scratch.
Set Up the IntelliJ Project
Before you can write any conversion algorithms, you need a working Java project that compiles and runs.
In this step, you will verify that your JDK is ready, create a new project in IntelliJ IDEA, and confirm that your code compiles successfully.
In this step, get ready to:
- Verify that JDK 25 is installed and accessible.
- Create a new Java project in IntelliJ IDEA.
- Write and run a minimal main method to confirm everything works.
Verify your JDK installation
The Java Development Kit (JDK) includes both the java runtime and the javac compiler. You need both to compile and run your project.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Command Prompt and press Enter to open it.
- Check that the Java runtime is available by running this command:
java -version
- You should see output that includes version 25.
- Confirm the compiler is also available by running:
javac -version
- You should see javac 25 (or similar) in the output.
Not seeing version 25?
If the command is not recognized, your JDK installation may not be on your system PATH. Open your system environment variables and confirm that the JDK bin directory is listed in your PATH.
If you see an older version number, you may have multiple JDKs installed. Ensure JDK 25 appears first in your PATH, or reference it directly by its full path.
Help me troubleshoot my JDK installation on Windows. I ran java -version and did not see version 25.
Create the IntelliJ project
Now that your JDK is confirmed, you will create a new Java project using IntelliJ IDEA's built-in build system. This avoids the complexity of Maven or Gradle for a single-file project.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type IntelliJ IDEA and press Enter to open it.
- On the Welcome screen, click New Project.
- In the New Project wizard, select Java from the list on the left.
- In the Name field, type DataEncoder.
- Set the Build system dropdown to IntelliJ.
- In the JDK dropdown, select your JDK 25 installation.
- Leave the Add sample code checkbox disabled.
- Click Create.
Why IntelliJ as the build system?
IntelliJ's native builder compiles your code directly without external configuration files. For a single-file project focused on algorithms and fundamentals, this is the simplest option.
Maven and Gradle are designed for larger projects with dependencies and build pipelines. You do not need either of those here.
IntelliJ creates the project and opens it. You should see the Project tool window on the left with a src folder.
- In the Project tool window on the left, right-click the src folder.
- Hover over New, then click Java Class.
- In the popup, type DataEncoder as the class name.
- Press Enter.
IntelliJ creates DataEncoder.java inside the src directory and opens it in the editor.
Write and run a minimal main method
Every Java application starts with a main method. You will add one that prints a confirmation message to prove the project compiles and runs.
- Replace the contents of DataEncoder.java with the following code:
public class DataEncoder {
public static void main(String[] args) {
// Print a confirmation message to verify the project runs
System.out.println("Data Encoder Ready");
}
}
What does this code do?
- public class DataEncoder declares the class. The class name must match the file name exactly.
- public static void main(String[] args) is the entry point that the JVM looks for when you run the program.
- System.out.println("Data Encoder Ready") prints text to the console so you can confirm execution works.
- Save the file with Ctrl+S.
- Click the green play arrow next to the main method in the gutter (left margin of the editor).
- Select Run 'DataEncoder.main()' from the dropdown.
The Run tool window opens at the bottom of IntelliJ. You should see Data Encoder Ready printed in the output.
Not seeing the output?
If you see a compilation error, check that your class name DataEncoder matches the file name DataEncoder.java exactly. Java is case-sensitive.
If IntelliJ reports that no JDK is configured, go to File then Project Structure then Project and set the SDK to JDK 25.
Help me fix a compilation error in my DataEncoder project in IntelliJ IDEA.
✔️ Awesome, I've got everything!
Great. Double check you have saved your file before moving on.
ⓧ I'd like to double check the full code
Here is the complete DataEncoder.java at this point:
public class DataEncoder {
public static void main(String[] args) {
System.out.println("Data Encoder Ready");
}
}
Your project is set up and running. Next up, you will build the interactive menu system and explore how Java represents numbers in different formats.
Build the Scanner Menu System
Your project compiles and runs, which means the foundation is solid. Now you need a way for users to interact with your encoder.
In this step, you will build a menu-driven interface using Scanner for input, a while loop to keep the program alive, and a switch statement to route user choices. You will also explore how Java lets you write numbers directly in binary, octal, and hex right inside your source code.
In this step, get ready to:
- Build a Scanner-based menu loop with a switch statement.
- Understand the Scanner nextInt/nextLine trap and its fix.
- Explore Java number literal syntax with a live demo method.
Build the interactive menu loop
The menu is the backbone of your CLI tool. It keeps the program running until the user exits, displays all available options each loop iteration, and routes the user's choice to the correct conversion method.
- In IntelliJ, open DataEncoder.java from the src folder in the Project tool window on the left.
- Select all the existing code and replace it with the following:
import java.util.Scanner;
public class DataEncoder {
// Lookup table for hex digits: index 0-15 maps to '0'-'F'
static final char[] HEX_CHARS = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
boolean running = true;
System.out.println("=== Data Encoder ===");
// Keep showing the menu until the user chooses Exit
while (running) {
System.out.println("\n--- Menu ---");
System.out.println("1: Decimal to Binary");
System.out.println("2: Decimal to Hex");
System.out.println("3: Decimal to Octal");
System.out.println("4: Binary to Decimal");
System.out.println("5: Hex to Decimal");
System.out.println("6: Text to Binary");
System.out.println("7: Binary to Text");
System.out.println("8: Literal Demo");
System.out.println("9: Type Danger Zone");
System.out.println("0: Exit");
System.out.print("Choose: ");
int choice = scanner.nextInt();
scanner.nextLine(); // consume the leftover newline from nextInt()
switch (choice) {
case 1:
System.out.println("(coming soon)");
break;
case 2:
System.out.println("(coming soon)");
break;
case 3:
System.out.println("(coming soon)");
break;
case 4:
System.out.println("(coming soon)");
break;
case 5:
System.out.println("(coming soon)");
break;
case 6:
System.out.println("(coming soon)");
break;
case 7:
System.out.println("(coming soon)");
break;
case 8:
literalDemo();
break;
case 9:
System.out.println("(coming soon)");
break;
case 0:
running = false;
System.out.println("Goodbye!");
break;
default:
System.out.println("Invalid choice.");
}
}
scanner.close();
}
}
What does this code do?
This sets up the full interactive skeleton for your encoder.
- HEX_CHARS is a lookup table you will use later when converting decimal to hexadecimal. Index 0 maps to character '0', index 10 maps to 'A', and so on up to 'F'.
- The while (running) loop keeps displaying the menu until the user picks 0, which flips running to false.
- The switch statement routes each menu choice to its handler. Each case needs a break to prevent fall-through into the next case.
- scanner.close() releases the input stream when the program exits.
Why does each case need a break?
Without break, Java "falls through" into the next case. If the user picks 1 and case 1 has no break, execution continues into case 2, case 3, and so on until it hits a break or the end of the switch block.
You will see a red error on literalDemo() because the method does not exist yet. You will add it shortly.
Understand the Scanner nextInt trap
There is a notorious pitfall with Scanner that catches nearly every Java beginner. Look at these two lines in your code:
int choice = scanner.nextInt();
scanner.nextLine(); // consume the leftover newline from nextInt()
Why is that second line critical?
When the user types 1 and presses Enter, the input buffer contains two things: the character 1 and a newline character \n. The nextInt() method consumes the 1 but leaves the \n sitting in the buffer.
If you later call scanner.nextLine() to read user text (like a binary string to decode), it immediately picks up that leftover \n and returns an empty string. Your program appears to skip the input entirely.
The fix is simple: call scanner.nextLine() immediately after nextInt() to throw away the dangling newline before your real input calls run.
Add the number literals demo
Java lets you write numbers in binary, octal, and hexadecimal directly in your source code using special prefixes. This is useful when you are working with bit patterns or memory addresses and want the code to reflect the base you are thinking in.
- In DataEncoder.java, place your cursor after the closing brace of the main method but before the final closing brace of the class.
- Add the following method:
static void literalDemo() {
// Binary prefix: 0b
int binary = 0b1010;
// Octal prefix: leading 0
int octal = 0777;
// Hex prefix: 0x
int hex = 0xFF;
// Long suffix L, with underscore separators for readability
long big = 9_223_372_036_854_775_807L;
// Underscores work in any numeric literal
int million = 1_000_000;
System.out.println("\n--- Number Literals Demo ---");
System.out.println("0b1010 (binary literal) = " + binary);
System.out.println("0777 (octal literal) = " + octal);
System.out.println("0xFF (hex literal) = " + hex);
System.out.println("9_223_372_036_854_775_807L (long max with underscores) = " + big);
System.out.println("1_000_000 (underscore separator) = " + million);
}
What does this code do?
- 0b1010 is a binary literal. The 0b prefix tells Java to interpret the digits as base-2. 1010 in binary equals 10 in decimal.
- 0777 is an octal literal. A leading 0 means base-8. 777 in octal equals 511 in decimal.
- 0xFF is a hexadecimal literal. 0x means base-16. FF equals 255 in decimal.
- The L suffix marks a number as a long (64-bit). Without it, Java treats the value as an int and the compiler rejects anything larger than 2,147,483,647.
- Underscores (1_000_000) are ignored by the compiler. They are purely for human readability.
- Save DataEncoder.java by pressing Ctrl+S.
- Confirm the red error on literalDemo() is gone. IntelliJ should show no errors in the file.
- Run the program by clicking the green play button in the gutter next to public static void main, or press Shift+F10.
- Type 8 and press Enter in the Run console. You should see the literal demo output showing each number format and its decimal equivalent.
- Type 0 and press Enter to exit. You should see Goodbye! printed.
Seeing a compilation error?
- If IntelliJ shows "cannot find symbol" on literalDemo(), make sure the method is placed inside the class braces but outside the main method braces.
- If you see "class, interface, or enum expected", check that you have not accidentally placed code outside the class-level curly braces.
- Make sure import java.util.Scanner; is the very first line of the file, above the class declaration.
Help me fix my DataEncoder compilation error.
✔️ Awesome, I've got everything!
Great. Double check you have saved your file and the menu runs cleanly.
ⓧ I'd like to double check the full code
import java.util.Scanner;
public class DataEncoder {
static final char[] HEX_CHARS = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
boolean running = true;
System.out.println("=== Data Encoder ===");
while (running) {
System.out.println("\n--- Menu ---");
System.out.println("1: Decimal to Binary");
System.out.println("2: Decimal to Hex");
System.out.println("3: Decimal to Octal");
System.out.println("4: Binary to Decimal");
System.out.println("5: Hex to Decimal");
System.out.println("6: Text to Binary");
System.out.println("7: Binary to Text");
System.out.println("8: Literal Demo");
System.out.println("9: Type Danger Zone");
System.out.println("0: Exit");
System.out.print("Choose: ");
int choice = scanner.nextInt();
scanner.nextLine();
switch (choice) {
case 1:
System.out.println("(coming soon)");
break;
case 2:
System.out.println("(coming soon)");
break;
case 3:
System.out.println("(coming soon)");
break;
case 4:
System.out.println("(coming soon)");
break;
case 5:
System.out.println("(coming soon)");
break;
case 6:
System.out.println("(coming soon)");
break;
case 7:
System.out.println("(coming soon)");
break;
case 8:
literalDemo();
break;
case 9:
System.out.println("(coming soon)");
break;
case 0:
running = false;
System.out.println("Goodbye!");
break;
default:
System.out.println("Invalid choice.");
}
}
scanner.close();
}
static void literalDemo() {
int binary = 0b1010;
int octal = 0777;
int hex = 0xFF;
long big = 9_223_372_036_854_775_807L;
int million = 1_000_000;
System.out.println("\n--- Number Literals Demo ---");
System.out.println("0b1010 (binary literal) = " + binary);
System.out.println("0777 (octal literal) = " + octal);
System.out.println("0xFF (hex literal) = " + hex);
System.out.println("9_223_372_036_854_775_807L (long max with underscores) = " + big);
System.out.println("1_000_000 (underscore separator) = " + million);
}
}
Your menu skeleton is fully wired. Next up, you will write the actual conversion algorithms that turn decimal numbers into binary, hexadecimal, and octal using nothing but loops, modulo, and arrays.
Implement the Base Conversion Engine
Your menu is working and you explored how Java writes number literals. Now it is time to build the actual conversion logic.
Every base conversion follows the same core algorithm: repeatedly divide by the target base, collect the remainders, then reverse them. You will implement this algorithm three times, once for binary (base 2), once for hexadecimal (base 16), and once for octal (base 8).
In this step, get ready to:
- Implement decimalToBinary using the modulo-divide algorithm with array-based digit storage.
- Implement decimalToHex with a hex character lookup table.
- Implement decimalToOctal and wire all three methods into the menu.
Implement the decimal to binary converter
The modulo-divide algorithm works like this: divide the number by 2, save the remainder (0 or 1), repeat until the number reaches 0, then read the remainders in reverse order. That reversed sequence of remainders is the binary representation.
- In DataEncoder.java, scroll below the closing brace of the main method and above the literalDemo method.
- Add the following method:
static String decimalToBinary(int number) {
// Handle zero as a special case (the loop below produces nothing for 0)
if (number == 0) return "0";
// Handle negative numbers by converting to positive and prepending '-' later
boolean negative = number < 0;
if (negative) number = -number;
// Array to store remainders (max 32 bits for an int)
int[] digits = new int[32];
int count = 0;
// Modulo-divide loop: collect remainders in reverse order
while (number > 0) {
digits[count] = number % 2;
number = number / 2;
count++;
}
// Reverse the filled portion into a char array
char[] result = new char[count];
for (int i = 0; i < count; i++) {
result[i] = (char) ('0' + digits[count - 1 - i]);
}
String output = new String(result);
return negative ? "-" + output : output;
}
What does this code do?
This method manually converts a decimal integer to its binary string representation using only arrays and arithmetic.
- The while loop divides by 2 repeatedly. Each number % 2 gives one binary digit (0 or 1), stored in the digits array.
- The remainders come out in reverse order (least significant bit first), so the second loop reads the array backwards into a char[] to produce the correct left-to-right binary string.
- Zero is a special case because the division loop would never execute (0 divided by 2 is still 0), leaving an empty result.
Why not use Integer.toBinaryString()?
Java provides built-in methods like Integer.toBinaryString() that do this in one line. You are implementing the algorithm manually because understanding the modulo-divide pattern is foundational knowledge for interviews, debugging, and every topic that touches binary data.
Now wire this method into the menu so you can test it.
- In the switch statement inside main, find case 1: which currently says:
case 1:
System.out.println("(coming soon)");
break;
- Replace the placeholder line with code that prompts for input and calls your new method:
case 1:
System.out.print("Enter decimal number: ");
int decBin = scanner.nextInt();
scanner.nextLine();
System.out.println("Binary: " + decimalToBinary(decBin));
break;
Why scanner.nextLine() after nextInt() again?
Every time you call scanner.nextInt(), it reads the number but leaves the newline character sitting in the input buffer. The scanner.nextLine() call consumes that leftover newline so it does not interfere with later inputs.
- Save DataEncoder.java and run the program.
- Choose option 1 from the menu, then enter 42. You should see Binary: 101010 printed.
Seeing 0 or an empty result?
- Make sure the method is placed outside the main method but still inside the DataEncoder class.
- Confirm your while loop condition is number > 0, not number >= 0 (which would loop forever).
- Check that case 1 calls decimalToBinary(decBin) and not decimalToBinary(choice).
My binary conversion returns an unexpected result.
Implement the decimal to hex converter
Hexadecimal uses the same modulo-divide algorithm, but with base 16 instead of base 2. The twist is that remainders 10 through 15 need to be represented as letters A through F.
- In DataEncoder.java, add the following method directly below decimalToBinary:
static String decimalToHex(int number) {
if (number == 0) return "0";
boolean negative = number < 0;
if (negative) number = -number;
// Max 8 hex digits for a 32-bit int
char[] digits = new char[8];
int count = 0;
while (number > 0) {
// Each remainder indexes into the HEX_CHARS lookup table
int remainder = number % 16;
digits[count] = HEX_CHARS[remainder];
number = number / 16;
count++;
}
// Reverse the filled portion
char[] result = new char[count];
for (int i = 0; i < count; i++) {
result[i] = digits[count - 1 - i];
}
String output = new String(result);
return negative ? "-" + output : output;
}
What does this code do?
- The HEX_CHARS array you defined at the top of the class acts as a lookup table. Remainder 0 maps to '0', remainder 10 maps to 'A', remainder 15 maps to 'F'.
- The digit storage uses a char[] of size 8 because a 32-bit integer needs at most 8 hexadecimal digits (each hex digit represents 4 bits).
- The algorithm structure is identical to decimalToBinary. Only the divisor (16 instead of 2) and the digit mapping change.
- Find case 2: in the switch statement, which currently says (coming soon).
- Replace the placeholder line with this:
case 2:
System.out.print("Enter decimal number: ");
int decHex = scanner.nextInt();
scanner.nextLine();
System.out.println("Hex: " + decimalToHex(decHex));
break;
- Save DataEncoder.java and run the program.
- Choose option 2, then enter 255. You should see Hex: FF printed.
Seeing lowercase letters or wrong digits?
- Verify that HEX_CHARS contains uppercase 'A' through 'F' at indices 10 through 15.
- Make sure the lookup uses HEX_CHARS[remainder] and not a manual character cast.
My hex conversion is giving incorrect output.
Implement the decimal to octal converter
Octal is base 8. The algorithm is the same pattern you have now written twice. The only differences are the divisor (8) and the array size (11 digits, because the largest 32-bit int needs at most 11 octal digits).
- Add the following method directly below decimalToHex:
static String decimalToOctal(int number) {
if (number == 0) return "0";
boolean negative = number < 0;
if (negative) number = -number;
// Max 11 octal digits for a 32-bit int
int[] digits = new int[11];
int count = 0;
while (number > 0) {
digits[count] = number % 8;
number = number / 8;
count++;
}
// Reverse the filled portion into a char array
char[] result = new char[count];
for (int i = 0; i < count; i++) {
result[i] = (char) ('0' + digits[count - 1 - i]);
}
String output = new String(result);
return negative ? "-" + output : output;
}
What does this code do?
This is the same modulo-divide pattern as the previous two methods. Divide by 8, store the remainder, reverse. Because all octal digits are 0 through 7, you can cast directly to a character with '0' + digits[...] without needing a lookup table.
- Find case 3: in the switch statement and replace the placeholder line with this:
case 3:
System.out.print("Enter decimal number: ");
int decOct = scanner.nextInt();
scanner.nextLine();
System.out.println("Octal: " + decimalToOctal(decOct));
break;
- Save DataEncoder.java and run the program.
- Choose option 3, then enter 8. You should see Octal: 10 (because 8 in decimal is 10 in octal).
- Try entering 0 for any of the three converters to confirm the special-case handling returns 0.
Getting a compile error?
- Each new method must be inside the DataEncoder class but outside the main method. Check your closing braces.
- Ensure each case block still ends with break; and you did not accidentally delete a neighbouring case.
I am getting a compile error in my DataEncoder class after adding the conversion methods.
✔️ Awesome, I've got everything!
Great. Double check you have saved your file before moving on.
ⓧ I'd like to double check the full code
Here is the full DataEncoder.java file as it should look at the end of this step:
import java.util.Scanner;
public class DataEncoder {
static final char[] HEX_CHARS = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
boolean running = true;
System.out.println("=== Data Encoder ===");
while (running) {
System.out.println("\n--- Menu ---");
System.out.println("1: Decimal to Binary");
System.out.println("2: Decimal to Hex");
System.out.println("3: Decimal to Octal");
System.out.println("4: Binary to Decimal");
System.out.println("5: Hex to Decimal");
System.out.println("6: Text to Binary");
System.out.println("7: Binary to Text");
System.out.println("8: Literal Demo");
System.out.println("9: Type Danger Zone");
System.out.println("0: Exit");
System.out.print("Choose: ");
int choice = scanner.nextInt();
scanner.nextLine();
switch (choice) {
case 1:
System.out.print("Enter decimal number: ");
int decBin = scanner.nextInt();
scanner.nextLine();
System.out.println("Binary: " + decimalToBinary(decBin));
break;
case 2:
System.out.print("Enter decimal number: ");
int decHex = scanner.nextInt();
scanner.nextLine();
System.out.println("Hex: " + decimalToHex(decHex));
break;
case 3:
System.out.print("Enter decimal number: ");
int decOct = scanner.nextInt();
scanner.nextLine();
System.out.println("Octal: " + decimalToOctal(decOct));
break;
case 4:
System.out.println("(coming soon)");
break;
case 5:
System.out.println("(coming soon)");
break;
case 6:
System.out.println("(coming soon)");
break;
case 7:
System.out.println("(coming soon)");
break;
case 8:
literalDemo();
break;
case 9:
System.out.println("(coming soon)");
break;
case 0:
running = false;
System.out.println("Goodbye!");
break;
default:
System.out.println("Invalid choice.");
}
}
scanner.close();
}
static String decimalToBinary(int number) {
if (number == 0) return "0";
boolean negative = number < 0;
if (negative) number = -number;
int[] digits = new int[32];
int count = 0;
while (number > 0) {
digits[count] = number % 2;
number = number / 2;
count++;
}
char[] result = new char[count];
for (int i = 0; i < count; i++) {
result[i] = (char) ('0' + digits[count - 1 - i]);
}
String output = new String(result);
return negative ? "-" + output : output;
}
static String decimalToHex(int number) {
if (number == 0) return "0";
boolean negative = number < 0;
if (negative) number = -number;
char[] digits = new char[8];
int count = 0;
while (number > 0) {
int remainder = number % 16;
digits[count] = HEX_CHARS[remainder];
number = number / 16;
count++;
}
char[] result = new char[count];
for (int i = 0; i < count; i++) {
result[i] = digits[count - 1 - i];
}
String output = new String(result);
return negative ? "-" + output : output;
}
static String decimalToOctal(int number) {
if (number == 0) return "0";
boolean negative = number < 0;
if (negative) number = -number;
int[] digits = new int[11];
int count = 0;
while (number > 0) {
digits[count] = number % 8;
number = number / 8;
count++;
}
char[] result = new char[count];
for (int i = 0; i < count; i++) {
result[i] = (char) ('0' + digits[count - 1 - i]);
}
String output = new String(result);
return negative ? "-" + output : output;
}
static void literalDemo() {
int binary = 0b1010;
int octal = 0777;
int hex = 0xFF;
long big = 9_223_372_036_854_775_807L;
int million = 1_000_000;
System.out.println("\n--- Number Literals Demo ---");
System.out.println("0b1010 (binary literal) = " + binary);
System.out.println("0777 (octal literal) = " + octal);
System.out.println("0xFF (hex literal) = " + hex);
System.out.println("9_223_372_036_854_775_807L (long max with underscores) = " + big);
System.out.println("1_000_000 (underscore separator) = " + million);
}
}
You now have a working base conversion engine built entirely from arithmetic and arrays. Next up, you will build the reverse direction and explore what happens when Java's type system breaks down.
Implement Reverse Conversions and Type Edge Cases
In the previous step, you built methods that convert decimal numbers into other bases. Now it's time to go the other direction. Given a binary or hex string, your program needs to reconstruct the original decimal number.
This step also introduces Java's type system quirks. You'll see firsthand what happens when you force a number into a type that's too small for it, and why floating point math doesn't always behave the way you'd expect.
In this step, get ready to:
- Convert binary and hex strings back to decimal using positional value math.
- Build a Type Danger Zone that demonstrates overflow, narrowing, and floating point precision loss.
- Wire the new methods into the menu system.
Implement binary to decimal conversion
Converting a binary string back to decimal is the reverse of the modulo-divide algorithm. Each digit in a binary string has a positional value. The rightmost digit is worth 1, the next is worth 2, then 4, then 8, and so on.
How positional value works
The binary string 1101 means: (1 × 8) + (1 × 4) + (0 × 2) + (1 × 1) = 13. Each position is a power of 2, counted from right to left starting at 0.
- In DataEncoder.java, scroll down below the decimalToOctal method.
- Add the binaryToDecimal method by copying the following code below decimalToOctal:
static int binaryToDecimal(String binary) {
int result = 0;
int length = binary.length();
for (int i = 0; i < length; i++) {
char ch = binary.charAt(i);
// Reject any character that isn't '0' or '1'
if (ch != '0' && ch != '1') {
System.out.println("Error: Invalid binary character '" + ch + "'");
return -1;
}
// Convert the char to its numeric value (0 or 1)
int bit = ch - '0';
// Calculate which power of 2 this position represents
int position = length - 1 - i;
// Compute 2^position with a loop (no Math.pow)
int power = 1;
for (int j = 0; j < position; j++) {
power = power * 2;
}
// Add this digit's positional value to the total
result = result + (bit * power);
}
return result;
}
What does this code do?
The method walks through each character in the binary string from left to right. For each character, it calculates the power of 2 that position represents and multiplies it by the bit value (0 or 1).
The expression ch - '0' converts a character like '1' into the integer 1. This works because characters are stored as numbers in sequence, so subtracting '0' gives the numeric offset.
Now wire the method into the menu. Find the case 4: block in your switch statement. It currently says:
case 4:
System.out.println("(coming soon)");
break;
- Replace that entire case block with:
case 4:
System.out.print("Enter binary string: ");
String binStr = scanner.nextLine();
System.out.println("Decimal: " + binaryToDecimal(binStr));
break;
Why nextLine() instead of nextInt() here?
Binary strings like 1101 look like numbers, but they need to be read as strings. If you used nextInt(), Java would interpret 1101 as one thousand one hundred and one, not a binary value.
- Save DataEncoder.java and run the program.
- Choose option 4 and enter 1101. You should see Decimal: 13 in the output.
Seeing the wrong decimal value?
Double check the power calculation loop. The variable position should be length - 1 - i, not i. The leftmost character has the highest positional value, not the lowest.
help me debug my binaryToDecimal method
Build the Type Danger Zone
Java's type system has strict rules about how numbers are stored. When you force a number into a type that's too small, the extra bits get chopped off. The result can be surprising.
What is narrowing?
A narrowing cast takes a value from a wider type (like int, 32 bits) and forces it into a narrower type (like byte, 8 bits). Java discards the upper bits. The remaining bits are reinterpreted under the narrower type's rules, which can flip the sign.
- In DataEncoder.java, scroll down below the literalDemo method.
- Add the typeDangerZone method by copying the following code below literalDemo:
static void typeDangerZone() {
System.out.println("\n--- Type Danger Zone ---");
// Narrowing: 130 doesn't fit in a byte (-128 to 127)
byte narrowed = (byte) 130;
System.out.println("(byte) 130 = " + narrowed + " (130 binary: 10000010, as signed byte MSB is sign bit)");
// Narrowing: 40000 doesn't fit in a short (-32768 to 32767)
short shortNarrow = (short) 40000;
System.out.println("(short) 40000 = " + shortNarrow + " (exceeds short max of 32767)");
// Overflow: adding 1 to the maximum int wraps around
int overflow = Integer.MAX_VALUE + 1;
System.out.println("Integer.MAX_VALUE + 1 = " + overflow + " (wraps to MIN_VALUE)");
// Truncation: casting double to int drops the decimal, no rounding
int truncated = (int) 3.99;
System.out.println("(int) 3.99 = " + truncated + " (truncation, not rounding)");
// IEEE 754: floating point can't represent 0.1 exactly in binary
double precision = 0.1 + 0.2;
System.out.println("0.1 + 0.2 = " + precision + " (IEEE 754 floating point imprecision)");
// Two's complement: -1 is all 1s in binary
System.out.println("\nTwo's complement of -1 (32-bit): 11111111111111111111111111111111");
System.out.println("Verify: Integer.toBinaryString(-1) = " + Integer.toBinaryString(-1));
}
What does this code do?
- (byte) 130 produces -126. The binary form 10000010 has its leftmost bit set. In a signed byte, that bit means "negative."
- Integer.MAX_VALUE + 1 wraps to Integer.MIN_VALUE. This is overflow. The number exceeds what 32 bits can hold, so it wraps around to the most negative value.
- (int) 3.99 gives 3, not 4. Casting a double to an int truncates the decimal portion entirely.
- 0.1 + 0.2 does not equal 0.3 because IEEE 754 floating point can't represent 0.1 exactly in binary.
Now wire it into the menu. Find the case 9: block in your switch statement. It currently says:
case 9:
System.out.println("(coming soon)");
break;
- Replace that entire case block with:
case 9:
typeDangerZone();
break;
- Save DataEncoder.java and run the program.
- Choose option 9. You should see all five type demonstrations printed, including (byte) 130 = -126 and Integer.MAX_VALUE + 1 = -2147483648.
Not seeing the expected output values?
Make sure you have the cast syntax exactly right. The parentheses go around the type name, not the value: (byte) 130 is correct. Also confirm the method name matches typeDangerZone exactly in both the method definition and the case 9 call.
help me debug my typeDangerZone output
Implement hex to decimal conversion
Hex to decimal uses the same positional value approach as binary to decimal, but with base 16. The extra complexity is mapping hex characters (A through F) to their numeric values (10 through 15).
- In DataEncoder.java, scroll down below the binaryToDecimal method you just added.
- Add the hexToDecimal method by copying the following code below binaryToDecimal:
static int hexToDecimal(String hex) {
int result = 0;
int length = hex.length();
for (int i = 0; i < length; i++) {
char ch = hex.charAt(i);
int digitValue;
// Map each character to its numeric value
if (ch >= '0' && ch <= '9') {
digitValue = ch - '0';
} else if (ch >= 'A' && ch <= 'F') {
digitValue = 10 + (ch - 'A');
} else if (ch >= 'a' && ch <= 'f') {
digitValue = 10 + (ch - 'a');
} else {
System.out.println("Error: Invalid hex character '" + ch + "'");
return -1;
}
// Calculate 16^position for this digit
int position = length - 1 - i;
int power = 1;
for (int j = 0; j < position; j++) {
power = power * 16;
}
// Add this digit's contribution to the total
result = result + (digitValue * power);
}
return result;
}
What does this code do?
- The if-else chain maps each character to its numeric value. Digits '0' through '9' use the same ch - '0' trick. Letters use ch - 'A' + 10 to shift into the 10-15 range.
- Both upper case (A-F) and lower case (a-f) are handled. This makes the method flexible for any hex input the user types.
- The power computation loop multiplies by 16 instead of 2, but the structure is identical to binaryToDecimal.
Now wire it into the menu. Find the case 5: block in your switch statement. It currently says:
case 5:
System.out.println("(coming soon)");
break;
- Replace that entire case block with:
case 5:
System.out.print("Enter hex string: ");
String hexStr = scanner.nextLine();
System.out.println("Decimal: " + hexToDecimal(hexStr));
break;
- Save DataEncoder.java and run the program.
- Choose option 5 and enter FF. You should see Decimal: 255.
- Try option 5 again with 1a (lower case). You should see Decimal: 26.
Getting an error for lower case hex?
Make sure you have the lower case check ch >= 'a' && ch <= 'f' as a separate else-if branch. If you only check for upper case letters, lower case input like ff hits the error path.
help me fix my hexToDecimal method
Test a round-trip
Try converting a number to hex with option 2 (enter 255, get FF), then converting it back with option 5 (enter FF, get 255). Your forward and reverse conversions should agree.
✔️ Awesome, I've got everything!
Great. Double check you've saved your file before moving on.
ⓧ I'd like to double check the full code
Here is the complete DataEncoder.java at this point in the project.
import java.util.Scanner;
public class DataEncoder {
static final char[] HEX_CHARS = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
boolean running = true;
System.out.println("=== Data Encoder ===");
while (running) {
System.out.println("\n--- Menu ---");
System.out.println("1: Decimal to Binary");
System.out.println("2: Decimal to Hex");
System.out.println("3: Decimal to Octal");
System.out.println("4: Binary to Decimal");
System.out.println("5: Hex to Decimal");
System.out.println("6: Text to Binary");
System.out.println("7: Binary to Text");
System.out.println("8: Literal Demo");
System.out.println("9: Type Danger Zone");
System.out.println("0: Exit");
System.out.print("Choose: ");
int choice = scanner.nextInt();
scanner.nextLine();
switch (choice) {
case 1:
System.out.print("Enter decimal number: ");
int decBin = scanner.nextInt();
scanner.nextLine();
System.out.println("Binary: " + decimalToBinary(decBin));
break;
case 2:
System.out.print("Enter decimal number: ");
int decHex = scanner.nextInt();
scanner.nextLine();
System.out.println("Hex: " + decimalToHex(decHex));
break;
case 3:
System.out.print("Enter decimal number: ");
int decOct = scanner.nextInt();
scanner.nextLine();
System.out.println("Octal: " + decimalToOctal(decOct));
break;
case 4:
System.out.print("Enter binary string: ");
String binStr = scanner.nextLine();
System.out.println("Decimal: " + binaryToDecimal(binStr));
break;
case 5:
System.out.print("Enter hex string: ");
String hexStr = scanner.nextLine();
System.out.println("Decimal: " + hexToDecimal(hexStr));
break;
case 6:
System.out.println("(coming soon)");
break;
case 7:
System.out.println("(coming soon)");
break;
case 8:
literalDemo();
break;
case 9:
typeDangerZone();
break;
case 0:
running = false;
System.out.println("Goodbye!");
break;
default:
System.out.println("Invalid choice.");
}
}
scanner.close();
}
static String decimalToBinary(int number) {
if (number == 0) return "0";
boolean negative = number < 0;
if (negative) number = -number;
int[] digits = new int[32];
int count = 0;
while (number > 0) {
digits[count] = number % 2;
number = number / 2;
count++;
}
char[] result = new char[count];
for (int i = 0; i < count; i++) {
result[i] = (char) ('0' + digits[count - 1 - i]);
}
String output = new String(result);
return negative ? "-" + output : output;
}
static String decimalToHex(int number) {
if (number == 0) return "0";
boolean negative = number < 0;
if (negative) number = -number;
char[] digits = new char[8];
int count = 0;
while (number > 0) {
int remainder = number % 16;
digits[count] = HEX_CHARS[remainder];
number = number / 16;
count++;
}
char[] result = new char[count];
for (int i = 0; i < count; i++) {
result[i] = digits[count - 1 - i];
}
String output = new String(result);
return negative ? "-" + output : output;
}
static String decimalToOctal(int number) {
if (number == 0) return "0";
boolean negative = number < 0;
if (negative) number = -number;
int[] digits = new int[11];
int count = 0;
while (number > 0) {
digits[count] = number % 8;
number = number / 8;
count++;
}
char[] result = new char[count];
for (int i = 0; i < count; i++) {
result[i] = (char) ('0' + digits[count - 1 - i]);
}
String output = new String(result);
return negative ? "-" + output : output;
}
static int binaryToDecimal(String binary) {
int result = 0;
int length = binary.length();
for (int i = 0; i < length; i++) {
char ch = binary.charAt(i);
if (ch != '0' && ch != '1') {
System.out.println("Error: Invalid binary character '" + ch + "'");
return -1;
}
int bit = ch - '0';
int position = length - 1 - i;
int power = 1;
for (int j = 0; j < position; j++) {
power = power * 2;
}
result = result + (bit * power);
}
return result;
}
static int hexToDecimal(String hex) {
int result = 0;
int length = hex.length();
for (int i = 0; i < length; i++) {
char ch = hex.charAt(i);
int digitValue;
if (ch >= '0' && ch <= '9') {
digitValue = ch - '0';
} else if (ch >= 'A' && ch <= 'F') {
digitValue = 10 + (ch - 'A');
} else if (ch >= 'a' && ch <= 'f') {
digitValue = 10 + (ch - 'a');
} else {
System.out.println("Error: Invalid hex character '" + ch + "'");
return -1;
}
int position = length - 1 - i;
int power = 1;
for (int j = 0; j < position; j++) {
power = power * 16;
}
result = result + (digitValue * power);
}
return result;
}
static void literalDemo() {
int binary = 0b1010;
int octal = 0777;
int hex = 0xFF;
long big = 9_223_372_036_854_775_807L;
int million = 1_000_000;
System.out.println("\n--- Number Literals Demo ---");
System.out.println("0b1010 (binary literal) = " + binary);
System.out.println("0777 (octal literal) = " + octal);
System.out.println("0xFF (hex literal) = " + hex);
System.out.println("9_223_372_036_854_775_807L (long max with underscores) = " + big);
System.out.println("1_000_000 (underscore separator) = " + million);
}
static void typeDangerZone() {
System.out.println("\n--- Type Danger Zone ---");
byte narrowed = (byte) 130;
System.out.println("(byte) 130 = " + narrowed + " (130 binary: 10000010, as signed byte MSB is sign bit)");
short shortNarrow = (short) 40000;
System.out.println("(short) 40000 = " + shortNarrow + " (exceeds short max of 32767)");
int overflow = Integer.MAX_VALUE + 1;
System.out.println("Integer.MAX_VALUE + 1 = " + overflow + " (wraps to MIN_VALUE)");
int truncated = (int) 3.99;
System.out.println("(int) 3.99 = " + truncated + " (truncation, not rounding)");
double precision = 0.1 + 0.2;
System.out.println("0.1 + 0.2 = " + precision + " (IEEE 754 floating point imprecision)");
System.out.println("\nTwo's complement of -1 (32-bit): 11111111111111111111111111111111");
System.out.println("Verify: Integer.toBinaryString(-1) = " + Integer.toBinaryString(-1));
}
}
Your Data Encoder now handles conversions in both directions and exposes Java's type system quirks. Next up, you'll use bitwise operators to encode entire text messages into their binary representation and decode them back.
Implement Bitwise Text Encoding
Your conversion engine handles numbers fluently. You can go from decimal to binary, hex, or octal, and back again. Now you will go deeper and convert actual text into raw binary.
Every character you type on a keyboard is stored as a number. The letter H is 72. The letter e is 101. In this step, you will use bitwise operators to extract each individual bit from a character and rebuild characters from their bits.
In this step, get ready to:
- Implement a method that converts text into its 8-bit binary representation using bit shifting.
- Implement a method that reconstructs text from space-separated binary bytes using bitwise OR.
- Wire both methods into the menu and verify a round-trip conversion.
Implement the textToBinary method
Every char in Java is a 16-bit unsigned integer representing a Unicode code point. To see a character as binary, you need to extract each bit individually. The expression (ch >> i) & 1 shifts the character right by i positions, then masks off everything except the lowest bit.
- In DataEncoder.java, scroll below the hexToDecimal method.
- Add the textToBinary method by pasting the following code:
static String textToBinary(String text) {
// Convert the string into a char array for direct access
char[] chars = text.toCharArray();
StringBuilder output = new StringBuilder();
for (int c = 0; c < chars.length; c++) {
char ch = chars[c];
char[] bits = new char[8];
// Extract each bit from position 7 (MSB) down to 0 (LSB)
for (int i = 7; i >= 0; i--) {
bits[7 - i] = (char) ('0' + ((ch >> i) & 1));
}
output.append(new String(bits));
// Add a space between each character's byte
if (c < chars.length - 1) {
output.append(' ');
}
}
return output.toString();
}
What does this code do?
The method converts each character in the input string to an 8-bit binary representation.
- toCharArray() converts the String into a char[] so you can access each character by index.
- (ch >> i) & 1 is the key operation. It shifts the character's bits right by i positions, then ANDs with 1 to isolate just that single bit.
- The loop goes from bit 7 (the most significant bit) down to bit 0. This produces the binary in left-to-right reading order.
- Spaces are appended between each character's byte so the decoder knows where one character ends and the next begins.
Why use bitwise operators instead of modulo-divide?
For numbers, you used the modulo-divide algorithm to extract digits. For characters, bitwise shift and AND are more natural because you already know the result is exactly 8 bits wide. The shift-and-mask pattern gives you direct positional access to any bit without building a reversed array.
- Save DataEncoder.java.
- You will not test this method directly yet. The next substep builds the decoder, then you will wire both into the menu together.
Implement the binaryToText method
Decoding is the reverse process. Given a string of space-separated 8-bit binary chunks, you need to reconstruct each character. The operation charValue |= (1 << (7 - i)) sets a specific bit in the result by shifting a 1 into the correct position and ORing it in.
- Directly below the textToBinary method you just added, paste the binaryToText method:
static String binaryToText(String binary) {
// Split input on spaces to get individual 8-bit chunks
String[] bytes = binary.split(" ");
char[] result = new char[bytes.length];
for (int b = 0; b < bytes.length; b++) {
String byteStr = bytes[b];
int charValue = 0;
// For each '1' in the binary string, set that bit in charValue
for (int i = 0; i < byteStr.length(); i++) {
if (byteStr.charAt(i) == '1') {
charValue |= (1 << (7 - i));
}
}
result[b] = (char) charValue;
}
return new String(result);
}
What does this code do?
- split(" ") breaks the input into individual byte strings (e.g. "01001000", "01100101").
- For each byte string, it scans left to right. When it encounters a '1', it calculates that bit's position (7 minus the index) and uses |= to set that bit in charValue.
- 1 << (7 - i) creates a number with only one bit set at the correct position. ORing it into charValue turns that bit on without disturbing the others.
- Finally, (char) charValue casts the integer back to a character.
- Save DataEncoder.java.
Seeing a compile error?
Make sure the method is placed outside main() but inside the DataEncoder class body. Check that the closing brace of the previous method is properly closed before your new method starts.
Help me fix a compile error in my binaryToText method.
Wire the methods into the menu and test a round-trip
Both conversion methods are ready. Now you need to replace the placeholder case 6 and case 7 blocks with real input handling that calls your new methods.
- In DataEncoder.java, find the case 6 block inside your switch statement.
- Find this code:
case 6:
System.out.println("(coming soon)");
break;
case 7:
System.out.println("(coming soon)");
break;
- Replace both cases with the following code:
case 6:
System.out.print("Enter text: ");
String text = scanner.nextLine();
System.out.println("Binary: " + textToBinary(text));
break;
case 7:
System.out.print("Enter binary (space-separated bytes): ");
String binText = scanner.nextLine();
System.out.println("Text: " + binaryToText(binText));
break;
What does this code do?
Both cases use scanner.nextLine() directly (not nextInt()) because the input is a string, not a number. No leftover newline trap here.
Case 6 takes any text and passes it to your encoding method. Case 7 expects the space-separated binary format that case 6 produces.
- Save DataEncoder.java.
- Run the program by clicking the green play button next to your main method in IntelliJ.
- Choose option 6 and enter Hello.
- You should see: Binary: 01001000 01100101 01101100 01101100 01101111.
- Now choose option 7 and paste that binary output back in.
- You should see: Text: Hello.
Getting garbled text back from binaryToText?
Make sure you are pasting the binary with single spaces between each 8-character group. Extra spaces or missing spaces will cause split(" ") to produce incorrect byte chunks.
If you see a blank result, check that you did not accidentally add a leading or trailing space to your input.
Help me debug my binaryToText round-trip.
✔️ Awesome, I've got everything!
Great. Double check you have saved DataEncoder.java and confirmed the round-trip works.
ⓧ I'd like to double check the full code
import java.util.Scanner;
public class DataEncoder {
static final char[] HEX_CHARS = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
boolean running = true;
System.out.println("=== Data Encoder ===");
while (running) {
System.out.println("\n--- Menu ---");
System.out.println("1: Decimal to Binary");
System.out.println("2: Decimal to Hex");
System.out.println("3: Decimal to Octal");
System.out.println("4: Binary to Decimal");
System.out.println("5: Hex to Decimal");
System.out.println("6: Text to Binary");
System.out.println("7: Binary to Text");
System.out.println("8: Literal Demo");
System.out.println("9: Type Danger Zone");
System.out.println("0: Exit");
System.out.print("Choose: ");
int choice = scanner.nextInt();
scanner.nextLine();
switch (choice) {
case 1:
System.out.print("Enter decimal number: ");
int decBin = scanner.nextInt();
scanner.nextLine();
System.out.println("Binary: " + decimalToBinary(decBin));
break;
case 2:
System.out.print("Enter decimal number: ");
int decHex = scanner.nextInt();
scanner.nextLine();
System.out.println("Hex: " + decimalToHex(decHex));
break;
case 3:
System.out.print("Enter decimal number: ");
int decOct = scanner.nextInt();
scanner.nextLine();
System.out.println("Octal: " + decimalToOctal(decOct));
break;
case 4:
System.out.print("Enter binary string: ");
String binStr = scanner.nextLine();
System.out.println("Decimal: " + binaryToDecimal(binStr));
break;
case 5:
System.out.print("Enter hex string: ");
String hexStr = scanner.nextLine();
System.out.println("Decimal: " + hexToDecimal(hexStr));
break;
case 6:
System.out.print("Enter text: ");
String text = scanner.nextLine();
System.out.println("Binary: " + textToBinary(text));
break;
case 7:
System.out.print("Enter binary (space-separated bytes): ");
String binText = scanner.nextLine();
System.out.println("Text: " + binaryToText(binText));
break;
case 8:
literalDemo();
break;
case 9:
typeDangerZone();
break;
case 0:
running = false;
System.out.println("Goodbye!");
break;
default:
System.out.println("Invalid choice.");
}
}
scanner.close();
}
static String decimalToBinary(int number) {
if (number == 0) return "0";
boolean negative = number < 0;
if (negative) number = -number;
int[] digits = new int[32];
int count = 0;
while (number > 0) {
digits[count] = number % 2;
number = number / 2;
count++;
}
char[] result = new char[count];
for (int i = 0; i < count; i++) {
result[i] = (char) ('0' + digits[count - 1 - i]);
}
String output = new String(result);
return negative ? "-" + output : output;
}
static String decimalToHex(int number) {
if (number == 0) return "0";
boolean negative = number < 0;
if (negative) number = -number;
char[] digits = new char[8];
int count = 0;
while (number > 0) {
int remainder = number % 16;
digits[count] = HEX_CHARS[remainder];
number = number / 16;
count++;
}
char[] result = new char[count];
for (int i = 0; i < count; i++) {
result[i] = digits[count - 1 - i];
}
String output = new String(result);
return negative ? "-" + output : output;
}
static String decimalToOctal(int number) {
if (number == 0) return "0";
boolean negative = number < 0;
if (negative) number = -number;
int[] digits = new int[11];
int count = 0;
while (number > 0) {
digits[count] = number % 8;
number = number / 8;
count++;
}
char[] result = new char[count];
for (int i = 0; i < count; i++) {
result[i] = (char) ('0' + digits[count - 1 - i]);
}
String output = new String(result);
return negative ? "-" + output : output;
}
static int binaryToDecimal(String binary) {
int result = 0;
int length = binary.length();
for (int i = 0; i < length; i++) {
char ch = binary.charAt(i);
if (ch != '0' && ch != '1') {
System.out.println("Error: Invalid binary character '" + ch + "'");
return -1;
}
int bit = ch - '0';
int position = length - 1 - i;
int power = 1;
for (int j = 0; j < position; j++) {
power = power * 2;
}
result = result + (bit * power);
}
return result;
}
static int hexToDecimal(String hex) {
int result = 0;
int length = hex.length();
for (int i = 0; i < length; i++) {
char ch = hex.charAt(i);
int digitValue;
if (ch >= '0' && ch <= '9') {
digitValue = ch - '0';
} else if (ch >= 'A' && ch <= 'F') {
digitValue = 10 + (ch - 'A');
} else if (ch >= 'a' && ch <= 'f') {
digitValue = 10 + (ch - 'a');
} else {
System.out.println("Error: Invalid hex character '" + ch + "'");
return -1;
}
int position = length - 1 - i;
int power = 1;
for (int j = 0; j < position; j++) {
power = power * 16;
}
result = result + (digitValue * power);
}
return result;
}
static String textToBinary(String text) {
char[] chars = text.toCharArray();
StringBuilder output = new StringBuilder();
for (int c = 0; c < chars.length; c++) {
char ch = chars[c];
char[] bits = new char[8];
for (int i = 7; i >= 0; i--) {
bits[7 - i] = (char) ('0' + ((ch >> i) & 1));
}
output.append(new String(bits));
if (c < chars.length - 1) {
output.append(' ');
}
}
return output.toString();
}
static String binaryToText(String binary) {
String[] bytes = binary.split(" ");
char[] result = new char[bytes.length];
for (int b = 0; b < bytes.length; b++) {
String byteStr = bytes[b];
int charValue = 0;
for (int i = 0; i < byteStr.length(); i++) {
if (byteStr.charAt(i) == '1') {
charValue |= (1 << (7 - i));
}
}
result[b] = (char) charValue;
}
return new String(result);
}
static void literalDemo() {
int binary = 0b1010;
int octal = 0777;
int hex = 0xFF;
long big = 9_223_372_036_854_775_807L;
int million = 1_000_000;
System.out.println("\n--- Number Literals Demo ---");
System.out.println("0b1010 (binary literal) = " + binary);
System.out.println("0777 (octal literal) = " + octal);
System.out.println("0xFF (hex literal) = " + hex);
System.out.println("9_223_372_036_854_775_807L (long max with underscores) = " + big);
System.out.println("1_000_000 (underscore separator) = " + million);
}
static void typeDangerZone() {
System.out.println("\n--- Type Danger Zone ---");
byte narrowed = (byte) 130;
System.out.println("(byte) 130 = " + narrowed + " (130 binary: 10000010, as signed byte MSB is sign bit)");
short shortNarrow = (short) 40000;
System.out.println("(short) 40000 = " + shortNarrow + " (exceeds short max of 32767)");
int overflow = Integer.MAX_VALUE + 1;
System.out.println("Integer.MAX_VALUE + 1 = " + overflow + " (wraps to MIN_VALUE)");
int truncated = (int) 3.99;
System.out.println("(int) 3.99 = " + truncated + " (truncation, not rounding)");
double precision = 0.1 + 0.2;
System.out.println("0.1 + 0.2 = " + precision + " (IEEE 754 floating point imprecision)");
System.out.println("\nTwo's complement of -1 (32-bit): 11111111111111111111111111111111");
System.out.println("Verify: Integer.toBinaryString(-1) = " + Integer.toBinaryString(-1));
}
}
Your Data Encoder is now feature-complete. You can convert numbers between bases, encode text into raw binary, and decode it back. You have built every algorithm by hand using arrays, loops, and bitwise operators. If you want to push further, the Secret Mission adds XOR encryption on top of these same bitwise skills.
Secret mission
XOR Cipher Encryption
Your Data Encoder can convert between bases and encode text into binary. But can it keep secrets? In this mission, you'll implement a real XOR cipher that encrypts any message with a key and decrypts it back using bitwise operations.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your project files or delete them entirely.
Resources you used:
- DataEncoder IntelliJ project (local files on your machine).
Keep everything running
No action needed. Choose this if you plan to keep experimenting with your Data Encoder or want to extend it further.
- Your DataEncoder project stays in IntelliJ IDEA, ready to open and run anytime.
Pause - I'll come back to this later
Close IntelliJ IDEA to free up memory. Your project files remain on disk and will reappear in IntelliJ's recent projects list when you reopen it.
- Close IntelliJ IDEA.
- Your project files stay on disk. To resume, open IntelliJ IDEA and select the DataEncoder project from the welcome screen.
Delete - I don't want to use this again
Remove all project files from your machine. This cannot be undone.
- Close IntelliJ IDEA if it is still open.
- Open IntelliJ IDEA's welcome screen, right-click the DataEncoder project in the recent projects list, and select Remove from Recent Projects.
- Delete the project folder from your file system. Open a terminal and run the command below, replacing the path with wherever IntelliJ created your project:
macOS
rm -rf ~/IdeaProjects/DataEncoder
Windows
rmdir /s /q %USERPROFILE%\IdeaProjects\DataEncoder
- Confirm the folder is gone by checking your IdeaProjects directory. It should no longer contain a DataEncoder folder.
That's a Wrap!
That's a Wrap!
Nice work! You just built a complete CLI data encoder from scratch using nothing but raw Java. No frameworks, no libraries, just algorithms you wrote yourself.
You've learned how to:
- Built manual base conversion algorithms that convert between decimal, binary, hex, and octal using the modulo-divide pattern and array-based digit storage.
- Explored Java's type system boundaries by deliberately triggering overflow, narrowing casts, and floating point precision loss.
- Implemented bitwise text encoding using shift and mask operations to convert text into binary representations and reconstruct it back.
- Secret Mission: Built an XOR cipher that encrypts and decrypts messages using bitwise XOR with key cycling.
Ready to quiz yourself?