Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MiniSpring

A tiny Spring Boot clone built from scratch in pure Java 25 — no Spring, no Tomcat, no Jackson, zero dependencies.

The goal is to demystify the framework. Annotations aren't magic, dependency injection isn't magic, and neither is routing. Under the hood it's reflection, a HashMap, and a for loop. This repo is the whole thing in ~500 lines you can read in one sitting.

void main() {
    MiniSpring.run("com.amigoscode", 8080);
}
  __  __ _       _ ____             _
 |  \/  (_)_ __ (_) ___| _ __  _ __(_)_ __   __ _
 | |\/| | | '_ \| \___ \| '_ \| '__| | '_ \ / _` |
 | |  | | | | | | |___) | |_) | |  | | | | | (_| |
 |_|  |_|_|_| |_|_|____/| .__/|_|  |_|_| |_|\__, |
                         |_|                 |___/

  MiniSpring started in 59ms
  Listening on http://localhost:8080
  Components registered: 1
  Controllers registered: 1

What it does

Write a controller exactly like you would in Spring Boot:

@RestController
@RequestMapping("/api/v1/users")
public class UserController {

    private final UserService userService;

    // Constructor injection — one constructor, no @Autowired needed
    public UserController(UserService userService) {
        this.userService = userService;
    }

    @Get
    public List<User> getAllUsers() {
        return userService.findAll();
    }
}
@Service
public class UserService {
    public List<User> findAll() { ... }
}

Then:

curl http://localhost:8080/api/v1/users
[{"id":"d1c6a2a4","name":"Amigos","email":"amigos@code.com"}]

Requirements

Maven must run on JDK 25 too. If mvn picks up an older JDK, point it at the right one:

export JAVA_HOME=$(/usr/libexec/java_home -v 25)   # macOS

Running it

mvn compile exec:exec
curl http://localhost:8080/api/v1/users

exec:exec, not exec:java. Main.java is a compact source file, which compiles to a package-private class with an instance main(). The in-process runner looks for a public static void main(String[]) and won't find one — only the real java launcher understands the JEP 512 protocol.

How it works

Five pieces, each one small enough to read in a few minutes.

File Role Spring equivalent
MiniSpring.java Scans the classpath, builds everything, starts the server SpringApplication.run()
di/Container.java Creates beans and injects dependencies ApplicationContext
Router.java Maps GET /path to a controller method DispatcherServlet
WebServer.java Embedded HTTP server (JDK's HttpServer) Embedded Tomcat
json/Json.java Serializes objects to JSON via reflection Jackson

Startup is five steps:

  1. Component scan — walk the package directory, load every .class file
  2. Sort — split them into @Service and @RestController
  3. Wire — hand them to the container, which builds each bean and injects its dependencies
  4. Route — read @RequestMapping off each controller and @Get off each method, build a route table
  5. Serve — start the HTTP server and delegate every request to the router

The annotations

They're just labels. Each one is a handful of lines with no behaviour of its own — the framework is what gives them meaning.

Annotation Applies to Purpose
@RestController class Marks a class as an HTTP controller
@RequestMapping class Base path shared by every route in the controller
@Get method Maps a method to GET, appended to the base path
@Service class Marks a class as a managed bean
@Autowired field, constructor Marks an injection point

Dependency injection

The container follows Spring's own rules for picking a constructor:

  1. A constructor annotated @Autowired wins
  2. Otherwise, a single constructor is used with no annotation at all (Spring 4.3+)
  3. Otherwise, fall back to the no-arg constructor

Beans are created recursively — to build one, the container first builds everything its constructor asks for. Field injection via @Autowired still works, and circular dependencies are detected and reported with the chain (A -> B -> A) instead of blowing the stack.

Project layout

src/main/java/
├── Main.java                          # compact source file, unnamed package
└── com/amigoscode/
    ├── app/                           # the demo application
    │   ├── User.java
    │   ├── UserController.java
    │   └── UserService.java
    └── framework/                     # MiniSpring itself
        ├── MiniSpring.java
        ├── Router.java
        ├── WebServer.java
        ├── annotations/
        ├── di/Container.java
        └── json/Json.java

Deliberate limitations

This is a teaching framework, not a production one. It supports GET only, matches paths by exact string equality (no path variables — @PathVariable exists but isn't wired up yet), serializes JSON one way, has no request bodies, no error handling beyond a blanket 500, and every bean is an eager singleton.

That's the point. Every omission is a feature you now know how to add.

License

MIT

About

A tiny Spring Boot clone built from scratch in pure Java 25 — no Spring, no Tomcat, no Jackson, zero dependencies.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages