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.
61 questionsLast updated
deleteOnExit not a good clean-up strategy?It runs only when the JVM ends normally, so a crash or kill leaves the files. In a long-running server every registered path stays in memory until shutdown, and it cannot delete non-empty folders. I delete temp files myself in finally or with try-with-resources.
Files.list and Files.walk?Files.list returns only the direct children of one folder. Files.walk goes through the whole tree, depth first, and includes the start folder itself; with a second argument I can limit how deep it goes.
Files.find instead of Files.walk with a filter?When the condition uses file attributes such as size or modified time. find hands the attributes to my test, already read during the walk, so I avoid a separate Files.size call per file and the checked exception that comes with it.
Files.walk hits a folder it cannot read?The stream throws UncheckedIOException wrapping the real error, often AccessDeniedException, and the walk stops. If I want to skip such folders and continue, I use Files.walkFileTree with a visitor that handles visitFileFailed.
Files.delete only removes empty folders, so I delete the tree deepest first. Either I collect Files.walk(root) sorted with Comparator.reverseOrder() and delete each path, or I use walkFileTree and delete files in visitFile and folders in postVisitDirectory.
String.startsWith not enough for that check?It compares characters, so /srv/uploads-old/x starts with /srv/uploads as text. Path.startsWith compares whole names, so uploads-old does not match uploads.
Files.copy not copy a folder's contents, and how do you copy a whole tree?Files.copy works on one entry, so for a folder it creates just the empty folder. For a tree I walk the source and, for each path, compute target.resolve(source.relativize(p)), creating folders before copying the files into them.
I build them from parts with Path.of(a, b) or resolve, so Java chooses the separator. User files start from user.home, other locations come from arguments or settings, and I use the exact case of every file name because Linux is case-sensitive.
Files.walk and Files.list stream be in try-with-resources?Each one holds an open directory handle until it is closed. In a loop or a long-running server, unclosed streams leak handles until the program hits the operating system's limit or folders stay locked on Windows.
Lesson: Paths and files: common mistakes and practiceFiles.delete and Files.deleteIfExists, and which do you use in clean-up code?delete throws NoSuchFileException if nothing is there, while deleteIfExists returns false. In clean-up code, where "already gone" is fine, I use deleteIfExists; when the file must exist, I use delete so the problem is not hidden.
Both load the entire file into memory before returning. For a settings file or a class list that is fine, but a log of several gigabytes would not fit in the heap and the program would fail with OutOfMemoryError. For big files I read line by line with Files.lines or a BufferedReader.
NoSuchFileException and FileNotFoundException?Both mean the file could not be found or opened. NoSuchFileException comes from the newer java.nio.file API, such as the Files methods. FileNotFoundException comes from older java.io classes such as FileReader and FileInputStream. Both are subclasses of IOException.
Files.writeString and Files.write?writeString(path, text) writes one String exactly as it is, so you put the \n characters in yourself. write(path, lines) takes a list of lines and adds the system line separator after each one. write also has a version that takes a byte[] for raw bytes.
CREATE_NEW instead of checking Files.exists and then writing?Between the exists check and the write, another program or thread can create the file, and then my write would overwrite it. CREATE_NEW does the check and the create as one step inside the operating system, so exactly one writer wins and the other gets FileAlreadyExistsException.
writeString with APPEND once for each of a million log lines?It works, but every call opens the file, writes a few bytes and closes it again, which is slow. For many lines I would open the file once with a BufferedWriter, write all lines and close it, or collect the lines and write them with one call.
₹250 instead of ₹250. What happened?The text was saved as UTF-8, where ₹ is three bytes, but read with windows-1252, which turned each byte into its own character. The fix is to read with UTF-8. If the garbled text was already saved again, it may need to be repaired by encoding it back with windows-1252 and decoding with UTF-8.
MalformedInputException, and how do you fix it?You get it when the bytes are not valid in the charset you read with, typically a windows-1252 or Latin-1 file read as UTF-8. The fix is to find out how the file was really saved and read it with that charset, then convert it to UTF-8 if the rest of the system expects UTF-8.
Lesson: Character sets and UTF-8Both mark the end of a line. Windows uses CRLF, the two characters \r\n (carriage return and line feed, names from old typewriters). Linux and macOS use LF, a single \n. So the same text is one byte per line bigger on Windows.
Files.readAllLines, Files.lines, BufferedReader.readLine, String.lines() and Scanner.nextLine all treat \r\n, \n and \r as line breaks and remove them. Problems come from hand-written code such as split("\n") or indexOf('\n').
Files.readAllLines and Files.lines?readAllLines reads the whole file and returns a List<String>, so memory grows with the file. Files.lines returns a lazy Stream<String> that reads lines as the pipeline needs them, so memory stays small even for gigabyte files. In return, the stream must be closed and can be used only once.
Files.lines really avoid reading the whole file?It reads the file in blocks of bytes through a buffer and builds one line at a time as the stream asks for it. If the pipeline ends early, for example with findFirst or limit, the rest of the file is never read. If the pipeline needs every line, it reads the whole file, but still never holds all of it in memory.
I would stream the file with Files.lines, map each line to its IP, and collect with groupingBy(ip -> ip, counting()). Memory then depends on the number of distinct IPs, not the file size. Finally I sort the map entries by count in descending order and take the first 10.
BufferedReader instead of reading one character at a time?Every trip to the operating system to read the disk costs time. A BufferedReader reads a big block, by default 8192 characters, into memory and then hands out characters and lines from that block. So a file of millions of characters needs only a few hundred real reads.
flush and close?flush pushes everything in the buffer to the file and keeps the writer open for more writes. close flushes and then releases the file; after that the writer cannot be used. I call flush only when someone else must see the data before I finish, such as a log being watched live.
Scanner and BufferedReader?Scanner reads tokens and converts them for you with nextInt, nextDouble and so on, which makes small programs easy. BufferedReader reads whole lines as Strings, and you split and parse them yourself. BufferedReader is much faster on big input and throws IOException on read errors, while Scanner hides them.
Scanner slow for large input?It finds and checks every token with regular expressions, and for numbers it also handles locale details. For a few values that cost is invisible, but for a million numbers it can be several times slower than BufferedReader with StringTokenizer and Integer.parseInt.
Output. Calling System.out.println hundreds of thousands of times is slow, so I append all answers to a StringBuilder and print it once at the end. I also use long when sums can go past about 2.1 × 10⁹.
An atomic rename only works within one file system; across drives the move turns into copy-and-delete. Then ATOMIC_MOVE throws AtomicMoveNotSupportedException. Creating the temp file next to the target avoids that, and as a fallback I can catch the exception and do a normal move with REPLACE_EXISTING.
The atomic move protects against crashes during the save, not against saving wrong data. A bug or a typing mistake can save bad content perfectly. A .bak copy taken before each save lets the user or support undo the last change.
By size and shape. A small file I need as one String: Files.readString. A small file to loop over: readAllLines. A file that can be large: Files.lines for filter-and-count jobs, or a BufferedReader loop when I need line numbers. For token-by-token reading of small input Scanner is easy; for big input I use BufferedReader with StringTokenizer.
With Files.lines in try-with-resources: flatMap each line into words with trim().split(" +"), filter out empty strings and count(). Only one line is in memory at a time. A BufferedReader loop with a counter works just as well.
Three common ones: the BufferedWriter was never closed or flushed, so the text stayed in memory; the file was rewritten with default options in a loop, so only the last write remained, which may have been empty; or the program crashed after the file was truncated. try-with-resources, APPEND where needed and the temp-then-move pattern fix them.
Byte streams (InputStream, OutputStream) move raw bytes and work for any data, including images and PDFs. Character streams (Reader, Writer) move text and use a charset to turn bytes into characters and back. Use bytes for binary data and characters for text.
java.io and one subclass of each.InputStream (for example FileInputStream), OutputStream (FileOutputStream), Reader (FileReader or BufferedReader) and Writer (FileWriter or PrintWriter). The first two handle bytes, the last two handle characters.
java.io.InputStream related to java.util.stream.Stream?No, they only share a word. InputStream reads a flow of bytes from a source such as a file. java.util.stream.Stream from Java 8 processes a sequence of objects with operations like filter and map.
read(buf) return fewer bytes than buf.length before the end of the stream?Yes. A file or network stream may return whatever is ready, so a short read does not mean the end; only -1 does. When you need exactly n bytes, use readNBytes, which keeps reading until it has them or the stream ends.
It opened the file with new FileOutputStream(file), which empties an existing file. Opening it with new FileOutputStream(file, true) writes in append mode, after the existing bytes.
FileReader and FileWriter?Readers decode bytes into characters with a charset. Image bytes are not valid text, so the decoder replaces them with U+FFFD, and the Writer encodes that as different bytes. The copy gets bigger and the original bytes cannot be recovered.
Lesson: Copy binary files such as imagesFirst compare Files.size of both files, which is cheap. If the sizes match, compare the content with Files.mismatch, which returns -1 for identical files. For files on two different machines, compare a checksum such as SHA-256 instead of sending the whole file.
FileInputStream slow?Each read() on an unbuffered file stream is a separate system call into the operating system, and each call has a fixed cost. For a 1 MB file that is about a million calls. A buffer reads 8 KB per call, so the same file needs only about 128.
System.exit(0) without closing the stream. What can go wrong?The bytes still in the buffer are never written, because the JVM does not flush open streams on exit. The report file is cut short. Closing the stream in try-with-resources before exiting fixes it.
Lesson: Buffering and why it mattersFileReader and FileInputStream?FileInputStream reads raw bytes and works for any file. FileReader reads characters: it reads the bytes and decodes them with a charset, so it is only for text files. For an image you use FileInputStream; for a text file, FileReader with UTF-8.
Reader.read() return an int and not a char?It needs a value that cannot be a character to signal the end of the stream. A char can hold every value from 0 to 65535, so read() returns an int in that range for data and -1 for the end. You cast it to char after checking for -1.
InputStreamReader, and why is it called a bridge?It wraps an InputStream and decodes the bytes into characters with a charset, so it connects the byte family of classes to the character family. OutputStreamWriter is the bridge in the other direction. FileReader and FileWriter are just these bridges built on file streams.
It is a file packed with the compiled classes, for example from src/main/resources. You open it with MyClass.class.getResourceAsStream("/name.txt"), which works from a folder or from inside a JAR, and wrap the stream in an InputStreamReader with UTF-8 for text.
MyClass.class.getResourceAsStream("x") and "/x"?Without a slash the name is relative to the package of MyClass, so com.app.MyClass looks for com/app/x. With a leading slash it is searched from the top of the classpath. ClassLoader.getResourceAsStream always searches from the top and takes names without a leading slash.
PrintWriter never throws IOException?A full disk or a lost network drive is silently ignored, so the program reports success while the file is incomplete. You must call checkError() after writing and treat true as a failure, or use a BufferedWriter, which throws.
System.out.println?Save System.out, call System.setOut with a PrintStream on a ByteArrayOutputStream, run the method, and restore the original in a finally block. Then compare the captured text. A cleaner design is to pass a PrintWriter or PrintStream to the method instead.
The binary data has no field names or separators, only bytes in a row. The reader decides what the next bytes mean by the method it calls, so a different order or type silently produces wrong values, or an exception if the lengths stop matching.
Lesson: Simple binary formats with data streamsWhen the data is large and numeric, must load fast, must keep doubles exact, and only my own program reads it, for example game state or sensor readings. For data people read, edit or share with other tools, I pick a text format, because it is easier to check and survives changes better.
Lesson: Simple binary formats with data streamsDataOutputStream on Windows read correctly on a Mac?Yes. Data streams always use big-endian byte order and fixed sizes, whatever the computer, so the bytes mean the same everywhere. Problems come only from reading in a different order or with different types.
Lesson: Simple binary formats with data streamsA buffered stream was never closed, so its last block stayed in memory. Closing the outermost stream, ideally in try-with-resources, flushes the buffer and closes every layer.
Lesson: Byte and character streams: common mistakes and practicenew BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8)). The socket gives bytes, the InputStreamReader decodes them with UTF-8, and the BufferedReader buffers them and provides readLine.
split(",", -1) instead of split(",")?With no limit, split removes empty strings at the end of the result, so "a,b,," gives 2 fields instead of 4. A negative limit keeps every field, so a row with empty last columns still has the right field count and I can report the missing values properly.
Spaces around values, empty fields, a header row, extra or missing columns, blank lines and dates in a different format than expected. I trim every field, keep empty fields with a limit of -1, check the field count and the header, and parse dates with a strict formatter.
Lesson: Turn a CSV line into a recordI open Files.lines(path, StandardCharsets.UTF_8) in try-with-resources, then skip(1) for the header, filter(l -> !l.isBlank()), map(Student::fromCsv) and toList(). The parsing rules live in the record's fromCsv method, so the pipeline stays short.
When I need the line number for error messages, or want to keep going after a bad row and collect several errors. A BufferedReader loop with a counter gives exact line numbers and full control over what happens on each line.
The line number is for a person who opens the file in an editor to fix it. Editors count every line, blank ones included, so my counter must too, or the message points to the wrong row.
Lesson: Read a whole CSV file into objectsIt reads the line one character at a time with a StringBuilder for the current field and a boolean inQuotes. Outside quotes, a comma ends the field and a quote at the start of a field opens quotes. Inside quotes, "" adds one quote and a single quote closes them. After the loop it adds the last field, and it throws if the line ended inside quotes.
Usually not. Libraries such as Apache Commons CSV or OpenCSV are tested on many real files and handle quoted line breaks, different separators and encodings. Writing a small parser is still worth doing once, because it shows exactly what those libraries do and what can go wrong.
Lesson: Quoted fields and commas inside valuesParsing turns text into a typed value, like Integer.parseInt("105"). Validation checks that the value is acceptable for this field, like marks being between 0 and 100. A row can parse perfectly and still be invalid.
Each lesson has quizzes, practice and more interview questions on the same topic.
Open the first lesson