All Posts

LLD for Parking Lot System: Designing a Smart Garage

Designing a parking lot is the 'Hello World' of OOP Design. Break down LLD using OOP pillars, SOLID, and classic design patterns.

Abstract AlgorithmsAbstract Algorithms
ยทยท17 min read
Share
Share on X / Twitter
Share on LinkedIn
Copy link

TLDR

TLDR: A Parking Lot is the "Hello World" of Low-Level Design. It teaches Encapsulation (ParkingFloor hides its Min-Heap), Abstraction (PricingStrategy interface), Inheritance (BikeSpot/CompactSpot/LargeSpot extend ParkingSpot), and Polymorphism (type dispatch without casting). Factory Pattern for vehicles, Strategy Pattern for pricing, O(log n) Min-Heap for allocation.


๐Ÿ“– The Mall Garage Problem

You pull up to a mall garage. Three things happen:

  1. Entry gate: You press a button โ†’ get a ticket โ†’ gate opens.
  2. Parking: Find a spot (the system might display "Floor 2, Spot 14").
  3. Exit: Scan your ticket โ†’ system calculates fee based on time parked โ†’ gate opens.

The software behind this manages hundreds of concurrent vehicles across multiple floors, spot types, and pricing tiers.


๐Ÿ” Parking Lot Design Basics

Before writing any code, it helps to map out the real-world entities the system must represent.

Spot Types โ€” Not every bay fits every vehicle. A typical garage divides its spaces into three tiers:

Spot TypeFitsNotes
BikeSpotMotorcycles, scootersNarrow bay, often near entrance
CompactSpotSedans, hatchbacksStandard stall
LargeSpotSUVs, trucks, vansWider or double-width bay

Vehicle Types โ€” Each vehicle carries a licensePlate and a VehicleType enum (BIKE, CAR, TRUCK). The type determines which spot tier it can occupy โ€” a TRUCK cannot squeeze into a CompactSpot.

Ticket โ€” When a vehicle enters, the system issues a Ticket that records three things: the vehicle (plate + type), the assigned spot (floor number + spot ID), and the entry timestamp. The ticket is the single source of truth for pricing at exit.

Pricing โ€” Fees are computed at exit from the ticket's entry timestamp and the vehicle type. A TRUCK costs more per hour than a CAR, which costs more than a BIKE. The exact rates are pluggable โ€” more on that in the Strategy Pattern section.

The Core Loop in plain English:

  1. Vehicle arrives โ†’ system finds the nearest free compatible spot โ†’ issues a ticket.
  2. Vehicle leaves โ†’ system reads the ticket โ†’ calculates fee from duration ร— rate โ†’ releases the spot โ†’ driver pays.

Understanding these five entities (Spot, Vehicle, Ticket, Floor, Lot) makes the class diagram and pattern choices in the next sections click immediately.


๐Ÿ”ข The Domain Model

classDiagram
    class ParkingLot {
        -static ParkingLot instance
        +List~ParkingFloor~ floors
        -PricingStrategy pricingStrategy
        +Ticket park(Vehicle v)
        +double exit(Ticket t)
    }
    class ParkingFloor {
        +int floorNumber
        -Map~VehicleType,PriorityQueue~ heaps
        +ParkingSpot findNearest(VehicleType t)
        +void releaseSpot(ParkingSpot s)
    }
    class ParkingSpot {
        <>
        +int id
        +boolean isFree
        +VehicleType type
        +void assign(Vehicle v)
        +void free()
    }
    class BikeSpot {
        +VehicleType type = BIKE
    }
    class CompactSpot {
        +VehicleType type = CAR
    }
    class LargeSpot {
        +VehicleType type = TRUCK
    }
    class Vehicle {
        <>
        +String licensePlate
        +VehicleType type
    }
    class Bike
    class Car
    class Truck
    class Ticket {
        +Vehicle vehicle
        +ParkingSpot spot
        +Instant entryTime
    }
    class PricingStrategy {
        <>
        +calculateFee(VehicleType type, long minutes) double
    }
    class HourlyPricing {
        +calculateFee(VehicleType type, long minutes) double
    }

    ParkingLot "1" *-- "many" ParkingFloor : owns
    ParkingFloor "1" *-- "many" ParkingSpot : contains
    BikeSpot --|> ParkingSpot : extends
    CompactSpot --|> ParkingSpot : extends
    LargeSpot --|> ParkingSpot : extends
    Bike --|> Vehicle : extends
    Car --|> Vehicle : extends
    Truck --|> Vehicle : extends
    Ticket --> Vehicle : records
    Ticket --> ParkingSpot : records
    ParkingLot o-- PricingStrategy : uses
    HourlyPricing ..|> PricingStrategy : implements

Hierarchy:

  • ParkingSpot โ†’ subclasses: BikeSpot, CompactSpot, LargeSpot
  • Vehicle โ†’ subclasses: Bike, Car, Truck
  • ParkingLot โ†’ Singleton (one physical lot, one controller)

๐Ÿงฑ OOP Pillars in the Parking Lot Domain

The four OOP pillars are not abstract ideals here โ€” each one maps to a concrete class or interface in this design.

Encapsulation โ€” ParkingFloor hides its Min-Heap

ParkingFloor owns a Map<VehicleType, PriorityQueue<ParkingSpot>> internally. No external class ever touches that map directly. The public surface is exactly two methods: findNearest(VehicleType) (wraps poll()) and releaseSpot(ParkingSpot) (wraps offer()). Callers never manipulate the heap โ€” so the internal structure can be swapped from PriorityQueue to TreeSet without changing a single line outside ParkingFloor.

Abstraction โ€” PricingStrategy and ParkingSpot hide the HOW

PricingStrategy exposes one method: calculateFee(VehicleType, long minutes). Callers know what it computes (a fee), never how hourly or dynamic pricing does it. Similarly, ParkingSpot as an abstract class exposes assign(Vehicle) and free() โ€” callers never need to know whether the underlying spot is a narrow bike bay or a wide truck stall.

Inheritance โ€” The Spot and Vehicle Hierarchies

BikeSpot extends ParkingSpot inherits the assign(Vehicle) / free() lifecycle from the abstract base, overriding only getCompatibleType() to return VehicleType.BIKE. Adding EVSpot later is one new subclass โ€” the entire lifecycle is already implemented in the base class.

Polymorphism โ€” Type Dispatch Without Casting

ParkingFloor stores PriorityQueue<ParkingSpot> entries that hold BikeSpot, CompactSpot, or LargeSpot instances โ€” all referenced as ParkingSpot. findNearest(VehicleType.CAR) dispatches to the correct heap and returns a ParkingSpot reference; the caller never casts. Similarly, VehicleFactory.create() always returns a Vehicle reference regardless of whether it built a Bike, Car, or Truck.

PillarWhere it appears in this design
EncapsulationParkingFloor hides PriorityQueue behind findNearest()/releaseSpot()
AbstractionPricingStrategy hides fee algorithm; ParkingSpot hides spot-type specifics
InheritanceBikeSpot, CompactSpot, LargeSpot extend ParkingSpot; Bike, Car, Truck extend Vehicle
PolymorphismPriorityQueue<ParkingSpot> holds all subtypes; VehicleFactory returns Vehicle for any concrete type

โš™๏ธ Finding the Nearest Spot: Min-Heap

A naive O(N) linear scan through all spots is too slow for a large garage.

Optimized: Use a Min-Heap (priority queue ordered by spot ID, lowest = nearest entrance) per vehicle type per floor.

Floor 1 Compact Heap: [101, 103, 107, ...]   โ† pop 101 in O(log N)
Floor 1 Large Heap:   [150, 155, ...]

Operations:

  • findNearest(CAR) โ†’ heaps.get(CAR).poll() โ†’ O(log N)
  • releaseSpot(spot) โ†’ heaps.get(spot.type).offer(spot) โ†’ O(log N)

For N = 1000 spots per floor: logโ‚‚(1000) โ‰ˆ 10 operations vs. 1000 for linear scan.


๐Ÿง  Deep Dive: Factory and Strategy Patterns

public class VehicleFactory {
    public static Vehicle create(VehicleType type, String plate) {
        return switch (type) {
            case BIKE   -> new Bike(plate);
            case CAR    -> new Car(plate);
            case TRUCK  -> new Truck(plate);
        };
    }
}

Callers use VehicleFactory.create(VehicleType.CAR, "ABC-123") โ€” never new Car(...) directly. This decouples object creation from business logic and makes it easy to add ElectricVehicle later.


โš™๏ธ Strategy Pattern for Pricing

Pricing rules vary (hourly, daily, weekend, EV charging surcharge):

public interface PricingStrategy {
    double calculateFee(VehicleType type, long parkingMinutes);
}

public class HourlyPricing implements PricingStrategy {
    public double calculateFee(VehicleType type, long minutes) {
        double ratePerHour = type == VehicleType.TRUCK ? 3.50 :
                             type == VehicleType.CAR   ? 2.00 : 1.00;
        return ratePerHour * Math.ceil(minutes / 60.0);
    }
}

The ParkingLot holds a reference to any PricingStrategy. Swapping from HourlyPricing to DynamicPricing requires zero changes to the rest of the code.


๐Ÿ“ Interface Contracts

Every behavioral variation point in the design is expressed as a Java interface. These are the two core contracts that decouple callers from implementations:

/**
 * Abstracts: the fee calculation algorithm (hourly, daily, dynamic, EV surcharge).
 * Implemented by: HourlyPricing, DailyPricing, DynamicPricing, ChargingPricing.
 * Consumed by: ParkingLot.exit(Ticket) โ€” calls calculateFee() without knowing the concrete rule.
 */
interface PricingStrategy {
    double calculateFee(VehicleType type, long parkingMinutes);
}

/**
 * Abstracts: nearest-spot lookup and release across all floors.
 * Implemented by: NearestFirstAllocator (Min-Heap), RoundRobinAllocator.
 * Consumed by: ParkingLot.park(Vehicle) โ€” delegates floor traversal to the allocator.
 */
interface SpotAllocator {
    Optional<ParkingSpot> findNearest(VehicleType type, List<ParkingFloor> floors);
    void release(ParkingSpot spot, List<ParkingFloor> floors);
}

Both interfaces are intentionally minimal โ€” each covers one behavioral axis. PricingStrategy does exactly one thing: compute a fee. SpotAllocator does exactly one thing: find and release spots. Neither interface carries unrelated methods that would force implementors to stub out no-ops.

InterfaceAbstractsImplemented byConsumed by
PricingStrategyFee calculation algorithmHourlyPricing, DailyPricing, ChargingPricingParkingLot.exit()
SpotAllocatorSpot lookup + release logicNearestFirstAllocator, RoundRobinAllocatorParkingLot.park()

โœ… SOLID Principles in the Parking Lot Design

PrincipleHow it appears in this design
SRPParkingFloor manages spots only. FeeCalculator handles pricing only. ParkingLot orchestrates entry/exit only. No class does two jobs.
OCPAdding an EV vehicle type means a new EVSpot extends ParkingSpot and a new ChargingPricing implements PricingStrategy โ€” zero existing classes modified.
LSPBikeSpot, CompactSpot, and LargeSpot all honour the ParkingSpot contract: assign(Vehicle) marks the spot occupied; free() marks it available. No subtype surprises the caller.
ISPPricingStrategy is a single-method interface. SpotAllocator is a two-method interface scoped to allocation only. Neither is a fat interface that forces no-op stubs.
DIPParkingLot depends on PricingStrategy (abstraction), not on HourlyPricing (concrete class). The concrete implementation is injected at construction time โ€” the lot controller never imports a concrete pricing class.

OCP in action โ€” adding EV support without touching any existing class:

// New subclass only โ€” ParkingSpot, ParkingFloor, ParkingLot all untouched
public class EVSpot extends ParkingSpot {
    public EVSpot(int id) { super(id, VehicleType.ELECTRIC_CAR); }
}

// New strategy only โ€” implements the existing interface
public class ChargingPricing implements PricingStrategy {
    private static final double CHARGE_RATE_PER_MIN = 0.25;
    private final PricingStrategy base = new HourlyPricing();

    @Override
    public double calculateFee(VehicleType type, long minutes) {
        return base.calculateFee(type, minutes) + (CHARGE_RATE_PER_MIN * minutes);
    }
}

ParkingLot, ParkingFloor, Ticket, and every other existing class remain untouched. Two new classes added, zero classes modified. This is the Open/Closed Principle as a concrete code change, not a theoretical promise.


โš–๏ธ Trade-offs & Failure Modes in Parking Lot Design

PatternApplied ToBenefit
SingletonParkingLotOne physical lot, one controller; no duplicate state
Factory MethodVehicleFactoryDecouple vehicle creation; easy to add new types
StrategyPricingStrategySwap pricing rules (hourly/daily/EV) without touching lot logic
Min-HeapParkingFloor.availableSpotsO(log n) nearest-spot allocation
Template MethodParkingSpot.assign()/free()Common lifecycle in abstract base class

๐Ÿ“Š How Parking Works: End-to-End Flow

Every parking interaction follows a predictable lifecycle โ€” from the moment a car joins the entry queue to the moment the exit barrier lifts. The diagram below traces that full path through the system.

flowchart TD
    A[Vehicle Arrives at Entry Gate] --> B{Compatible Spot Available?}
    B -- No --> C[Display 'Lot Full' & Reject Vehicle]
    B -- Yes --> D[Allocate Nearest Spot\nMin-Heap pop โ†’ O log N]
    D --> E[Issue Ticket\nvehicle + spot + entryTime]
    E --> F[Vehicle Parks in Assigned Spot]
    F --> G[Vehicle Returns to Exit Gate]
    G --> H[Driver Scans Ticket]
    H --> I[Calculate Fee\nduration ร— rate via PricingStrategy]
    I --> J[Driver Pays Fee]
    J --> K[Release Spot\nMin-Heap offer โ†’ O log N]
    K --> L[Exit Gate Opens]

What makes this non-trivial at scale:

  • Step D must complete in O(log N) โ€” slow allocation creates entry-gate queues that back up into the street.
  • Step I must be strategy-swappable โ€” pricing rules change for holidays, EV charging surcharges, and peak-hour tariffs without any structural code change.
  • Step K must be thread-safe โ€” dozens of exits can fire simultaneously; without locking, two gate threads can both read the same spot as "available" and double-allocate it.

The two Min-Heap operations (D and K) are symmetric: poll() at entry, offer() at exit. This keeps the heap always consistent with the real state of the floor.


๐ŸŒ Real-World Applications of Parking Lot Design

The Factory + Strategy + Min-Heap combination is not academic โ€” the same structural decisions appear across a broad range of deployed parking infrastructure.

Airport Garages โ€” Large multi-level garages display real-time availability counts above each lane ("Floor 3 โ€” 47 spots free"). The Min-Heap allocation logic maps directly to the software that decrements and increments those counters per floor, per spot tier.

Hospital Parking Systems โ€” Hospitals add priority spot categories: accessible bays, ambulance zones, reserved staff spaces. The ParkingSpot hierarchy (BikeSpot, CompactSpot, LargeSpot) extends naturally to AccessibleSpot and ReservedSpot without touching allocation or pricing logic.

Smart City Parking Apps โ€” Apps like ParkWhiz and SpotHero expose real-time APIs backed by lot management systems. The Strategy Pattern for pricing maps directly to dynamic rate engines that adjust fees by time of day, demand level, and event proximity. Injecting a DemandBasedPricing strategy requires zero changes to the core lot controller.

EV Charging Stations โ€” Charging-enabled spots add a per-minute electricity surcharge on top of the parking rate. A ChargingPricing strategy encapsulates that logic cleanly โ€” the rest of the system never needs to know a charging cable is involved.

The same three patterns scale from a 50-space surface lot to a 5,000-space multi-level garage with minimal structural change.


๐Ÿงช Practical Exercises: Extending the Design

Try these exercises to move from reading to building:

Exercise 1 โ€” Add an Electric Vehicle Spot Type The current design has BikeSpot, CompactSpot, and LargeSpot. Add an EVSpot that can only be assigned to ElectricCar vehicles. Steps: (a) add ELECTRIC_CAR to VehicleType; (b) add ElectricCar to VehicleFactory; (c) implement ChargingPricing that adds $0.25/min on top of the base HourlyPricing rate; (d) inject it into ParkingLot at startup.

Exercise 2 โ€” Multi-Level Nearest-Spot Search The current ParkingLot.park() only searches Floor 1. Modify it to iterate through all floors and return the globally nearest spot (lowest floor number wins; tie-break by lowest spot ID). Benchmark: how does the search time grow as you add floors?

Exercise 3 โ€” Thread-Safe Concurrent Entry Gates Simulate two vehicles arriving in the same millisecond. What happens if two gate threads both call findNearest(CAR) simultaneously on the same floor? Add a ReentrantLock (or synchronized block) to ParkingFloor.findNearest() and releaseSpot(). Write a JUnit test using two threads to verify no spot is ever double-allocated.

Each exercise directly extends one design decision from this article โ€” Factory extension, allocation scope, and thread safety respectively.


๐Ÿ“š Key Lessons from Designing a Parking Lot System

  • Singleton prevents duplicate state โ€” a parking lot is one physical system; one controller instance with one source of truth stops inconsistent spot counts across gate threads.
  • Min-Heap is the right data structure for "find nearest" โ€” O(log N) poll/offer beats O(N) linear scan at any non-trivial garage size; 1,000 spots means ~10 operations vs. 1,000.
  • Factory decouples creation from business logic โ€” adding a new vehicle type (ElectricCar, Motorbike) is a one-class change that never touches gate, floor, or payment code.
  • Strategy makes pricing pluggable โ€” new fee rules are new classes, not edits to existing ones. This is the Open/Closed Principle in concrete action.
  • Thread safety is not optional โ€” concurrent entry gates without locking produce double-allocation bugs that are rare, hard to reproduce in tests, and expensive to diagnose in production.

๐Ÿงญ Decision Guide: When to Apply These Parking Lot Patterns

ConditionRecommendation
Multiple extensible vehicle typesFactory Pattern โ€” one creation point, zero scattered new Car() calls
Pricing rules change per business contextStrategy Pattern โ€” swap HourlyPricing for DynamicPricing with no lot code changes
>100 spots per floorMin-Heap per vehicle type โ€” O(log N) vs O(N) matters at scale
One physical lot, one controllerSingleton ParkingLot โ€” prevents dual-instance state drift
Entry gates run concurrentlyThread-safe heap operations โ€” wrap PriorityQueue in synchronized or use PriorityBlockingQueue

Start simple: a flat HashMap<SpotId, Spot> works for a garage under 50 spots. Add the Min-Heap only when profiling shows allocation latency. Apply Factory and Strategy from the start โ€” they add one interface each and pay back immediately when requirements change.


๐Ÿ“Œ TLDR: Summary & Key Takeaways

  • Singleton ParkingLot prevents duplicate instance state.
  • Min-Heap per vehicle type per floor โ†’ O(log N) nearest-spot allocation vs. O(N) naive scan.
  • Factory Pattern decouples vehicle creation from business logic.
  • Strategy Pattern keeps pricing rules pluggable and testable in isolation.
  • See the companion post Implement LLD for Parking Lot for the full thread-safe Java implementation.

๐Ÿ“ Practice Quiz

  1. Why is a Min-Heap better than a List for tracking available spots?

    • A) Min-Heap supports more vehicle types.
    • B) Min-Heap gives O(log N) for nearest-spot pop and spot release vs O(N) linear scan.
    • C) Min-Heap prevents overlapping spots.
      Correct Answer: B โ€” A Min-Heap (priority queue) gives O(log N) for both poll() (allocate nearest) and offer() (release spot), compared to O(N) for scanning a list every time.
  2. What does the Factory Pattern give you in vehicle creation?

    • A) Thread safety for concurrent gate access.
    • B) A single creation point that decouples new Car() from business logic, making it easy to extend with new types.
    • C) A way to pool and reuse Vehicle objects.
      Correct Answer: B โ€” Factory Pattern centralises all vehicle construction in VehicleFactory.create(), so callers never use new Car() directly. Adding ElectricCar later means one new class and one new switch case โ€” nothing else changes.
  3. You want to add "peak-hour surcharge" pricing. With the Strategy Pattern, how many existing classes do you need to change?

    • A) All classes that calculate fees.
    • B) Zero โ€” create a new PeakHourPricing implements PricingStrategy and inject it into ParkingLot.
    • C) ParkingLot, Ticket, and ParkingFloor all need updates.
      Correct Answer: B โ€” Strategy Pattern means zero modifications to existing classes. You write PeakHourPricing implements PricingStrategy, inject it into ParkingLot, and the rest of the system is untouched. This is the Open/Closed Principle in practice.

๐Ÿ› ๏ธ Spring Boot and MapStruct: Wiring the Parking Lot Domain to a REST Layer

Spring Boot turns the ParkingLot domain model into a running web service with zero XML configuration โ€” @SpringBootApplication auto-configures the IoC container, embedded Tomcat, and Jackson JSON serialization. MapStruct generates type-safe, reflection-free mapping code between domain objects (ParkingSpot, Ticket) and REST response DTOs, eliminating the manual target.setField(source.getField()) boilerplate that violates DRY across multiple controllers.

How they solve the problem in this post: The ParkingLotService becomes a Spring @Service singleton that the @RestController receives via constructor injection. MapStruct generates ParkingSpotMapper.toDto(ParkingSpot) at compile time, so the controller never reaches directly into the domain model โ€” maintaining a clean separation between the REST representation and the internal domain.

// โ”€โ”€โ”€ REST DTO โ€” what the API returns (no domain logic) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
public record ParkingSpotDto(int id, String type, boolean available, int floor) {}
public record TicketDto(String ticketId, String plate, String vehicleType,
                        int floor, int spotId, String entryTime) {}

// โ”€โ”€โ”€ MapStruct mapper โ€” compile-time generated, zero reflection โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
// Dependency: org.mapstruct:mapstruct:1.5.5 + mapstruct-processor (annotationProcessor)
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;

@Mapper(componentModel = "spring")  // generates a Spring @Component bean
public interface ParkingSpotMapper {
    @Mapping(source = "free", target = "available")
    ParkingSpotDto toDto(ParkingSpot spot);
}

// โ”€โ”€โ”€ Spring service: wraps ParkingLot Singleton โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
import org.springframework.stereotype.Service;

@Service
public class ParkingLotService {

    private final ParkingLot lot = ParkingLot.getInstance();

    public TicketDto park(String plate, VehicleType type) {
        Vehicle v = VehicleFactory.create(plate, type);
        Ticket t  = lot.park(v).orElseThrow(() ->
                new org.springframework.web.server.ResponseStatusException(
                    org.springframework.http.HttpStatus.CONFLICT,
                    "No spot available for " + type));
        return new TicketDto(t.getId(), plate, type.name(),
                             t.getFloor(), t.getSpotId(),
                             t.getEntryTime().toString());
    }

    public double exit(String ticketId) {
        return lot.exit(ticketId);
    }
}

// โ”€โ”€โ”€ REST controller โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/v1/parking")
public class ParkingLotController {

    private final ParkingLotService service;
    private final ParkingSpotMapper mapper;

    public ParkingLotController(ParkingLotService service, ParkingSpotMapper mapper) {
        this.service = service;
        this.mapper  = mapper;
    }

    /** POST /api/v1/parking/enter?plate=MH01AB1234&type=CAR */
    @PostMapping("/enter")
    public ResponseEntity<TicketDto> enter(
            @RequestParam String plate,
            @RequestParam VehicleType type) {
        return ResponseEntity.status(HttpStatus.CREATED).body(service.park(plate, type));
    }

    /** DELETE /api/v1/parking/exit/{ticketId} โ†’ returns fee */
    @DeleteMapping("/exit/{ticketId}")
    public ResponseEntity<Map<String, Object>> exit(@PathVariable String ticketId) {
        double fee = service.exit(ticketId);
        return ResponseEntity.ok(Map.of("ticketId", ticketId, "fee", fee));
    }
}

@Mapper(componentModel = "spring") tells MapStruct to generate a @Component-annotated implementation class, so Spring auto-discovers and injects it like any other bean. The generated code is plain Java with direct field access โ€” 5โ€“10ร— faster than ModelMapper and catches field-name mismatches at compile time, not at runtime.

For a full deep-dive on Spring Boot REST layer patterns and MapStruct advanced mapping, a dedicated follow-up post is planned.



Abstract Algorithms

Written by

Abstract Algorithms

@abstractalgorithms