Interview questions · free
Java Developer interview questions and answers
29 questions Java Developer interviews in India ask, grouped by the 7 skills they test, with a short model answer for each. Then the HR round every job has. Try each one out loud before you open the answer.
- 29 questions
- 7 skills
- 17 for freshers
- Updated 30 Oct 2026
First mock interview free: 3 questions, a score and feedback on each · No card needed
Answer out loud first. Read the question, say your answer as you would in the room, then open the model answer and compare. Interviewers ask follow-ups, so learn the reasoning, not the words.
Core Java · Core skill
OOP
Java interviews in India almost always open with OOP: interviewers check that you understand how Java's objects, equality and inheritance really behave, not just the definitions.
Why must you override hashCode() whenever you override equals() in Java?Fresher
Because of the contract between them: if two objects are equal by equals(), they must return the same hashCode(). HashMap and HashSet first use the hash code to pick a bucket, and only then call equals() inside that bucket. If you override only equals(), two equal objects usually get different default hash codes, land in different buckets, and the set happily stores both, or map.get() with an equal key returns null.
Use the same fields in both methods, for example with Objects.equals() and Objects.hash(). Records generate both for you. Avoid fields that can change while the object is a key in a map.
What is the difference between method overloading and method overriding?Fresher
Overloading means several methods with the same name but different parameter lists, such as print(int) and print(String). The compiler picks one at compile time from the argument types; changing only the return type isn't allowed.
Overriding means a subclass gives its own version of an inherited instance method with the same signature. Which version runs is decided at runtime by the actual object, not the variable's type: Animal a = new Dog(); a.sound() runs Dog's method. An override can't reduce visibility or throw broader checked exceptions, but it may return a subtype. Static, private and final methods can't be overridden. Add @Override so the compiler catches signature mistakes.
How would you make a class immutable in Java, and why would you want to?1-6 years
Make the class final so no subclass can add mutable state, make every field private and final, set them all in the constructor, and provide no setters. If a field holds a mutable object, such as a List or a Date, copy it in the constructor and return a copy or an unmodifiable view from the getter, otherwise callers can change it from outside. A record gives you most of this in one line, but you still have to copy mutable components yourself.
Immutable objects are thread-safe without locks, safe to use as map keys and easy to reason about. String, Integer and LocalDate are all immutable. A "change" returns a new object, like LocalDate.plusDays().
Explain the SOLID principles. Which one have you actually applied, and how?1-6 years
Single responsibility: a class has one reason to change. Open/closed: add behaviour by adding code, not editing working code. Liskov substitution: a subclass must work wherever its parent is expected. Interface segregation: small, focused interfaces instead of one large one. Dependency inversion: depend on abstractions, not concrete classes.
Then give one real example: "Our checkout had a long if-else for each payment type. I introduced a PaymentProcessor interface with one class per method, chosen from a map. Adding wallet payments became a new class, with no change to the checkout code (open/closed), and the service depended only on the interface, so it was easy to test with mocks." One honest example beats five definitions.
Core Java · Core skill
Collections
Collections are used in nearly every line of real Java code, so interviewers test whether you pick the right one and know how HashMap works inside.
What is the difference between List, Set and Map? Which implementation would you choose to keep insertion order without duplicates?Fresher
A List is an ordered collection that allows duplicates and access by index, like ArrayList. A Set holds no duplicates. A Map stores key-value pairs with unique keys; it isn't a Collection itself.
Each has implementations for different needs. HashSet and HashMap are the fastest but have no predictable order. LinkedHashSet and LinkedHashMap keep insertion order. TreeSet and TreeMap keep elements sorted, with O(log n) operations. So to keep insertion order without duplicates, use a LinkedHashSet, for example to remove repeated email addresses from a list while keeping the first-seen order. Saying why you rejected the other options shows you understand the trade-offs.
How does HashMap work internally?Fresher
A HashMap is an array of buckets, 16 by default. To store a key, it calls the key's hashCode(), mixes the higher bits into the lower ones, and uses the result to pick a bucket index. Each bucket holds nodes of key, value and hash. If two keys land in the same bucket (a collision), they're chained; since Java 8, a bucket with more than 8 entries turns into a red-black tree once the table has at least 64 buckets.
On get(), it finds the bucket, then compares the hash and calls equals() to find the right key. When the number of entries passes capacity times the load factor (0.75), it doubles the array and redistributes the entries. It allows one null key and isn't thread-safe.
What is the difference between Comparable and Comparator? How would you sort employees by department, then by salary from highest to lowest?Fresher
Comparable defines a class's natural order through its own compareTo() method, so there's only one, like String's alphabetical order. Comparator is a separate object that defines an order from outside the class, so you can have as many as you need without changing the class.
For this sort, build a Comparator: employees.sort(Comparator.comparing(Employee::getDepartment).thenComparing(Employee::getSalary, Comparator.reverseOrder())). The first key sorts by department, and the second sorts by salary in descending order within each department. Sorting a List this way is stable and O(n log n). A common mistake is subtracting values in compareTo(), which can overflow; use Integer.compare() or the Comparator helpers instead.
Using streams, how would you find the average salary in each department? What do you watch out for when using streams?1-6 years
employees.stream().collect(Collectors.groupingBy(Employee::getDepartment, Collectors.averagingDouble(Employee::getSalary))) returns a Map from department to average salary. groupingBy with a downstream collector such as counting(), summingDouble() or mapping() covers most reporting needs.
Watch out for a few things. A stream can be consumed only once. Lambdas shouldn't change outside state, such as adding to a shared list. Parallel streams aren't automatically faster: they use the shared ForkJoinPool and only help with large, CPU-heavy work. Checked exceptions inside lambdas are awkward. Long chains can be harder to read and debug than a simple loop, so use streams where they make the intent clearer.
You get a ConcurrentModificationException while removing items from a list inside a for-each loop. Why, and how do you fix it?1-6 years
The for-each loop uses an Iterator, and ArrayList's iterator is fail-fast: the list keeps a modification count, and if the list is changed structurally other than through the iterator, the next call to next() throws ConcurrentModificationException. It happens even with a single thread.
Fixes: use list.removeIf(item -> item.isExpired()), which is the cleanest; or loop with an explicit Iterator and call iterator.remove(); or collect the items to keep into a new list with a stream. If several threads really share the collection, use a concurrent one such as CopyOnWriteArrayList or ConcurrentHashMap, whose iterators don't throw this exception. Interviewers often ask next why a normal index-based loop that removes elements can skip items.
Core Java
Exceptions
Interviewers want to see that your code fails cleanly: the right exception type, resources closed, and errors handled at the right layer instead of swallowed.
What is the difference between checked and unchecked exceptions? Give examples.Fresher
Checked exceptions are subclasses of Exception that aren't RuntimeExceptions, such as IOException and SQLException. The compiler forces you to catch them or declare them with throws, because the caller is expected to recover, for example by retrying or showing a message. Unchecked exceptions are RuntimeException and its subclasses, such as NullPointerException, IllegalArgumentException and ArrayIndexOutOfBoundsException. They usually mean a programming mistake or invalid input, and the compiler doesn't force handling.
Errors, like OutOfMemoryError, are also unchecked and usually shouldn't be caught. Many modern libraries prefer unchecked exceptions; Spring, for example, wraps SQLException in its unchecked DataAccessException.
What is try-with-resources, and why is it better than closing resources in a finally block?Fresher
Try-with-resources, added in Java 7, closes any object that implements AutoCloseable automatically when the block ends, whether it ends normally or with an exception. For example: try (Connection con = dataSource.getConnection(); PreparedStatement ps = con.prepareStatement(sql)) { ... }. Resources are closed in the reverse order they were opened.
With finally you had to check for null, wrap close() in its own try-catch, and an exception thrown by close() could hide the original error. Try-with-resources keeps the original exception and attaches any exceptions from close() as suppressed exceptions, which you can read with getSuppressed(). Forgetting to close JDBC connections is a classic cause of a connection pool running dry.
How do you handle exceptions across the layers of an application: repository, service and controller?1-6 years
Catch an exception only where you can do something useful with it, and never swallow it with an empty catch. In the repository layer, low-level errors like SQLException are translated into meaningful exceptions; Spring does this for you. In the service layer, throw domain exceptions that describe the business problem, such as InsufficientBalanceException or OrderNotFoundException, and pass the original as the cause: throw new OrderNotFoundException(id, e).
At the top, one global handler maps each exception to the right response: 404 for not found, 400 for invalid input, and a generic 500 for anything unexpected, without showing stack traces to users. Log each error once, at the boundary, with context such as the order ID, rather than logging and rethrowing at every layer.
Core Java
Concurrency
Java backends serve many requests at once, so interviewers check that you can run work in parallel safely and recognise race conditions.
What are the ways to create a thread in Java, and why is an ExecutorService usually preferred?Fresher
You can extend Thread and override run(), or, better, implement Runnable and pass it to a Thread, which keeps your task separate from the threading mechanism. If the task returns a result or throws a checked exception, implement Callable and submit it to an ExecutorService, which returns a Future.
Creating a new thread for every task is expensive and unbounded. An ExecutorService, such as Executors.newFixedThreadPool(10), reuses a pool of threads, queues extra tasks, limits how many run at once and lets you shut down cleanly with shutdown(). Since Java 21, Executors.newVirtualThreadPerTaskExecutor() suits large numbers of tasks that mostly wait on I/O. Forgetting to shut down an executor can keep the application from exiting.
What is the difference between synchronized and volatile? Would a volatile counter be thread-safe?1-6 years
volatile guarantees visibility: a write to the variable by one thread is seen by other threads reading it, and the compiler and CPU can't reorder operations around it in unsafe ways. It doesn't make compound actions atomic. synchronized gives mutual exclusion, so only one thread runs the block for a given lock at a time, and it also gives visibility when the lock is released and acquired.
So a volatile counter isn't thread-safe: count++ is a read, an add and a write, and two threads can interleave and lose an update. Use AtomicInteger, LongAdder or a synchronized method for counters. volatile is right for a simple flag, such as a running boolean that another thread sets to false to stop a loop.
Your service calls three independent external APIs one after another, and the response is slow. How would you run them in parallel?1-6 years
Use CompletableFuture. Start each call with CompletableFuture.supplyAsync(() -> client.getX(), executor), passing your own executor sized for I/O rather than the default common pool, which is meant for CPU work. Then wait for all of them with CompletableFuture.allOf(a, b, c).join() and read each result with join(), or combine two with thenCombine(). The total time becomes roughly the slowest call instead of the sum.
Handle failures per call with exceptionally() or handle(), for example returning a default if a non-critical API fails, and put a limit on each with orTimeout(). Also check that the external services can take the parallel load, and that things like the logging context are passed to the worker threads if you rely on them.
Reading an answer isn't the same as giving one
In a mock interview you answer new Java Developer questions out loud or typed, and every answer is scored, with what to fix and a stronger answer.
Spring Boot
Fundamentals
Most Java developer roles in India are Spring Boot projects, so even a core Java interview checks that you understand dependency injection and how a Boot app starts.
What is dependency injection, and how does Spring do it?Fresher
Dependency injection means an object receives the objects it needs from outside instead of creating them itself. An OrderService gets a PaymentGateway passed in rather than calling new RazorpayGateway(), so you can swap the implementation or pass a mock in tests.
In Spring, the container creates objects called beans, from classes marked @Component, @Service, @Repository or @Controller, or from @Bean methods in a @Configuration class, and wires them together. Constructor injection is preferred: the fields can be final, required dependencies are obvious, and tests can create the class directly. With a single constructor, Spring injects it without @Autowired. Field injection hides dependencies and makes testing harder.
What does the @SpringBootApplication annotation do?Fresher
It combines three annotations. @SpringBootConfiguration marks the class as a source of bean definitions. @EnableAutoConfiguration tells Spring Boot to configure beans based on the libraries on the classpath, for example a DataSource when a database driver is present. @ComponentScan scans the class's package and its sub-packages for components.
The main method calls SpringApplication.run(), which creates the application context, runs the auto-configuration and, for a web app, starts the embedded server, Tomcat by default. A common mistake is putting a controller or service in a package outside the main class's package; it's never scanned, and you get a 404 or a missing bean error.
How do you use different database settings for development, testing and production in a Spring Boot application?1-6 years
Use profiles. Common settings go in application.properties (or .yml), and each environment gets its own file, such as application-dev.properties and application-prod.properties, holding the database URL, pool size and logging levels. Activate one with spring.profiles.active, usually set by the SPRING_PROFILES_ACTIVE environment variable on the server. @Profile on a bean creates it only in that profile, for example a fake email sender in dev.
Keep passwords out of the repository: environment variables and command-line arguments override the packaged files, and Boot maps SPRING_DATASOURCE_PASSWORD to spring.datasource.password. In production the value usually comes from a secrets manager. For groups of settings, bind them to a class with @ConfigurationProperties instead of many @Value fields.
Databases
SQL
Java developers write and debug SQL behind every feature, often through JDBC or JPA, so interviewers test both queries and how you keep data correct.
What is the difference between DELETE, TRUNCATE and DROP?Fresher
DELETE removes rows, all of them or only those matching a WHERE clause, one row at a time. It fires delete triggers and can be rolled back inside a transaction. TRUNCATE removes all rows at once, with no WHERE clause; it's much faster on large tables, and in MySQL it also resets the auto-increment counter. DROP removes the whole table: its structure, data, indexes and permissions.
One detail interviewers like: in MySQL and Oracle, TRUNCATE and DROP are DDL statements that commit automatically, so they can't be rolled back, while PostgreSQL and SQL Server can roll back a TRUNCATE inside a transaction. Mention which database you've used.
Why should you use PreparedStatement instead of Statement in JDBC?Fresher
Mainly to prevent SQL injection. With Statement you build the SQL by joining strings, so a user who types ' OR '1'='1 into a login form can change the query. With PreparedStatement you write placeholders: SELECT * FROM users WHERE email = ? and then call ps.setString(1, email). The value is sent separately from the SQL, so it's always treated as data, never as code.
It also handles quoting and types for you, such as names with apostrophes and dates, and the database can reuse the query plan when the same statement runs many times. Placeholders work only for values; table or column names, for example in ORDER BY, must be checked against a fixed list.
Two users book the last seat for a show at the same moment, and both bookings succeed. How would you prevent this?1-6 years
This is a race condition: both transactions read the seat as free before either writes. Wrapping the code in a transaction isn't enough at the default isolation level. There are three common fixes. Lock the row while checking it: SELECT ... FOR UPDATE inside the transaction, so the second booking waits and then sees the seat taken. Or make the write conditional: UPDATE seats SET booked_by = ? WHERE id = ? AND booked_by IS NULL, and treat zero affected rows as "already booked". Or add a unique constraint on (show_id, seat_no) in the bookings table, so the database rejects the second insert.
With JPA, optimistic locking with a @Version column works well when conflicts are rare. The unique constraint is the safety net either way.
Testing
Unit testing
Teams expect Java developers to test their own code with JUnit and Mockito, and interviewers ask how you test logic that depends on other classes.
What JUnit 5 annotations do you use most, and how do you test that a method throws an exception?Fresher
@Test marks a test method. @BeforeEach and @AfterEach run before and after every test, for example to create a fresh object; @BeforeAll and @AfterAll run once for the whole class and must be static unless you switch to the per-class test lifecycle. @DisplayName gives a readable name, @Disabled skips a test, and @ParameterizedTest with @ValueSource or @CsvSource runs one test with many inputs, which is ideal for edge cases.
For exceptions, use assertThrows: var ex = assertThrows(IllegalArgumentException.class, () -> account.withdraw(-100)); then check ex.getMessage(). It also fails the test if nothing is thrown. In JUnit 4 the equivalents were @Before, @After and @Test(expected = ...), so say which version your project used.
What is the difference between a mock and a stub? How do you create a mock with Mockito?Fresher
A stub returns fixed answers so the code under test can run, and you check the result. A mock also records how it was called, so you can verify the interaction, for example that an email was sent exactly once. Mockito objects can do both.
With JUnit 5: annotate the class with @ExtendWith(MockitoExtension.class), declare @Mock PaymentGateway gateway and @InjectMocks OrderService service. Stub with when(gateway.charge(any())).thenReturn(success), call the service, assert the result, then verify(gateway).charge(order). Mock the boundaries, such as databases, external APIs and clocks, not simple value objects. Too many mocks make tests pass while the real code is broken.
Your project has 85% code coverage, but bugs still reach production. What would you look at?1-6 years
Coverage shows which lines ran during the tests, not whether the tests checked the right behaviour. So I'd look at tests with weak or no assertions, tests that cover only the happy path, and missing edge cases: null and empty inputs, boundary values, negative amounts, time zones and duplicate requests. Heavy mocking is another cause: every unit passes, but the parts fail together, so add some integration tests for database queries and API contracts.
Branch coverage is more honest than line coverage, and mutation testing with a tool like PIT changes your code to see whether any test fails. Most useful of all: for every production bug, write a failing test first, then fix it.
Every job
HR and behavioural round
Java roles get many applicants, so the HR round checks communication, willingness to learn and fit with project work, often in a service company's delivery model.
Tell me about yourself.Fresher
Keep it to about a minute and point everything towards the Java role. Present, past, future works well: "I'm a [degree] graduate from [college], and I enjoy building backend applications in Java. For my final-year project I built [project] with Spring Boot and MySQL, where I wrote the [module] and [one result, like cutting load time or handling a number of users]. I've also practised [DSA on a platform / an internship or certification]. I'm looking to start as a Java developer where I can work on real production code and grow into backend design."
Pick one project to talk about in depth, because the next question is often about it.
Why Java? Many freshers are choosing Python or JavaScript now.Fresher
Give real reasons, not "Java is popular". Good ones: it's widely used in banking, insurance, e-commerce and large enterprise systems, so the work is serious backend engineering; its strong typing and tooling suit large codebases; and Spring Boot is a mature ecosystem you've already built with. Then show that the choice came from experience: "I tried Python for scripting, but I enjoyed designing classes and APIs in Java more, and building [project] in Spring Boot confirmed it."
Don't criticise other languages. Adding that you're happy to learn others when a project needs them shows flexibility.
Are you willing to work on any technology or project we assign, even if it isn't Java?Fresher
Interviewers, especially at service companies, ask this because project needs change. Answer honestly and positively: say yes, show you can learn, and mention your preference without making it a condition. For example: "Yes. My strongest skills are in Java and Spring Boot, and I'd love to use them, but I picked up [another tool, such as React or SQL] quickly for my project, so I'm confident I can learn what the project needs. Over time, I'd like to build deeper expertise in backend development."
If there's something you truly can't accept, it's better to say so politely now than to refuse a project later.
Why are you looking to leave your current company?1-6 years
Talk about what you're moving towards, not what you're running from. Good reasons are growth, technology, scope or a better fit: "I've spent [n] years on [project], maintaining a Spring monolith and handling production support. I've learned a lot, but I'd like to work on [microservices / product development / a larger scale], and this role offers that." If there's a fact like a project ending or a relocation, state it simply.
Never criticise your manager or company, even if it's true; interviewers assume you'd say the same about them. Keep your answer consistent with your resume and notice period.
Tell me about a time you disagreed with a senior developer's technical approach. What did you do?1-6 years
Use the STAR method and show that you disagreed with data and respect, then supported the final decision. For example: "Our lead wanted to load all transactions into memory for a monthly report (situation). I was writing the export (task). I tested it with a production-sized copy, showed that it needed several gigabytes of memory, and suggested streaming the rows in pages instead, sharing a small prototype (action). We used the paged approach, and the report ran without memory errors (result). If he'd chosen otherwise, I'd still have built it his way and raised the risk in writing."
Avoid stories where you were simply right and the senior was wrong.
Free interview questions, roadmaps and mock interviews: https://myskillgrid.com/interview-questions/java-developer/
Ready to answer them for real?
Your first Java Developer mock interview is free: 3 questions, a score and feedback for each, and a stronger answer to learn from, in about 10 minutes.