Loading...
Loading...
Backend interview guide
Dependency injection, auto-configuration, beans, REST controllers and configuration. Every answer is short enough to say aloud, and every question links to the lesson that teaches it.
60 questionsLast updated
With tight coupling a class creates or names the exact class it uses. With loose coupling it depends only on an interface and receives the real object from outside, usually through its constructor. Then either side can change without editing the other.
Lesson: Tight coupling and the new keywordA unit test wants to run one class alone with known inputs. If the class creates a real email sender or database connection inside a method, the test cannot replace it, so it sends real emails or needs a real database. With constructor injection the test passes a fake instead.
Lesson: Tight coupling and the new keywordCalls like new OrderService() stop compiling with "cannot be applied to given types". I see that as useful: the compiler lists every place that must now choose which dependency to pass.
Inversion of control means my class does not control things it used to control itself, such as creating its collaborators or deciding when its code runs. Something outside does it: main or a container creates the objects, or a framework calls my methods.
Yes. Constructor injection is plain Java: I create the objects in main and pass them into constructors. That place is called the composition root. Spring only automates the creating and connecting.
Creating two objects where one shared object was needed, such as two stock repositories. Each class then works on its own copy, so a report shows old stock. It compiles and runs; only the results are wrong.
Lesson: Inversion of control in plain wordsIt is a set of Java libraries built around a dependency injection container, called the application context. Around the container are modules such as Spring Web MVC for web apps, JDBC support and transaction management.
Lesson: Spring Framework vs Spring BootSpring Boot looks at the libraries on the classpath and my existing beans, and creates the beans I would otherwise write by hand. For example, Tomcat on the classpath means it starts a web server on port 8080. If I define my own bean of that type, Boot backs off.
Lesson: Spring Framework vs Spring BootYes. With only spring-context I can create an AnnotationConfigApplicationContext from a @Configuration class and get beans from it. Boot just saves the setup work, which is why almost all new projects use it.
Spring Data, which writes repository code for databases; Spring Security, for login and access rules; and Spring Cloud, for systems made of many services. With Boot I add each one through its starter.
Lesson: Spring Framework vs Spring BootThe group identifies the organisation, usually a reversed domain such as com.shoplite. The artifact is the project's name, such as shoplite-api. Together with the version they identify the project in Maven.
A jar contains the embedded Tomcat, so java -jar runs the whole app. A war is meant for deploying into a separately installed server, which ShopLite does not need.
release version 17 not supported. What does it mean?Maven is running with a JDK older than 17, so javac cannot compile for Java 17. I check with mvnw -v, install JDK 17 or newer and point JAVA_HOME at it; in the IDE I also check the project SDK.
Component scanning starts at the main class's package and goes down. With the main class in com.shoplite, every feature package such as com.shoplite.product is found. A class outside, like com.hello, is silently ignored and its endpoints return 404.
A fat or executable jar contains my classes, all library jars and the embedded Tomcat. mvnw package builds it, and on the server I run java -jar shoplite-api.jar, with settings passed as arguments or environment variables.
contextLoads test do, and why keep it?It is an empty @SpringBootTest method, so it passes only if the whole application context starts. It catches wiring and configuration mistakes during package, before a broken jar is built.
Spring Boot is Spring set up for me: starters bring tested libraries, auto-configuration creates the usual beans, Tomcat is embedded, and defaults work out of the box. So a new API runs with one class and java -jar, and I spend my time on features, not setup.
new inside the class?That fixes the class to one implementation, so switching from email to SMS means editing the class, and tests do real work such as sending messages. With constructor injection I pass a different implementation or a fake without touching the class.
Lesson: Practice: why SpringThat is a circular dependency, and it usually means the design is wrong. I change one side to receive data instead of the other service, or move the shared logic into a third class that both use.
Lesson: Practice: why SpringApplicationContext is the Spring container that holds all beans. It extends BeanFactory, the basic interface for getting beans, and adds events, resource loading, messages and creating all singleton beans at startup. In Spring Boot I always work with an ApplicationContext; SpringApplication.run returns one.
Not exactly. Spring creates one object per bean definition in one container and shares it, but the class itself is normal: anyone can still call new on it. The classic Singleton pattern forces one object per class with a private constructor and a static getInstance() method.
Calling getBean hides the class's dependencies inside its code and ties the class to the container, so it is harder to read and to test. With constructor injection the needs are visible in the constructor, and a test can pass in simple fakes with new.
When data access support is on the classpath, Spring wraps @Repository beans in a proxy that turns technology-specific persistence exceptions, such as JPA's, into Spring's unchecked DataAccessException types like DuplicateKeyException. The service then catches Spring's exceptions and does not depend on MySQL or Hibernate classes. JdbcTemplate and JdbcClient translate SQLException themselves.
The default name is the simple class name with a small first letter: OrderMailer becomes orderMailer. If the first two letters are capitals, the name stays as it is, so GSTCalculator stays GSTCalculator. I can set my own name with @Service("feeCalculator").
Both get the same default bean name, so Spring stops at startup with ConflictingBeanDefinitionException. I either rename one class or give each bean an explicit name such as @Component("productPriceRules").
Required dependencies are visible in one place, the fields can be final so the object is complete and unchangeable, and a unit test can build the class with new and fakes without Spring. It also makes circular dependencies fail at startup instead of hiding them, and a long constructor warns me that the class does too much.
The service depends on a Notifier interface through its constructor. In the test I pass a fake Notifier that adds each message to a list, call the service with new, and assert on the list. No Spring context and no real SMS are involved.
Field injection would only hide the problem. Eleven dependencies mean the class has several jobs, so I would group helpers that answer one question, such as pricing, into their own service and inject that instead.
Lesson: Constructor injection and why to prefer itA class whose job is to define beans through @Bean methods. It is itself a component, so scanning finds it, and Spring makes sure that calling one @Bean method from another inside it still returns the shared bean.
So the time is a dependency I can replace. The app gets the real clock from a @Bean method, and tests pass Clock.fixed(...), so I can check "sale ends at midnight" or "order placed on a Sunday" on any day, without waiting or changing the computer's time.
If nothing decides between them, startup fails with "required a single bean, but 2 were found", backed by NoUniqueBeanDefinitionException. I fix it with @Primary on the normal choice, @Qualifier on the parameter for a specific one, or by injecting a List when I need all of them.
Inject List<DiscountPolicy> to loop over all of them, ordered by @Order, or Map<String, DiscountPolicy> to pick one by bean name at run time. Adding a new rule is then one new @Component class, with no if chain to change.
I define a DiscountPolicy interface and write each discount as a @Component class. The checkout injects one policy chosen with @Primary or @Qualifier, or a List if it picks the best one. A new festival discount is then a new class, and the checkout code stays as it is.
Not automatically. Spring only makes one shared object; if that object has a field that changes, concurrent requests can corrupt it. I keep singletons stateless, with only final fields holding other beans, so there is nothing to corrupt; a truly shared counter would use something like AtomicInteger.
The singleton is created once, so it receives one prototype object and keeps it forever; every caller shares it. To get a fresh object each time I inject ObjectProvider<T> and call getObject() where I need it.
Rarely. Request scope can hold per-request data such as a trace id that many beans need, without passing it through every method. For normal business data I pass it explicitly, because it keeps services simple, stateless and easy to unit test.
Lesson: Bean scopes and lifecycle callbacks@Component and its stereotypes go on my own classes and are found by scanning. @Bean methods in a @Configuration class register library types such as Clock, or objects that need values when built. I never register one class both ways, because that creates two beans.
A @Qualifier on the injection point wins, then a @Primary bean, then a bean whose name equals the parameter name; otherwise startup fails with "required a single bean, but 2 were found". If I need all of them I inject a List or Map.
I treat it as a design problem. I switch the classes to constructor injection so the loop is visible, find the small piece one class needs from the other, and move it into a third bean. I do not enable allow-circular-references, because that only hides the problem.
spring-boot-starter-parent imports the spring-boot-dependencies BOM, which holds a version property for each library Boot supports. Maven uses that version whenever my dependency has none. Libraries Boot does not manage, like Guava, still need an explicit version.
I run mvnw dependency:tree -Dincludes=tools.jackson.core. It shows the version and the path, for example webmvc to starter-jackson to jackson-databind 3.1.5. If two paths want different versions, the tree shows the one Maven picked.
My code only uses the java.sql and javax.sql interfaces, so it never needs the driver classes to compile. The driver is needed only when the app runs and opens a connection. runtime scope makes that clear and stops anyone importing driver classes by mistake.
Boot jars list auto-configuration classes in META-INF/spring/...AutoConfiguration.imports. At startup Boot evaluates each one's conditions, such as @ConditionalOnClass for a library on the class path and @ConditionalOnMissingBean for "the user has not defined this". Only configurations whose conditions pass add their beans.
I start the app with --debug and read the conditions report. Positive matches show what was applied and why; negative matches show the exact condition that failed, for example a missing class or an existing bean. Then I fix the dependency, the property or my own bean.
It is meant for auto-configuration classes, which Boot processes after all user configuration, so the check sees every user bean. In ordinary @Configuration classes the order is not guaranteed, so the condition can give the wrong answer. In my own code I just define the bean I want.
Because scanning starts at the main class's package. In the root package, like com.shoplite, every feature package is below it and gets scanned with no extra settings, including new features added later.
The main class sits in the root package and each feature, like product or order, is a sub-package with its own controller, service and repository. Every feature is under the root, so the default scan finds them all, and a new feature needs no configuration.
Nothing fails at startup, because no other bean depends on a controller. Its URLs simply return 404, which makes this mistake easy to miss. Listing the beans at startup or calling the endpoint in a test catches it.
Lesson: Component scanningWith server.port in application.properties, or --server.port=9090 on the command line, which wins over the file. server.port=0 picks a random free port, which is useful in tests.
Each request runs on one thread from Tomcat's pool, up to 200 by default. A thread stays busy while my code waits for the database, so many slow requests can use up the pool and new ones queue. That is why beans must be stateless: many threads use the same singleton at once.
Lesson: The embedded Tomcat serverWhen the app gets a stop signal, Tomcat stops accepting new requests and waits for running ones to finish, up to spring.lifecycle.timeout-per-shutdown-phase. It is the default in Spring Boot 4. Without it, a deployment could cut an order or a payment in the middle and the user gets an empty reply.
It is Spring Boot's built-in explainer for known startup errors, such as a port in use, a missing bean or a bad property value. It replaces the raw stack trace with a Description of what went wrong and an Action to fix it. Libraries can register their own.
Lesson: Reading the startup logSpring wraps the original exception at every layer, for example "bean A failed because bean B failed because the pool could not be built". Java prints the outer exception first and each wrapped one as a Caused by:, so the original problem is the last one.
DevTools loads libraries with a base class loader and my classes with a restart class loader. When class files change, it throws away the restart class loader and the application context and builds them again, while the JVM and the library classes stay loaded.
Lesson: Spring Boot DevTools<optional>true</optional> means projects that depend on mine do not inherit DevTools, and the Boot Maven plugin leaves it out of the repackaged jar. With runtime scope my code also cannot import its classes by accident.
When the class path itself changes, like a new dependency in pom.xml, or the JVM settings change. Libraries are in the base class loader, which a restart does not rebuild, so I stop the app, reload Maven and start again.
I go to the end of the log. If there is an APPLICATION FAILED TO START block, I follow its Description and Action. Otherwise I read the last Caused by: for the root cause and the first line of our code under it. If it is about beans Boot should or should not create, I run with --debug and read the conditions report.
I define my own bean of the same type, building it from Boot's builder or customizer when one exists so I keep Boot's settings. Then I run with --debug: the report shows Boot's bean under negative matches with "found beans of type ..." and my bean name.
Spring MVC passes the return value to an HTTP message converter. With the web starter that is Jackson, which writes each record component or getter as a JSON field and sets Content-Type: application/json. I never build JSON strings by hand.
I check the exact path in the error's path field against the mapping: spelling, case and a trailing slash. Then I check the HTTP method, and whether the controller is in a package under the main class so component scanning finds it. A @Controller without @ResponseBody can also give a 404, because Spring looks for a view.
Each lesson has quizzes, practice and more interview questions on the same topic.
Open the first lesson