From Servlets to Spring Boot: Understanding the Evolution of Java Backend Development
Servlet
A Servlet is a Java class that handles client requests, processes them, and returns the response.
We can create different servlets for different purposes:
- One servlet for accepting REST API requests
- Another servlet for accepting SOAP API requests
- Another servlet for handling file uploads
For example, in Spring MVC, the DispatcherServlet is configured by default to handle all APIs using "/*" mapping.
Servlet classes are known as Servlet APIs.
A Servlet Container is responsible for managing servlets.
Custom Servlet
To create a custom servlet:
- You need to extend the
HTTPServletclass. - You need to override methods like:
doGet()
doPost()
doPut()
doDelete()These methods handle different types of HTTP requests.
Java Application Without Any Framework
Previously, if you wanted to build an application using Java without any framework, you had to create servlet classes that would handle the incoming requests.
After creating servlets, you needed to map those servlets inside the web.xml file.
In web.xml, we needed to define:
- What are all the servlets available
- Which APIs each servlet handles
- Which servlet should handle which request
The request flow was:
- The request first arrives at the Servlet Container (Tomcat).
- The servlet container checks
web.xml. - It identifies which servlet should handle the request.
- It invokes the particular method of that servlet that handles the request.
Problems with Servlets
1. web.xml File
In Servlet-based applications, everything was configured inside web.xml.
It contained:
- All servlets
- All servlet mappings
- Which servlet handles which API
- All filters
- All listeners
For a large application, this became a huge problem because the file could contain thousands of lines of configuration.
2. Servlet Class Problem
Servlet classes also created problems.
A servlet can have only one method for each HTTP request type:
- One
doGet() - One
doPost() - One
doPut() - One
doDelete()
But in real applications, we need multiple GET APIs, multiple POST APIs, and so on.
Because of this limitation, we had to use many if conditions inside methods.
For example, inside doGet(), we had to check the request path manually:
if (request.getPathInfo().equals("/users")) {
...
}
else if (request.getPathInfo().equals("/users/1")) {
...
}The servlet method became responsible for handling multiple APIs.
3. Object Creation Problem
In Servlets:
- We create objects manually using
new. - Our class decides what objects to create and when to create them.
This causes:
- Tight coupling
- Hard-to-test code
- Lifecycle management becomes our responsibility
4. Unit Testing is Much Harder
In Servlets, object creation depends on the Servlet Container.
Because of this dependency, mocking is not easy and unit testing becomes much harder.
5. WAR Deployment Problem
With Servlet-based applications:
- We needed to create a WAR file of our application.
- Then deploy it manually on Tomcat.
This process was time-consuming.
Spring Framework / Spring MVC
Because of the problems existing in Servlets, the Spring Framework was introduced.
Spring Framework does not support Java 25 yet.
Spring solved many problems that existed in traditional Servlet-based applications.
1. Removing the web.xml Problem
Spring introduced annotation-based configuration.
Instead of managing everything manually inside web.xml, we could configure things using annotations.
2. Inversion of Control (IoC)
IoC is a more flexible way to manage object dependencies and their lifecycle.
It works through Dependency Injection.
3. Unit Testing
Spring Dependency Injection makes unit testing much easier.
4. Easy API Creation
Spring made API creation easier.
We just need to use annotations like:
@GetMappingto create GET APIs.
We can create multiple APIs in the same way.
5. Integration
Spring makes integration with other technologies easier, such as:
- Hibernate
- Security
- Other frameworks
Problems with Spring Framework
Although Spring solved many problems that existed with Servlets, Spring itself had some problems.
1. Dependency Management Problem
While adding dependencies in Spring, we needed to add the dependency along with its version.
The added dependency should also be compatible with the already existing dependencies.
So, we needed to manually take care of dependency versions and compatibility.
2. WAR Deployment Problem
Similar to Servlet-based applications:
- We still needed to create a WAR file of our application.
- Then we had to deploy it on Tomcat manually.
This process was time-consuming.
Spring Boot
Because of these problems, Spring Boot was introduced.
Spring Boot follows the software principle:
Convention over Configuration
This means:
Prefer sensible defaults (convention) but allow developers to override them (configuration).
Convention vs Configuration
Convention
Convention means the default behavior that Spring Boot applies automatically.
Configuration
Configuration means custom settings that override the default behavior.
You can change the default behavior using:
application.propertiesapplication.yml- Java
@Beandefinitions - Custom configuration classes
Spring Boot assumes that you want the most common setup, so it:
- Auto-configures beans
- Provides default settings
- Detects dependencies and configures them
- Chooses the default server (Tomcat)
- Chooses the default port (8080)
- Uses Jackson as the default JSON library
- Uses Logback as the default logging framework
Embedded Server
Spring Boot automatically configures the server port by default.
This follows the convention approach.
The default port is:
8080If we want to override it, we can configure:
server.port=8081This is configuration.
Auto Configuration
Spring Boot automatically configures beans based on classpath dependencies.
What is Classpath Dependency?
Classpath dependencies mean the JAR files or libraries that exist in your project.
Examples:
spring-boot-starter-webspring-boot-starter-data-jpaspring-boot-starter-security
When certain libraries are present in your project, Spring Boot automatically creates and configures the required beans.
You do not need to manually write configuration classes.
Auto Configuration Example
Convention
Spring Boot checks what is present in the classpath and configures things automatically.
For example:
If your dependencies include:
spring-boot-starter-data-jpaSpring Boot automatically configures:
EntityManagerFactoryHibernateJpaVendorAdapterDataSourceTransactionManager- Repository scanning
You do not need to create these beans manually.
Configuration
You can override or customize auto-configured values using application.properties.
Example:
spring.datasource.url=jdbc:postgresql://localhost:5432/test
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=updateDependency Management
Spring Boot provides starter dependencies.
Examples:
spring-boot-starter-webspring-boot-starter-test
These starters:
- Automatically select compatible versions
- Prevent dependency conflicts
- Reduce manual work
You do not need to worry about versions anymore.
Spring vs Spring Boot
Spring Boot is a layer built on top of Spring that provides:
- Auto Configuration
- Embedded servers
- Opinionated defaults
- Easy project setup through Spring Initializr
- Minimal configuration
Spring Framework
In Spring, we had to perform manual configuration.
This included:
- Large XML files
- Manual bean creation
- Manual server setup
- Complex configuration for MVC, JPA, Hibernate, and transactions
Server Setup in Spring
The server did not come embedded.
We had to:
- Install an external server like Tomcat, Jetty, or WildFly
- Create a WAR file
- Deploy the application manually
Spring Boot
In Spring Boot:
- Almost everything is auto-configured.
- We just need to annotate and run.
- No XML configuration is required.
Embedded Server in Spring Boot
Spring Boot comes with embedded servers:
- Embedded Tomcat
- Embedded Jetty
- Embedded Netty
We can run the application using:
java -jarand deploy it as an executable JAR.
Smart Defaults in Spring Boot
Spring Boot applies smart defaults.
For example:
When we add:
spring-boot-starter-webSpring Boot automatically configures:
- DispatcherServlet
- WebMVC
- Jackson
- Tomcat
- Validation
- Logging
- Error handling
We do not need to configure these manually.
Production Ready Defaults
Spring Boot provides production-ready defaults like:
- Logging
- Error pages
- Metrics
Spring Boot provides pre-made dependencies, making it easier to write code.
Get Bashira Farhin’s stories in your inbox
Join Medium for free to get updates from this writer.
Examples:
spring-boot-starter-webspring-boot-starter-data-jpaspring-boot-starter-security
Cons of Spring Boot
Although Spring Boot makes development easier, it also has some disadvantages.
1. Less Control
Because of auto-configuration, we get less control over configurations.
2. Heavy Startup for Small Applications
For small applications, Spring Boot can have heavier startup time because it packages many libraries by default.
3. Larger JAR Size
Spring Boot applications have larger JAR sizes because the embedded server is included.
4. Difficult Auto-Configuration Debugging
Debugging auto-configuration issues can be difficult because too many auto-configured beans may confuse developers.
Application.properties vs Application.yml
Spring Boot allows configuration using two formats:
application.propertiesapplication.yml
YAML or .yml is not related to Docker.
.properties File
Properties files use:
- Flat key-value pairs
- Dot (
.) notation to represent hierarchy
Example:
spring.datasource.username=root
spring.datasource.password=1234When we want nested configuration, there is a lot of repetition.
Properties files are:
- Good for small configurations
- Simple
But they become long and ugly when configurations are deeply nested.
Lists in Properties Files
For lists, properties files need special syntax.
Example:
spring.profiles.active[0]=dev
spring.profiles.active[1]=testSpring Profiles in Properties Files
Spring uses configuration files based on the active profile.
The active profile is set using:
spring.profiles.active=devSpring Naming Rule
Default configuration file:
application.propertiesProfile-specific configuration files:
application-{profile}.propertiesThese files are loaded only when that profile is active.
Examples:
application-dev.properties
application-qa.properties
application-prod.propertiesMultiple Active Profiles
Example:
spring.profiles.active=prod,devThis activates all mentioned profiles.
However, the configuration file loaded with higher priority will be the last profile mentioned.
In this case:
application-dev.propertieswill be loaded.
If spring.profiles.active is not set:
application.propertiesis used.
If both are set, priority is given to the user-provided configuration.
The value in application.properties can be changed from:
- Terminal
- Operating System
- pom.xml
That is why it is dynamic.
Application.yml File
YAML files use the YAML format.
They provide:
- Hierarchical structure
- Clean and structured configuration
- Better readability for complex configurations
Example:
spring:
datasource:
username: root
password: 1234Lists in YAML
YAML supports lists naturally.
Example:
spring:
profiles:
active:
- dev
- testIn YAML, you can also use the dot operator to represent hierarchy.
However, there is no point in using it because YAML was introduced to remove the downside of .properties files.
Build Tool
Writing code is only part of the work.
To run an application, we often need to:
- Compile source code
- Download and manage dependencies (libraries)
- Run tests
- Create a final package (JAR, WAR, ZIP, Docker image, etc.)
- Clean old builds
- Deploy the application
A build tool automates all of these tasks.
Both Maven and Gradle are build tools.
Maven
Maven is a build automation tool primarily built for Java projects.
Later, it was configured to build projects written in other languages as well.
Maven builds a project using:
- Project Object Model (POM)
- A set of plugins
Maven is hosted by the Apache Software Foundation.
Initially, it was known as the Jakarta project.
Maven Configuration
In Maven, configuration is written in XML using:
pom.xmlBecause XML requires many tags, Maven configuration contains a lot of boilerplate.
For adding a dependency in Maven, we need to use multiple tags.
Example:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>Maven Performance
Maven is slower because it performs a full build from scratch every time we create a build.
Maven performs compilation every time, even if the input files have not changed.
Maven does not provide:
- Advanced caching
- Parallel build by default
Maven as a Project Management Tool
Maven is also a project management tool.
It helps developers with:
- Build generation
- Dependency resolution
- Documentation
Maven uses the POM (Project Object Model) to achieve this.
When we run a Maven command:
- Maven looks for
pom.xmlin the current directory. - It reads the required configuration from it.
Before Maven, there was a tool called Ant.
In Ant, we had to define:
- What to do
- How to do it
Maven Project Identification
groupId and artifactId are used to identify a particular project in Maven Central.
Every pom.xml is a child of another pom.xml.
By default, every pom.xml is a child of the super pom.xml.
Maven Lifecycle
Maven has different phases in its lifecycle.
The lifecycle phases are:
- Validate
- Compile
- Test
- Package
- Verify
- Install
- Deploy
Validate
Checks the project structure.
Compile
Compiles the source code.
Test
Runs tests on the code, such as unit tests.
Package
Packages the compiled code into:
- JAR
- WAR
Verify
Checks the integrity of the package.
Install
Installs the package into the local repository.
Deploy
Deploys the package into a remote repository.
Maven Commands
When we run:
mvn installIt runs every lifecycle step except the last one.
If you want to run a particular phase, then all previous phases will also run.
Each phase contains a list of goals/tasks.
If you want to run a specific goal of a particular phase:
- All goals of previous phases will run.
- Current phase goals before the defined goal will run.
Adding Custom Goals in Maven
If you want to add a new goal or task to any lifecycle, you can use:
<build>
</build>inside the pom.xml file.
Gradle
Gradle is a build automation tool for multiple programming languages.
It was primarily built to remove the drawbacks of Maven.
Gradle introduces a Java and Groovy-based DSL (Domain Specific Language) for declaring project configuration.
What is DSL?
Gradle provides a customized language designed specifically for build configuration.
You can think of DSL as:
- A special language inside Groovy or Kotlin
- It provides special keywords offered by Gradle
- It is designed to make build configuration easy to describe
Examples of Gradle DSL Keywords
Some common DSL keywords are:
dependencies { }
tasks { }
repositories { }
plugins { }These are not standard Groovy or Kotlin keywords. They are Gradle DSL constructs.
Advantages of DSL
DSL avoids unnecessary boilerplate such as XML tags.
For example, to add a dependency in Gradle:
implementation 'org.springframework.boot:spring-boot-starter-web'Since Gradle DSL is based on Groovy/Kotlin, you can write:
- Functions
- Conditions
- Loops
- Variables
DSL provides:
- Better readability
- Greater flexibility
- Less boilerplate
- The power of a real programming language
- Easier custom builds
Gradle Performance
Gradle provides better performance because it performs incremental builds.
Gradle remembers the previous build and checks:
- Have the input files changed?
- Have the output files changed?
If nothing has changed, Gradle skips that task.
When a new build is triggered, Gradle only builds the newer or updated tasks instead of rebuilding everything.
It also avoids recompiling when there are no changes in the input files.
Gradle Build Workflow
Gradle is task-based, not phase-based.
It provides several optimization features:
- Daemon
- Caching
- Parallel builds
Incremental Builds
Incremental builds mean Gradle rebuilds only what has changed.
Parallel Builds
Parallel builds make multi-module projects faster.
If your project has multiple independent modules, Gradle can build them simultaneously using multiple CPU cores.
Example modules:
user-serviceauth-servicepayment-service
If these modules do not depend on each other, Gradle builds them in parallel.
Build Cache
Gradle reuses outputs from previous builds using the build cache.
Daemon Process
Gradle normally runs a background process called a daemon, which stays running in memory.
This daemon keeps:
- JVM preloaded
- Classes already loaded
- Build scripts parsed
- Configuration cached
Without Daemon
For every build:
- Start the JVM
- Load Gradle
- Configure the environment
- Start the build
With Daemon
Gradle reuses the already-running JVM, allowing the build to start immediately.







