Java interview prep cheat sheet
I asked ChatGPT to interview me based on a job description for a Java/Spring developer position. There were no years of experience required for the job, so I was assuming mid level, but asked for mid/senior level questions.
I don't necessarily think these were senior level questions, but the LLM did a good job regardless.
It's extremely helpful to prepare like this!
I get to answer questions, and the LLM points out the stuff I omitted.
ChatGPT (GPT-5.2) prepared a list of topics to discuss based on its understanding of a mid/senior level Java/Spring interview, and asked me questions about them.
I list the answers here for future reference. Here's to hoping this will be useful to you/me in the future.
- Java Core & OOP
- Concurrency (Threading & Multiprocessing)
- Design Patterns
- Spring & Spring Boot
- Persistence & Databases (SQL/NoSQL, ORM)
- Messaging & Distributed Systems
- Testing
- Build Tools & DevOps
- System Design / Senior-level discussion
1. Java Core & OOP
- Differences between == and .equals()? How does it apply to a String? What role does hashCode() play?
- == compares references (memory addresses for objects)
- .equals() compares logical equality (object state), depending on implementation
- String.equals() compares character sequences
- Equal objects must have the same hashCode()
- Hash collisions are possible
- .equals() is defined in Object and should be overridden
- If .equals() is not overridden, it behaves like ==
- hashCode() is critical for hash-based collections (HashMap, HashSet)
- What happens if you override .equals() but do not override hashCode(), and then use the object as a key in a HashMap?
- Two objects that are logically equal (equals() == true) will:
- Produce different hash codes
- Go into different buckets in a HashMap
- You cannot retrieve the value using an equal (but different) key object
HashMap lookup steps:
- Compute hashCode()
- Find bucket
- Use equals() to find exact key
3.Why is String immutable?
- thread safety
- safe memory optimization (String Pool/String interning)
- cached hashCode() (computed once, reused), faster lookups in hash-based collections
- boxed classes are also immutable (Integer, Double, Long, etc.)
- some degree of TOCTOU (Time of Check, Time of Use) safety
checkPermission(path);
// compromised Java code can not modify 'path' without its reference changing
openFile(path);
- Difference between String, StringBuilder, StringBuffer
- String - immutable
- StringBuilder - mutable, not thread-safe
- StringBuffer - mutable, thread-safe, mostly legacy
- Memory & JVM
Stack vs Heap, where are
- objects
- local variables
- static variables
- String literals
stored?
- Objects - heap
- Local variables, object references - stack
- Static variables - heap
- String literals - heap (String pool)
2. Concurrency
- synchronized vs volatile vs AtomicInteger
- synchronized:
- methods or blocks
- mutual exclusion, critical sections
- visibility
- uses locks, OS support for putting threads to sleep (after exhausing CAS period)
- e.g. more code that needs to be protected
- volatile
- visibility, threads always read the latest value
- not good for ++/-- compound operations
- e.g. flags
- AtomicInteger
- atomic read-modify-write operations
- lock-free, not synchronized, uses compare-and-swap (CAS) CPU instruction
- e.g. counters
- Difference between wait(), notify(), sleep()
wait,notify:- live on Object
- used for condition-based thread coordination
- must be called from synchronized context (needs a lock)
- called in a loop with a condition check to avoid bugs, spurious wakeups
wait()releases the lock, acquires it when notified, when it returns it already has the locknotify()wakes up a waiting threadnotifyAll()wakes up all waiting threads
sleep:- static on Thread
- used for thread sleep
- does not release the lock
- Thread pools (ExecutorService) vs manual Threads
- creating threads manually:
- expensive (OS resources, stack allocation)
- too many threads cause context switching, memory pressure, reduced throughput
- must finish execution before GC'd (garbage collected)
- using a thread pool (ExecutorService):
- reuse threads
- bound concurrency (max thread count)
- task queueing when all busy
- separates task submission from execution
- can be tuned for IO/CPU-bound workloads, foundation for async, reactive
- Runnable vs Callable
- Runnable:
- no return value
- no checked exceptions
- old, used by Thread, ExecutorService
- Callable:
- returns a value
- can throw checked exceptions
- Future vs CompletableFuture
- Future:
- .get() blocks
- no callbacks
- no composition
- poor error handling
- no manual completion (only by the ExecutorService)
- CompletableFuture:
- callbacks
- composition (chaining, combining, etc.)
- exception handling
- manual completion
TODO: OOP questions, principles SOLID, DRY, etc. whatever goes on interviews these days
3. Spring & Spring Boot
- What is DI (dependency injection)? How does it work in Spring?
- object's dependencies are provided from the outside, rather than the object creating them itself
- loose coupling
- better testability
- Spring:
- uses an IoC container (ApplicationContext), which instantiates beans, resolves dependencies, manages bean lifecycle
- defining beans:
- component scanning/class annotations:
- @Component, @Service, @Repository, @Controller
- @Configuration, @Bean for creating them if needed
- XML configuration
- Java configuration
- component scanning/class annotations:
- injecting beans (@Autowired):
- constructor injection (preferred): explicit, enables immutability, simple testing, no partially constructed objects
- field injection
- method injection
- @Component vs @Service vs @Repository
- stereotype annotations
- separation of layers
- @Component:
- generic
- @Service, @Component mainly semantic
- @Repository adds exception translation
- Spring Boot vs Spring
- Spring Boot:
- removes boilerplate code
- auto-configuration based on classpath, application properties
- opinionated defaults
- embedded servers
- starter dependencies ensure compatible versions
- How does autoconfiguration work internally?
- @EnableAutoConfiguration:
- loads config from config file in META-INF/...
- beans are created if conditions match, guarded by @ConditionalOn... annotations
- @SpringBootApplication is a meta annotation:
- @EnableAutoConfiguration
- @ComponentScan
- @Configuration
- Spring MVC, REST APIs: @Controller vs @RestController, @ResponseBody?
-
@Controller:
- marks a class as a controller
- handles HTTP requests
- returns views, view names (JSP, Thymeleaf, etc.)
-
@RestController (@Controller + @ResponseBody):
- marks a class as a REST controller
- handles HTTP requests
- returns data (JSON, XML, etc.), usually JSON serialized with Jackson
- Exception handling in Spring
- @ExceptionHandler:
- handle specific exceptions
- in controller, in global handler
- @ControllerAdvice/@RestControllerAdvice:
- global exception handling
- multiple controllers
- @ResponseStatus:
- maps exception to HTTP status code
- can use ResponseEntity object for more control
- use custom exceptions for domain errors
- avoid leaking internal exceptions
- be consistent with the error formatting, status codes, semantics
- log at the correct level
- 4xx client, 5xx server errors
- JPA/Hibernate: @Entity vs @Embeddable vs @MappedSuperclass
- @Entity:
- mapped to a database table
- has an id
- can have lifecycle callbacks
- @Embeddable + @Embedded:
- can be embedded in another entity (columns in a row, a value object)
- no id
- no lifecycle callbacks
- @MappedSuperclass:
- hoist common attributes into a superclass
- child classes inherit the attributes, but are mapped to separate tables
- used for id, timestamps (audit fields), etc.
- Transaction management in Spring
- @Transactional:
- marks a method as transactional
- Spring uses AOP proxies (like decorators in Python almost)
- starts a transaction before the method is called
- commits the transaction after the method returns
- rolls back the transaction if an unchecked exception is thrown
- propagation can be tuned (do nested transactions open new ones, or join an existing one, etc.)
- Messaging systems: what do they solve?
- decouple components:
- producers don't need to know who the consumers are, how many there are
- reliability:
- messages stored durably (usually)
- failed consumers can reprocess queued up messages
- scalability:
- horizontal scaling by increasing consumer count, round robin
- Kafka vs RabbitMQ
-
Kafka:
- distributed append-only log
- message TTLs
- consumers track offsets
- topics and partitions
- ordered per partition
- message replay
- event sourcing
- stream processing
- uses own protocol
- "dumb broker, smart consumers"
-
RabbitMQ:
- queue-based
- focus on routing, topic based filtering
- exchanges and queues
- messages removed once ACKed
- no replay
- dead-letter queues
- uses AMQP
- "smart broker, dumb consumers"
4. Testing
How do you test a Spring Boot application?
- unit tests:
- mock dependencies
- don't start the entire application context
- integration tests:
- can initialize the entire context with @SpringBootTest
- can use slices:
- @WebMvcTest
- @DataJpaTest/@DataMongoTest
- @RestClientTest
- can use TestContainers, embedded DBs
5. Build tools
- Maven:
- declarative
- convention over configuration
- xml-based
- linear lifecycle
- no incremental builds by default
- Gradle:
- script-based (Groovy/Kotlin DSL)
- more expressive
- incremental builds
- build cache
- parallel execution
6. DevOps, Monitoring, Caching
Why use caching like Redis?
- in memory, fast access to hot data paths
- orders of magnitude faster than disk/network DB calls
- reduces DB load
- absorbs traffic spikes
- cache patterns:
- cache aside
- write through (synchronous DB updates)
- write behind (asynchronous DB updates)
- good for rate limiting, distributed locks (TTL based leases)
DevOps and Monitoring questions left out...
Things like Docker, Kubernetes, Grafana, ELK stack, etc.