← Back to list

Verizon Java Tech Lead Interview Experience

Interview of a candidate with 8.5+ years of Experience

Shivam Srivastava in Coding Odyssey · 2026-07-05 16:26 · 77 claps · 20.3 min read paywalled
#java #technology #software-development #software-engineering #programming
Open on Medium ↗
Wiki topics: 💻 · Programming

Verizon Java Tech Lead Interview Experience

Interview of a candidate with 8.5+ years of Experience

If you are not a paid member of Medium, please use my friend link to read the entire article: Friend Link

One of my friends, recently, appeared for the Verizon interview for Java Tech Lead role.

For context, he has over 8.5 years of experience in Java, SQL, Spring Boot, Microservices, and related technologies.

I’ll break this down in 2 parts:

  1. Interview Process
  2. Interview Questions

This is how it went:

1. Interview Process:

The process was smooth and as follows:

  • He applied for the job on company portal via a referral.
  • He got an call from Verizon HR team regarding the opening.
  • He shared the requested details with the HR team.
  • A coding test was scheduled for him.
  • After clearing the coding round, the first technical round was setup.

On the day of the interview:

  • The interviewer joined the call on time.
  • They exchanged pleasantries and got straight into technical questions.
  • The interviewer were professional and courteous throughout.

2. Interview Questions:

Below are some of the technical questions he was asked. I’ve merged similar questions and follow-up queries for clarity.

I’ll include textbook explanations for all the answers to help anyone preparing for interviews.

Q1. What is Spring Boot Actuator?

Spring Boot Actuator is a production-ready feature that helps us monitor and manage our application.

It exposes several built-in endpoints that provide information about the application’s health, metrics, environment, beans, mappings, etc., without writing custom APIs.

How to enable it:

Add the dependency:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

Expose the required endpoints:

management.endpoints.web.exposure.include=health,info,metrics,prometheus
management.endpoint.health.show-details=always

Commonly Used Endpoints:

Example:

GET /actuator/health

Response:

{
  "status": "UP"
}

Custom Health Check

We can also create our own health indicator.

@Component
public class DatabaseHealthIndicator implements HealthIndicator {

@Override
    public Health health() {
        if (databaseAvailable()) {
            return Health.up().build();
        }
        return Health.down()
                .withDetail("Database", "Connection Failed")
                .build();
    }
}

Now the custom health status will also be available in:

GET /actuator/health

Q2. How can we change the port of Spring Boot Actuator endpoints?

By default, Actuator endpoints run on the same port as the application.

For example, if the application runs on 8080, then:

Application : http://localhost:8080
Actuator    : http://localhost:8080/actuator/health

If we want to expose the Actuator endpoints on a different port, we can configure it using the following property:

server.port=8080
management.server.port=8081

Now,

Application : http://localhost:8080
Actuator    : http://localhost:8081/actuator/health

We can also change the base path

By default, all endpoints are available under /actuator.

If we want to change it:

management.endpoints.web.base-path=/manage

Now the health endpoint becomes:

http://localhost:8081/manage/health

Why do we use a separate port?

  • Improves security by isolating management endpoints from application traffic.
  • Makes it easier for monitoring tools like Prometheus to scrape metrics.
  • Allows firewall rules or reverse proxies to restrict access to only the monitoring port.

Q3. What is AOP? What are its use cases?

AOP (Aspect-Oriented Programming) is a programming paradigm that helps us separate cross-cutting concerns from the business logic.

A cross-cutting concern is a functionality that is common across multiple methods or classes, such as:

  • Logging
  • Security
  • Transaction Management
  • Exception Handling
  • Performance Monitoring
  • Auditing

Without AOP, we would end up writing the same code in multiple places, leading to code duplication and poor maintainability.

Example

Suppose every service method needs logging.

Without AOP:

public void createUser() {
    log.info("Method Started");
    // Business Logic
    log.info("Method Completed");
}

Every method would contain the same logging code.

With AOP:

We write the logging logic once, and it gets executed automatically whenever the target method is called.

@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
    public void logBefore() {
        System.out.println("Method execution started...");
    }
}

Now every method inside the service package will be logged automatically.

Common AOP Annotations:

Use Cases:

  • Logging — Log request and response details.
  • Security — Validate authentication and authorization before executing a method.
  • Transaction Management@Transactional is implemented using AOP.
  • Performance Monitoring — Measure method execution time.
  • Auditing — Capture who created or updated a record.
  • Exception Handling — Log exceptions or perform common error handling.

How does AOP work internally:

Spring AOP creates a proxy around the target object.

When a method is called:

Client
   │
   ▼
Proxy
   │
   ├── Before Advice
   ├── Target Method
   └── After Advice

Instead of calling the actual object directly, the client interacts with the proxy, which executes the configured advice before or after invoking the target method.

In simple words, AOP helps us keep our business logic clean by moving common functionalities like logging, security, transactions, and monitoring into separate reusable aspects.

Q4. How do you handle exceptions in your project?

In our project, we use Global Exception Handling using @RestControllerAdvice. Instead of handling exceptions in every controller, we centralize the exception handling in one place.

The flow looks like this:

Controller
     │
     ▼
Service
     │
     ▼
Repository
     │
     ▼
Exception Thrown
     │
     ▼
@RestControllerAdvice
     │
     ▼
Standard Error Response

Step 1: Create Custom Exceptions

public class ResourceNotFoundException extends RuntimeException {

public ResourceNotFoundException(String message) {
        super(message);
    }
}

Step 2: Global Exception Handler

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleResourceNotFound(
            ResourceNotFoundException ex) {
        ErrorResponse response = new ErrorResponse(
                HttpStatus.NOT_FOUND.value(),
                ex.getMessage());
        return ResponseEntity.status(HttpStatus.NOT_FOUND)
                .body(response);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleException(Exception ex) {
        ErrorResponse response = new ErrorResponse(
                HttpStatus.INTERNAL_SERVER_ERROR.value(),
                "Something went wrong");
        return ResponseEntity.internalServerError()
                .body(response);
    }
}

Step 3: Standard Error Response

public class ErrorResponse {

    private int status;
    private String message;
    // Constructors, Getters and Setters
}

Example Response:

{
    "status": 404,
    "message": "User not found"
}

How do we use it:

Suppose a user is not found:

User user = repository.findById(id)
        .orElseThrow(() ->
            new ResourceNotFoundException("User not found"));

The exception is automatically handled by the @RestControllerAdvice, and the client receives a proper HTTP response.

Use of Global Exception Handling:

  • Avoids writing try-catch blocks in every controller.
  • Keeps the code clean and maintainable.
  • Returns consistent error responses across all APIs.
  • Makes it easy to add handling for new exceptions in one place.

Q5. Suppose you’ve a food delivery application which is monolithic in nature. Now, you’ve to divide this application into microservices, how will you do it?

The first step is to identify the business capabilities or bounded contexts.

So, if monolithic application goes like:

+--------------------------------------+
|      Food Delivery Application       |
|--------------------------------------|
| User                                |
| Restaurant                          |
| Menu                                |
| Order                               |
| Payment                             |
| Delivery                            |
+--------------------------------------+
              │
          Single Database

Each microservice should own a single business responsibility along with its own database.

For a food delivery application, I would split it into services like:

                    API Gateway
                         │
 ┌──────────┬──────────┬──────────┬──────────┬──────────┐
 │          │          │          │          │          │
User    Restaurant    Menu      Order    Payment   Delivery
Service    Service   Service    Service   Service    Service
 │          │          │          │          │          │
User DB  Restaurant DB Menu DB Order DB Payment DB Delivery DB

Responsibilities of each service:

  • User Service — User registration, login, profile management.
  • Restaurant Service — Restaurant details, timings, ratings.
  • Menu Service — Food items, pricing, availability.
  • Order Service — Create and manage orders.
  • Payment Service — Payment processing and refunds.
  • Delivery Service — Assign delivery partner and track delivery.

Database per Service:

Each microservice should have its own database.

User Service  ------> User DB
Order Service ------> Order DB
Menu Service  ------> Menu DB
Payment Service ---> Payment DB
Delivery Service ---> Delivery DB
Resturant Service ---> Resturant DB

This ensures loose coupling. Services should never directly access another service’s database.

Communication:

  1. Synchronous communication (REST/OpenFeign) for operations requiring an immediate response.

Example: Order Service → Menu Service to validate item availability.

2. Asynchronous communication (Kafka/RabbitMQ) for events.

Example:

  • Order Created → Payment Service
  • Payment Successful → Delivery Service
  • Delivery Assigned → Notification Service
Client
   │
   ▼
API Gateway
   │
   ├── User Service
   ├── Restaurant Service
   ├── Menu Service
   ├── Order Service ─────REST────► Menu Service
   │
   └── Kafka
         │
         ├── Payment Service
         └── Delivery Service

Handling Transactions:

Since each service has its own database, we cannot use a single database transaction.

Instead, we use the Saga Pattern to maintain consistency across multiple services.

For example:

Order Created
      │
      ▼
Payment Successful
      │
      ▼
Assign Delivery Partner
      │
      ▼
Send Notification

If payment fails, the Order Service updates the order status to FAILED instead of rolling back everything.

Other Considerations:

  • Use an API Gateway for routing requests.
  • Use Service Discovery if services are dynamically deployed.
  • Secure APIs using JWT/OAuth2.
  • Implement Centralized Logging and Distributed Tracing.
  • Use Spring Boot Actuator, Prometheus, and Grafana for monitoring.

Q6. How will you make sure your food delivery application is available during a huge surge in traffic?

To handle a sudden increase in traffic, I would focus on scalability, high availability, and fault tolerance.

1. Deploy Multiple Instances:

Instead of running a single instance, I would deploy multiple instances of each microservice.

Load Balancer
                     │
      ┌──────────────┼──────────────┐
      │              │              │
   Order-1       Order-2       Order-3

If one instance goes down, the load balancer routes traffic to the remaining instances.

2. Auto Scaling:

Configure auto-scaling in Kubernetes or the cloud platform.

  • Increase the number of pods when CPU or memory usage crosses a threshold.
  • Scale down automatically when traffic decreases.

This ensures we use resources efficiently while handling peak loads.

3. Load Balancer:

Use a load balancer to distribute incoming requests evenly across all healthy instances.

This prevents any single instance from becoming overloaded.

4. Use Caching:

Frequently accessed data such as restaurant details, menus, or offers can be cached using Redis.

Client
   │
   ▼
Redis Cache
   │
(Cache Miss)
   ▼
Database

This significantly reduces database load and improves response time.

5. Asynchronous Processing:

Not every task needs to be processed synchronously.

For example:

Order Placed
      │
      ▼
    Kafka
      │
 ┌────┴──────┐
 │           │          
Inventory   Notification

Sending notifications or updating analytics can happen asynchronously using Kafka or RabbitMQ.

6. Database Scaling:

  • Use database indexes.
  • Add Read Replicas for read-heavy operations.
  • Partition or shard data if the dataset becomes very large.

7. Monitor the Application:

Use Spring Boot Actuator, Prometheus, and Grafana to monitor CPU usage, memory, request latency, error rates, and database performance.

Set up alerts so the operations team is notified before issues become critical.

8. Fault Tolerance:

Use Resilience4j features like:

  • Circuit Breaker
  • Retry
  • Timeout

This prevents cascading failures if one dependent service becomes unavailable.

Q7. Suppose you have two instances of a microservice, and both crash. How will you make the service fault tolerant?

If both instances crash, the service becomes unavailable. To make the system fault tolerant, I’d implement multiple strategies instead of relying on just having two instances.

1. Deploy Multiple Instances Across Different Nodes:

Don’t keep both instances on the same server or Kubernetes node.

Load Balancer
                │
      ┌─────────┴─────────┐
      │                   │
 Node-1               Node-2
   │                     │
Order-1              Order-2

If one node fails, the other can still serve requests.

2. Use Kubernetes Self-Healing:

If a pod crashes, Kubernetes automatically creates a new one.

Order-1 (Crash)
      │
      ▼
Kubernetes
      │
      ▼
New Order-1 Created

This minimizes downtime without manual intervention.

3. Configure Auto Scaling:

Maintain a minimum number of running instances.

For example:

  • Minimum Pods = 2
  • Maximum Pods = 10

During failures or traffic spikes, Kubernetes automatically creates additional pods.

4. Health Checks:

Configure:

  • Liveness Probe — Restarts unhealthy containers.
  • Readiness Probe — Stops sending traffic to unhealthy instances.

These typically use the Spring Boot Actuator health endpoint.

5. Load Balancer:

The load balancer should route requests only to healthy instances.

If an instance becomes unhealthy, it is automatically removed from the pool.

6. Fault Tolerance:

If the crashed service depends on another service, use Resilience4j features such as:

  • Circuit Breaker
  • Retry
  • Timeout
  • Fallback

This prevents cascading failures across the system.

7. Monitoring and Alerts:

Use:

  • Spring Boot Actuator
  • Prometheus
  • Grafana

Configure alerts so the operations team is notified immediately when instances go down or error rates increase.

8. Multi-Zone Deployment:

In production, deploy instances across multiple Availability Zones or data centers.

Availability Zone A
    │
 Order-1

Availability Zone B
    │
 Order-2

Even if an entire zone goes down, the service remains available.

Q8. Suppose you have N instances. How will you ensure that requests are distributed equally across all instances?

To distribute requests evenly across multiple instances, we place the service behind a Load Balancer.

The load balancer acts as a single entry point and forwards incoming requests to healthy instances based on a load balancing algorithm.

                    Client
                       │
                       ▼
                Load Balancer
                       │
      ┌──────────┬──────────┬──────────┐
      │          │          │
   Instance-1 Instance-2 Instance-3

This prevents one instance from getting overloaded while others remain idle.

Common Load Balancing Algorithms:

  • Round Robin — Requests are distributed one after another to each instance. This is the most commonly used algorithm.
  • Least Connections — Sends the request to the instance handling the fewest active connections.
  • Weighted Round Robin — Gives more traffic to instances with higher capacity.

How does the Load Balancer know which instances are available?

The load balancer performs health checks periodically.

If an instance becomes unhealthy, it is automatically removed from the pool and no traffic is routed to it.

              Load Balancer
                     │
         Health Check Every Few Seconds
                     │
        ┌──────────┬──────────┬──────────┐
        │          │          │
        UP         DOWN       UP
        │                     │
     Traffic Only          Traffic Only

In a Kubernetes Environment:

In Kubernetes, a Service acts as an internal load balancer.

When traffic reaches the Service, Kubernetes automatically distributes requests across all healthy pods.

          Kubernetes Service
                    │
      ┌─────────────┼─────────────┐
      │             │             │
    Pod-1         Pod-2         Pod-3

If new pods are created due to auto-scaling, Kubernetes automatically includes them in the load balancing.

Example:

For example, if there are 10,000 requests per minute and 5 instances of the Order Service, the load balancer distributes the requests across all five instances instead of sending everything to a single instance.

If one instance goes down, traffic is automatically routed to the remaining healthy instances.

Q9. How do you design and choose databases for your food delivery microservice applications?

When designing databases for microservices, the first principle I follow is Database per Service.

Each microservice should own its own database, and no other service should access it directly.

Microservices
    |   
User Service  ------> User DB
Order Service ------> Order DB
Menu Service  ------> Menu DB
Payment Service ---> Payment DB
Delivery Service ---> Delivery DB
Resturant Service ---> Resturant DB

This keeps the services loosely coupled and allows them to evolve independently.

How do I choose the database:

I choose the database based on the nature of the data and the business requirements.

1. Relational Database (MySQL, PostgreSQL, Oracle)

I use a relational database when:

  • Data has relationships.
  • ACID transactions are important.
  • Complex joins are required.

Examples:

  • Orders
  • Payments
  • Users

2. NoSQL Database (MongoDB, Cassandra):

I use NoSQL when:

  • The schema changes frequently.
  • Large volumes of data need to be stored.
  • Horizontal scalability is required.

Examples:

  • Product Catalog
  • Restaurant Menus
  • User Preferences

3. Redis:

Redis is used for caching frequently accessed data.

Examples:

  • Restaurant details
  • Menu information
  • User sessions

This reduces database load and improves response time.

Other Factors:

  • Scalability — Can the database handle future growth?
  • Performance — Read-heavy vs write-heavy workloads.
  • Consistency Requirements — Do we need strong consistency or is eventual consistency acceptable?
  • Backup & Disaster Recovery — Ensure regular backups and recovery strategies.
  • Replication & High Availability — Use replicas to improve availability.

How do services communicate:

Since each service has its own database, services never query another service’s database directly.

Instead, they communicate using:

  • REST APIs / Feign Client
  • Kafka or RabbitMQ for asynchronous communication

Q10. In what scenarios is Feign Client better than RestTemplate?

Feign Client is preferred when we’re making service-to-service communication in a microservices architecture. It is declarative, requires less boilerplate code, and integrates seamlessly with Spring Cloud.

On the other hand, RestTemplate requires us to manually construct the HTTP request, URL, headers, and handle the response.

Example:

Using RestTemplate:

RestTemplate restTemplate = new RestTemplate();
User user = restTemplate.getForObject(
    "http://user-service/users/1",
    User.class
);

Here, we need to manually specify the URL and make the HTTP call.

Using Feign Client:

@FeignClient(name = "user-service")
public interface UserClient {

    @GetMapping("/users/{id}")
    User getUser(@PathVariable Long id);
}

Calling it is as simple as:

User user = userClient.getUser(1L);

It looks just like calling a local method.

Why is Feign Client preferred:

  • Less boilerplate code.
  • Declarative and easy to maintain.
  • Integrates with Spring Cloud LoadBalancer for client-side load balancing.
  • Works well with Service Discovery (Eureka/Consul), so we don’t hardcode URLs.
  • Easily integrates with Resilience4j for Circuit Breaker and Retry.
  • Easier to test and maintain.

When would I use RestTemplate:

  • Calling external third-party APIs.
  • Small applications where Spring Cloud isn’t being used.
  • When more control over the HTTP request is required.

Note: RestTemplate is in maintenance mode, and Spring recommends using WebClient for new applications.

Q11. Suppose we have Search, Order, Payment, and Delivery microservices. The user places an order, but the payment fails. How would you manage this?

Since each microservice has its own database, we cannot use a single database transaction across all the services.

Instead, we use the Saga Pattern, where each service performs its own transaction. If one step fails, compensating actions are executed to undo the previous successful steps.

For example:

Search Product
      │
      ▼
Place Order
      │
      ▼
Order Status = PENDING
      │
      ▼
Payment Service
      │
      ├── Success ─────► Assign Delivery Partner
      │                      │
      │                      ▼
      │                Order Status = CONFIRMED
      │
      └── Failed ─────► Order Status = CANCELLED
                         Release Inventory

How does it work:

  1. The Order Service creates the order with a PENDING status.
  2. It then calls the Payment Service.
  3. If the payment is successful:
  • The order status is updated to CONFIRMED.
  • The Delivery Service is notified to assign a delivery partner.
  1. If the payment fails:
  • The order status is updated to CANCELLED (or PAYMENT_FAILED).
  • Any reserved inventory is released.
  • Since delivery hasn’t started yet, no delivery partner is assigned.

How do services communicate:

In most microservice architectures, this flow is implemented using an event broker like Kafka.

Order Created
      │
      ▼
Kafka
      │
      ▼
Payment Service
      │
      ├── Payment Success Event
      │         │
      │         ▼
      │   Delivery Service
      │
      └── Payment Failed Event
                │
                ▼
         Order Service
      Update Status = CANCELLED

This keeps the services loosely coupled.

Why not use @Transactional:

Because @Transactional works only within a single database.

In a microservices architecture, every service has its own database, so distributed transactions are handled using the Saga Pattern instead of a single database transaction.

If you want a deep dive on Saga Design Pattern, I have written an extensive article on it:

[embed]Saga Design Pattern in Microservices (With Code & Interview Questions) Interview-Friendly Guidemedium.com

Q12. How would you manage security in a microservices architecture?

In a microservices architecture, I secure the application at multiple levels.

1. Authentication and Authorization:

I use JWT tokens with Spring Security.

  • The user logs in and receives a JWT token.
  • Every subsequent request carries the token in the Authorization header.
  • The token is validated before the request reaches the microservice.
Client
   │
Login
   │
   ▼
Authentication Service
   │
Returns JWT Token
   │
   ▼
Client
   │
Authorization: Bearer <JWT>
   │
   ▼
API Gateway

2. API Gateway:

Instead of exposing all microservices directly, I expose only the API Gateway.

The gateway is responsible for:

  • Authenticating requests.
  • Validating JWT tokens.
  • Routing requests to the appropriate microservice.
  • Applying rate limiting if required.
Client
   │
   ▼
API Gateway
   │
 ┌─┴───────────────┐
 │                 │
User Service   Order Service

3. Secure Service-to-Service Communication:

For communication between microservices, I use:

  • JWT token propagation, or
  • mTLS (Mutual TLS) in highly secure environments.

This ensures that only trusted services can communicate with each other.

4. Role-Based Access Control (RBAC):

Different users should have different permissions.

Example:

  • Customer → Place Orders
  • Restaurant → Update Menu
  • Admin → Manage Users

These roles are validated before allowing access to protected APIs.

5. HTTPS:

All communication should happen over HTTPS to encrypt data in transit.

6. Secrets Management:

Sensitive information such as database passwords, API keys, and JWT secrets should never be hardcoded.

Instead, use:

  • Kubernetes Secrets
  • HashiCorp Vault
  • AWS Secrets Manager
  • Azure Key Vault

7. Monitoring and Logging:

Monitor authentication failures and suspicious activity using centralized logging and alerting tools.

Q13. How would you manage branching in Git in your project among the development team?

In our project, we follow a feature branching strategy.

The idea is that every developer works on their own feature branch instead of directly committing to the main branch.

main
  │
develop
  ├──────── feature/user-login
  ├──────── feature/order-service
  ├──────── feature/payment
  └──────── feature/delivery

Our workflow is:

  1. Create a feature branch from the develop branch.
  2. Implement the feature and commit changes regularly.
  3. Push the branch to the remote repository.
  4. Raise a Pull Request (PR).
  5. At least one or two team members review the code.
  6. Run automated checks like unit tests, code quality, and security scans.
  7. Once the PR is approved, merge it into the develop branch.
  8. After successful testing, the develop branch is merged into main for production deployment.

How do we maintain code quality:

Before merging a PR, we ensure:

  • Code review is completed.
  • Unit tests pass.
  • SonarQube quality gate passes.
  • No merge conflicts.
  • CI pipeline completes successfully.

How do we handle production bugs:

If there’s a critical production issue, we create a hotfix branch from the main branch.

main
  │
hotfix/payment-fix
  │
  ├── Merge back to main
  └── Merge back to develop

This allows us to deploy the fix quickly without waiting for the next release.

Q14. How do you do Code/PR Reviews?

Whenever I review a Pull Request, I don’t just check if the code is working. I review it from multiple perspectives to ensure it’s maintainable, scalable, and production-ready.

1. Understand the Requirement:

Before reviewing the code, I first understand what the developer is trying to implement. This helps me verify whether the solution actually meets the business requirement.

2. Code Quality:

I check whether:

  • The code is clean and readable.
  • Proper naming conventions are followed.
  • There is unnecessary code duplication.
  • SOLID principles are followed wherever applicable.

3. Business Logic:

I verify that:

  • The implementation satisfies the requirement.
  • All possible scenarios are handled.
  • Edge cases are considered.

4. Exception Handling:

I check that:

  • Exceptions are handled properly.
  • Meaningful error messages are returned.
  • No sensitive information is exposed in responses.

5. Performance:

I look for potential performance issues such as:

  • Unnecessary database calls.
  • Multiple API calls inside loops.
  • Inefficient SQL queries.
  • Opportunities for caching or pagination.

6. Security:

I verify that:

  • Inputs are validated.
  • Sensitive information isn’t hardcoded.
  • APIs are properly secured.
  • Authorization checks are in place where required.

7. Unit Tests:

I ensure that:

  • New functionality has unit tests.
  • Existing tests continue to pass.
  • Important business scenarios are covered.

8. Code Standards:

Finally, I check:

  • Proper logging.
  • No commented or dead code.
  • Meaningful commit messages.
  • SonarQube or other quality checks are passing.

Q15. If there is a property which is common amongst all your microservices. How would you manage this? Would you duplicate this property in all your microservices?

No. I would not duplicate the same property across all microservices because it becomes difficult to maintain and increases the chances of configuration inconsistencies.

Instead, I would use Spring Cloud Config Server to centralize the configuration.

               Git Repository
                    │
                    ▼
        Spring Cloud Config Server
                    │
     ┌──────────────┼──────────────┐
     │              │              │
 User Service   Order Service   Payment Service

All the common properties are stored in a Git repository, and every microservice fetches them from the Config Server during startup.

Example of Common Properties:

common.timeout=5000
logging.level.root=INFO
kafka.bootstrap-servers=localhost:9092

Microservice Configuration:

Each microservice simply points to the Config Server.

spring.application.name=order-service
spring.config.import=configserver:http://localhost:8888

Spring Boot automatically fetches the configuration from the Config Server.

What if a property changes:

If a common property is updated in the Git repository, we don’t need to update every microservice individually.

Depending on the setup, we can:

  • Refresh the configuration using the Actuator refresh endpoint.
POST /actuator/refresh
  • Or use Spring Cloud Bus, which broadcasts the configuration change to all the microservices so they pick up the latest configuration without restarting.

What kind of properties do we keep centrally:

Typically, we centralize properties such as:

  • Common logging configuration
  • Kafka configuration
  • API timeouts
  • Feature flags
  • Common URLs
  • Security-related configurations

Service-specific properties remain within the respective microservice.

Q16. How does Docker work internally?

Docker uses containerization to package an application along with all its dependencies into a Docker Image. When we run the image, Docker creates a lightweight Container.

Unlike Virtual Machines, Docker containers share the host operating system’s kernel, which makes them much faster and consume fewer resources.

Docker Architecture:

                Docker Client
                     │
          docker build / docker run
                     │
                     ▼
              Docker Daemon
                     │
      ┌──────────────┼──────────────┐
      │              │              │
 Docker Images   Docker Containers  Docker Networks

How does it work:

1. Docker Client:

This is where we execute commands like:

docker build
docker run
docker stop

2. Docker Daemon:

The Docker Daemon (dockerd) receives these commands and performs the actual work.

It is responsible for:

  • Building images
  • Creating containers
  • Managing networks
  • Managing volumes

3. Docker Image

A Docker Image is a read-only template that contains:

  • Application code
  • JDK
  • Required libraries
  • Dependencies
  • Configuration

Images are created using a Dockerfile.

Example:

FROM eclipse-temurin:21-jre
COPY target/app.jar app.jar
ENTRYPOINT ["java","-jar","app.jar"]

4. Docker Container

When we execute:

docker run my-app

Docker creates a Container from the image.

A container is simply a running instance of an image.

Internally, what happens when we run a container?

docker run
      │
      ▼
Docker Client
      │
      ▼
Docker Daemon
      │
      ▼
Checks Image
      │
Image Exists?
      │
 ├── Yes
 │      ▼
 │ Create Container
 │
 └── No
        ▼
Pull Image from Docker Hub
        │
        ▼
Create Container

Why are Docker containers lightweight:

Unlike Virtual Machines, Docker containers do not have their own operating system.

They share the Host OS Kernel.

           Applications
                │
     ┌──────────┴──────────┐
     │        Docker       │
     │     Container 1     │
     │     Container 2     │
     │     Container 3     │
     └──────────┬──────────┘
                │
        Host Operating System
                │
            Linux Kernel

That’s why containers:

  • Start in seconds.
  • Consume less memory.
  • Have better resource utilization.

Q17. What is your versioning format for your deployment and how is its management process?

In our project, we follow Semantic Versioning (SemVer) for application releases.

The format is:

MAJOR.MINOR.PATCH

Example:
1.0.0
1.2.0
1.2.5
2.0.0

What does each version represent:

  • MAJOR — Incremented when there are breaking changes or major releases.
  • MINOR — Incremented when new features are added while maintaining backward compatibility.
  • PATCH — Incremented for bug fixes, security fixes, or small improvements.

For example:

1.0.0 → Initial Release
1.1.0 → Added Coupon Feature
1.1.1 → Fixed Payment Bug
2.0.0 → Breaking API Changes

How do we manage deployments:

Our release flow looks like this:

Developer
     │
Feature Branch
     │
Pull Request
     │
Code Review
     │
Develop Branch
     │
CI Pipeline
     │
QA / UAT
     │
Release Tag (v1.2.0)
     │
Production

Once all testing is completed, we create a Git tag for the release, such as:

git tag v1.2.0
git push origin v1.2.0

The CI/CD pipeline then builds the application and deploys that tagged version to production.

How do we handle hotfixes:

For production issues:

  • Create a hotfix branch from the production branch.
  • Fix the issue.
  • Increment the PATCH version.

Example:

v1.2.0
↓
Hotfix
↓
v1.2.1

This allows us to release the fix quickly without waiting for the next feature release.

Q18. What is Terraform?

Terraform is an Infrastructure as Code (IaC) tool developed by HashiCorp that allows us to create, manage, and provision infrastructure using configuration files instead of manually creating resources.

Instead of manually creating VMs, Kubernetes clusters, databases, or load balancers through a cloud console, we define them in code, and Terraform creates them automatically.

How does Terraform work:

Terraform Code (.tf)
          │
          ▼
    terraform plan
          │
          ▼
Shows what will be created
          │
          ▼
    terraform apply
          │
          ▼
Creates Infrastructure
          │
          ▼
AWS / Azure / GCP

Basic Terraform Workflow:

  1. Write the infrastructure in .tf files.
  2. Run **terraform init** to initialize Terraform.
  3. Run **terraform plan** to preview the changes.
  4. Run **terraform apply** to create or update the infrastructure.
  5. Run **terraform destroy** if the infrastructure needs to be removed.

Example:

provider "aws" {
  region = "ap-south-1"
}
resource "aws_instance" "web" {
  ami           = "ami-xxxxxxxx"
  instance_type = "t2.micro"
}

Running:

terraform apply

creates the EC2 instance automatically.

Why do we use Terraform:

  • Infrastructure is managed as code.
  • Easy to version control using Git.
  • Ensures consistent environments across Dev, QA, and Production.
  • Supports multiple cloud providers like AWS, Azure, and GCP.
  • Reduces manual errors and speeds up infrastructure provisioning.

Q19. What are S.O.L.I.D Principles?

I have already written a extensive deep dive article on the same. Requests you to please go through it:

[embed]S.O.L.I.D Principles in Java: Deep Dive with Interview Questions A Complete Guidemedium.com

Q20. How is Kibana configured in your project?

In our project, we use the ELK Stack for centralized logging.

  • Elasticsearch stores and indexes the logs.
  • Logstash (or Filebeat) collects and forwards the logs.
  • Kibana is used to search, visualize, and analyze those logs.

The flow looks like this:

Spring Boot Microservices
          │
      Logback Logs
          │
          ▼
 Filebeat / Logstash
          │
          ▼
   Elasticsearch
          │
          ▼
       Kibana

How does it work:

  1. Every microservice generates logs using SLF4J and Logback.
  2. Filebeat (or Logstash) collects the log files.
  3. The logs are sent to Elasticsearch.
  4. Elasticsearch indexes the logs.
  5. Kibana reads the indexed logs and provides dashboards and search capabilities.

What do we use Kibana for:

  • Search logs using filters.
  • Debug production issues.
  • Track application errors and exceptions.
  • View logs for a specific service or request.
  • Create dashboards for monitoring.

For example, if a customer reports that an order failed, we can search using:

  • Request ID / Correlation ID
  • Order ID
  • User ID
  • Log level (ERROR)
  • Time range

This helps us trace the request across multiple microservices.

How do we trace a request across microservices?

We use a Correlation ID (or Trace ID).

Client Request
      │
Correlation ID: abc123
      │
      ▼
API Gateway
      │
      ▼
Order Service
      │
      ▼
Payment Service
      │
      ▼
Delivery Service

Since every service logs the same Correlation ID, we can search for **abc123** in Kibana and view the complete request flow end-to-end.

Example Log:

2026-07-05 10:15:20 INFO
CorrelationId=abc123
OrderId=1001
Payment completed successfully

Searching for **abc123** in Kibana shows all logs generated for that request across different microservices.

Final Thoughts:

The candidate cleared this round and moved to the next technical round.

This seemed like a good interview to know about various skill sets of the candidate especially for the tech lead role.

If you or someone you know recently had an interview or if you’d like me to explain any topic, feel free to reach out to me via email. I’ll write an detailed article on the same.

If you need help with interview preparation, or need consultation in general. Please reach out to me over the email.

Email: shivamsrivastava.iec@gmail.com

If you found my work helpful and want to show your support:

Buy Shivam a Coffee

For collaboration or clarifications please connect with me on:

Email: shivamsrivastava.iec@gmail.com

Quora: Shivam Srivastava

X.com (Twitter): Shivam on X

Buy Me a Coffee: Shivam Srivastava

If you liked this article, you’ll also enjoy my below list of articles:

[embed]List: Interview Experiences and Learnings | Curated by Shivam Srivastava | Medium Interview Experiences and Learnings · 67 stories on Mediummedium.com

[embed]List: Deep Dive Series | Curated by Shivam Srivastava | Medium Deep Dive Series · 31 stories on Mediummedium.com


메타데이터
post_id
069a00e8a1d3
slug
verizon-java-tech-lead-interview-experience-069a00e8a1d3
url
https://medium.com/coding-odyssey/verizon-java-tech-lead-interview-experience-069a00e8a1d3
canonical_url
https://medium.com/coding-odyssey/verizon-java-tech-lead-interview-experience-069a00e8a1d3
author_url
https://medium.com/@shivamsrivastava.iec
status
ok
fetched_at
2026-07-08 23:08:13