Loading...
Loading...
Java interview guide
Checked and unchecked exceptions, try-with-resources, files and robust error handling. Every answer is short enough to say aloud, and every question links to the lesson that teaches it.
60 questionsLast updated
It depends on the data. For independent rows such as registrations, I continue and report bad rows, with an error limit so the wrong file stops quickly. For data that must be complete, such as marks for report cards, I validate everything and save nothing unless every row is valid.
Lesson: Skip and report bad rowsFrom what is normal for that file. If a clean file usually has zero or one bad row, a limit like 10 or 1% of rows is far above normal but still stops quickly on the wrong file. I make it a named constant or a setting so it can change without editing the loop.
Lesson: Skip and report bad rowsWith a round trip: I write records that contain commas, quotes and empty values, read the file back with the parser, and check that each record equals the original. Any escaping mistake shows up as a difference.
A BOM is the invisible character \uFEFF at the start of a file; in UTF-8 it is the bytes EF BB BF. I write it when the CSV is meant to be opened in Excel on Windows, so that Excel reads it as UTF-8. When reading such files back in Java I strip it, because Java keeps it as the first character.
It is a text file of key=value settings with # comments. I create a Properties object and call load with a UTF-8 Reader from Files.newBufferedReader, inside try-with-resources. Then getProperty(key, default) gives each value as a String.
A missing setting gets a sensible default from getProperty(key, default), so the program runs even without a file. An invalid value, like max.errors=ten, must not be silently replaced: I throw an exception that names the key and shows the bad value, so the person can fix that exact line.
Built-in defaults first, then the properties file, then environment variables, then -D system properties. Each later source is more specific to this run, so it should win. Servers often set environment variables, while a developer testing one change uses -D.
A comma after the last item, single quotes instead of double quotes, keys without quotes, and strings that are not escaped, so a quote or backslash in the data breaks the document. Locale-formatted decimals like 78,5 are another one.
By hand is fine for small fixed shapes, like a status message or test data, as long as every string is escaped. For real data with lists, nesting and dates, and always for reading JSON, I use a library such as Jackson or Gson, which is tested and handles every edge case.
Lesson: Write simple JSON text by handI read it as UTF-8, check and skip the header, skip blank lines and parse each line with a quote-aware parser. Each row is validated field by field; good rows become records and bad rows become RowErrors with the line number and reason. An error limit stops on the wrong file, and at the end I return the records and the error report together.
Because spaces inside quotes are part of the value, like a padded code or a title. The parser's only job is to cut the line into the exact values; trimming belongs in the code that knows what each field means, such as Integer.parseInt(f.get(2).trim()) for marks.
CSV for flat tables that people open in a spreadsheet, like marks or payments. JSON for nested data sent between programs, like a student with a list of marks. A properties file for simple key=value settings that someone edits by hand.
A marker interface has no methods. A class implements it only to give Java some information about the class. Serializable is the common example: it tells the serialization code that objects of this class may be saved.
No. It is a new object with copies of the saved field values, so == with the original is false. If the program that saved it has ended, the original does not even exist any more.
One simple way is to write the count first with writeInt and then each object, so the reader calls readInt and loops that many times. Another way is to save one collection object that holds them all. Reading until EOFException also works, but it uses an exception for a normal end of data.
Inside one stream, each object is written once. Later references to it are written as back-references, so after reading the objects are shared again, and cycles do not cause an endless loop.
Lesson: Object graphs and NotSerializableExceptionYes. Implementing an interface is inherited, so the subclass is serializable without repeating implements Serializable. All fields in the serializable classes of the chain are saved.
It is an instance field marked with the transient keyword. Serialization skips it, so its value is not saved, and after deserialization it holds the default value of its type.
= 10. What value does it have after deserialization?It is 0. For a serializable class, Java does not run the constructor or the field initializers while reading, so the field keeps its default value. To restore it, I set it in a private readObject method or rebuild it lazily.
It keeps the password out of the file, which is good. But the other fields are still stored as plain bytes that anyone with the file can read, so transient is not encryption. Sensitive files still need protection, such as file permissions or real encryption.
It is a private static final long version number of a serializable class. Java writes it into the serialized bytes and, on reading, compares it with the class in the program. If they differ, reading fails with InvalidClassException, which stops old data from being loaded into a class it no longer fits.
The error message shows the old computed number as "stream classdesc serialVersionUID". I declare exactly that number as the class's serialVersionUID, so the check passes and the new field loads with its default. If the default is not sensible, I set a better value in readObject.
No. It only controls the version check. Even with the same number, a changed field type throws InvalidClassException, and a renamed class throws ClassNotFoundException. It also does not check that the loaded values make sense; that needs validation, which the next lesson covers.
Java saves the record's component values and, on reading, calls the canonical constructor with them. So a record is always built through its constructor, its compact constructor checks run, and custom readObject/writeObject methods are ignored.
Its compact constructor runs for new objects and for loaded ones, so one set of checks covers both. Records are also immutable and match components by name, so adding a component is handled through the same constructor with a default value.
Lesson: Records and validation while readingNo. It stops invalid values in my own classes, but the stream can name other classes, and some JDK or library classes can do harmful things while they are being read. That risk comes before my checks run, which is why untrusted data needs filters or, better, a different format. The next lesson covers this.
Lesson: Records and validation while readingIt lets me allow only the classes I expect and set limits on depth, object count, bytes and array length. I create one with ObjectInputFilter.Config.createFilter and set it on the stream before the first readObject, or set jdk.serialFilter for the whole program. Anything not allowed is rejected before it is read.
No. Validation in my class runs after other classes in the stream may already have been read. It protects my invariants, but not against unexpected classes. For user data I would use a text format such as JSON or CSV instead, plus checks.
Lesson: Why reading untrusted data is dangerousText formats are readable, easy to edit and debug, and work with any language. They are not tied to one exact class version, and the program builds objects with its own checks, so data cannot decide which classes run. Java serialization is binary, Java-only and risky with untrusted data.
Lesson: Text formats as the usual choiceI read columns by header name instead of position, and I give a default when a column is missing in old files. Old readers that also use header names simply ignore the new column.
Lesson: Text formats as the usual choiceIt works for serializable objects and handles nested data, but it is slow and needs every class to be serializable. A copy constructor or a copy method is clearer and faster, so I would use serialization only as a quick shortcut in trusted code.
Lesson: Text formats as the usual choiceTransient fields have their type's default, 0, false or null, because initializers and constructors do not run. Static fields are never saved, so they show whatever value the class has in the reading program.
I use records whose compact constructors check the values, because records are rebuilt through the constructor. For plain classes I add a private readObject that checks the same rules after defaultReadObject(). For the class list itself I use an allow-list filter.
When the problem is expected in a correct program and the caller can do something useful about it, for example a declined payment or a sold-out seat. Making it checked means no caller can forget that case. For bugs such as a negative amount I use an unchecked exception instead.
Lesson: Checked or unchecked for your own exceptionsstream().map(...). It does not compile. What are your options?I can catch it inside the lambda and rethrow it wrapped in an unchecked exception, such as UncheckedIOException for an IOException, then catch that outside the stream. Or I can use a plain loop, where throws works normally. Hiding it in an empty catch is not an option.
No. The check is only a compiler rule about catching or declaring. The JVM throws and catches checked and unchecked exceptions in exactly the same way, which is also why other JVM languages such as Kotlin can ignore the rule.
Lesson: Checked or unchecked for your own exceptionsIt groups all known application problems, so the top-level handler can catch them in one block and show a proper message. Bugs such as NullPointerException are not in that family, so they still stand out. It also tells a reader at once that an error came from our code.
No. An exception describes something that already happened, so its data should be private final and set in the constructor. If a catch block could change it, the message and the fields might disagree by the time it is logged.
It means checking input as early as possible and throwing at once when it is wrong. The stack trace then points at the call that passed the bad value, not at some later method that tripped over it. Bugs are found sooner and fixed faster.
Lesson: Validate early and fail fastObjects.requireNonNull if Java throws a NullPointerException anyway?Java would throw it later, wherever the null is first used, which may be far from the real mistake. requireNonNull throws at the entry point with a message naming the field. It also returns the value, so it fits in the assignment line.
Not usually. Public methods and constructors are the doors where outside data comes in, so they must check. Private helpers are called only by my own already-checked code; there an assert or a comment is often enough, unless the helper is complex.
A layer catches the low-level exceptions of the code below it and rethrows them as its own type, with a message in its own words. For example, the storage layer catches IOException and throws StorageException("Could not load marks for class 10B", e). The layers above never see file details.
A full path can reveal the user name, the folder structure or server details, and the user cannot act on it anyway. The user message uses a logical name like "marks for class 10B"; the path stays in the cause and the logs for developers.
Lesson: Translate exceptions at layer boundariesOnly the storage layer changes: it now catches SQLException instead of IOException, but it still throws the same StorageException. The service and UI code, and their tests, stay exactly as they were.
The user cannot act on it, it makes the app look broken, and it can reveal class names, file paths and library versions that help an attacker. I show a friendly sentence, maybe with a reference id, and log the full trace.
Lesson: Messages for users versus developersIf the message said "no such account", anyone could use the page to test which emails are registered. One message for both cases gives an attacker nothing, and the real user still knows to check both fields.
Lesson: Messages for users versus developersmain for a command-line tool so it is easy to test?Put the logic in a method like int run(String[] args, PrintStream out, PrintStream err) that returns the exit code, and keep main to one line: System.exit(run(args, System.out, System.err)). Tests call run directly and check the code and the printed text.
java.util.logging?From highest to lowest: SEVERE, WARNING, INFO, CONFIG, FINE, FINER, FINEST. With the default configuration only INFO and above appear on the console.
FINE, but FINE messages still do not show. What would you check?The handler's level. A message must pass both the logger's level and each handler's level, and the default ConsoleHandler is set to INFO. I would lower the handler's level too, usually in logging.properties.
java.util.logging what real projects use?It is built into the JDK, so it is good for small tools and learning. Many larger projects use SLF4J with Logback or Log4j 2, which add more features, but the ideas are the same: named loggers, levels, handlers or appenders, and configuration files.
Lesson: Log errors with java.util.loggingA catch block that logs the exception and then throws it again. When several layers do it, one failure shows up in the log several times, each with a stack trace. Each catch block should either handle and log, or translate and pass on, never both.
Lesson: Where to catch: anti-patternsmain do?It catches the app's own exceptions and shows their user-friendly message, then catches everything else, logs it as SEVERE with the stack trace and shows a general "unexpected error" sentence. It also returns a suitable exit code for each case.
Throwable ever a good idea?Very rarely. Throwable includes Errors like OutOfMemoryError, after which the JVM may not be in a usable state. At most a top-level handler might catch it to log and exit; normal code should never catch it.
It has a maximum number of attempts, a wait between attempts that usually grows (backoff), and a give-up step that rethrows or wraps the last exception. It retries only temporary errors and only operations that are safe to repeat. Each failed attempt is logged at WARNING, the final failure once at SEVERE.
The retries should stop. I catch InterruptedException, restore the flag with Thread.currentThread().interrupt() so callers can see it, and then end the loop, for example by throwing an InterruptedIOException.
If the first attempt actually saved but failed afterwards, a retry adds the payment twice. Only operations that give the same result when repeated should be retried; otherwise the operation needs a check, such as a unique payment id, before it runs again.
Lesson: Retry and recover from file errorsValidate input early with guard clauses and record constructors. Give the app one base exception and translate IOException into a StorageException at the storage layer, keeping the cause. Then one top-level handler shows user-safe messages, logs unexpected errors with stack traces, and returns exit codes; retries only for temporary errors.
I would go through each catch and decide what the failure should cause: handle it where there is a sensible recovery (skip and count, default, ask the user), translate it with the cause at a layer boundary, or remove the catch and let it propagate. Then I would make sure every failure is visible in a count, a log or a message.
Lesson: Designing error handling: practiceWith a helper that has an attempt limit, a growing wait and a give-up step. It retries only temporary errors, rethrows permanent ones such as NoSuchFileException at once, restores the interrupt flag if the sleep is interrupted, and wraps the final failure in the app's own exception with the cause.
When the records are independent and the rest of the job is still useful, like importing attendance rows. Then I skip it, count it and report it with the line number and value. If the records depend on each other, such as a bank transfer file, stopping is safer.
Lesson: Designing error handling: practiceEach lesson has quizzes, practice and more interview questions on the same topic.
Open the first lesson