I Built a Full Admin Panel in 30 Lines of Java. No Frontend. No CRUD. No Kidding.
Tags: java, springboot, lowcode, webdev
Last quarter my team got a new requirement: build an operations dashboard. Manage users, filter orders, configure parameters, export reports, handle permissions.
Pretty standard stuff.
I sat down and broke it into tasks:
- 12 Controllers
- 12 Services
- 12 Repositories
- 12 frontend pages (tables + forms + search bars)
- Permission configs × however many roles we had
- Import/export logic for the important ones
My estimate: 6 weeks.
My tech lead's response: read receipt, no reply.
I Found Erupt While Doom-Scrolling GitHub
A few days into the sprint, I was browsing GitHub and saw a project called Erupt.
The tagline: "Annotation-driven development. Zero frontend code. Zero CRUD. Auto table creation."
My immediate reaction was "yeah sure, another toy framework."
But I clicked anyway.
Then I saw this:
@Erupt(
name = "User Management",
power = @Power(export = true, importable = true)
)
@Table(name = "t_user")
@Entity
public class User extends BaseModel {
@EruptField(
views = @View(title = "Username"),
edit = @Edit(title = "Username", search = @Search(vague = true))
)
private String username;
@EruptField(
views = @View(title = "Status"),
edit = @Edit(title = "Status", type = EditType.BOOLEAN, search = @Search)
)
private Boolean active;
@EruptField(
views = @View(title = "Created At", sortable = true),
edit = @Edit(title = "Created At", search = @Search)
)
private Date createTime;
}
That's it. That's the whole model.
I cloned the repo, ran EruptSampleApplication.main(). No database config. No YAML tweaking. H2 in-memory database starts automatically.
Opened localhost:8080. Logged in (erupt / erupt).
The admin panel was just... there.
Paginated table. Search bar. Add/edit form. Excel export button. Permission control.
I stared at the screen for a solid minute confirming it wasn't hardcoded fake data.
It wasn't.
What This Looks Like Compared to the Traditional Way
Let me be concrete. Here's the traditional approach for one entity with search, pagination, and export:
// 1. Entity (fine, same either way)
@Entity
public class User { ... }
// 2. Repository
public interface UserRepository extends JpaRepository<User, Long> {
Page<User> findByUsernameContaining(String username, Pageable pageable);
// + more query methods
}
// 3. Service
@Service
public class UserService {
public Page<User> search(String username, int page, int size) { ... }
public User create(UserDTO dto) { ... }
public User update(Long id, UserDTO dto) { ... }
public void delete(Long id) { ... }
public void exportExcel(HttpServletResponse response) { ... }
}
// 4. Controller
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping public Page<User> list(...) { ... }
@PostMapping public User create(...) { ... }
@PutMapping("/{id}") public User update(...) { ... }
@DeleteMapping("/{id}") public void delete(...) { ... }
@GetMapping("/export") public void export(...) { ... }
}
// 5. Frontend: table component, form component, search component,
// API calls, state management, pagination logic...
// (we're not even counting this)
That's roughly 200–300 lines of backend alone, plus a full frontend for every entity.
The Erupt way for the same result: the 30-line class I showed above.
One annotation. One class. Done.
How Does It Actually Work?
Erupt's core idea is: encode frontend UI semantics directly into Java annotations.
At runtime, the framework translates your annotations into a JSON Schema, sends it to a built-in Angular frontend (packaged inside the JAR), and the frontend dynamically renders the correct table, form, and search components.
This means:
- No code generation — it's not printing boilerplate for you to maintain
- Purely dynamic — change an annotation, the UI changes immediately
- Fully type-safe — rename a field, the compiler tells you what broke
And crucially: it's built on top of Spring Boot. Your existing Spring Beans, Services, and Repositories all work normally alongside it.
The Features I Use Every Week
Search in one annotation:
edit = @Edit(title = "Email", search = @Search(vague = true))
// vague = true means LIKE search, false means exact match
Dropdown from a SQL query:
edit = @Edit(
title = "Department",
type = EditType.CHOICE,
choiceType = @ChoiceType(
fetchHandler = SqlChoiceFetchHandler.class,
fetchHandlerParams = "select id, name from t_department"
)
)
Custom row-level actions:
@Erupt(
name = "Orders",
rowOperation = @RowOperation(
title = "Approve",
mode = RowOperation.Mode.SINGLE,
operationHandler = ApproveOrderHandler.class
)
)
Type-safe queries in business logic (this one I genuinely love):
List<User> users = eruptDao.lambdaQuery(User.class)
.like(User::getUsername, "john")
.eq(User::getActive, true)
.ge(User::getCreateTime, "2024-01-01")
.list();
No string column names. No typos. Rename the field → compilation error → you fix it.
DataProxy: Where You Put Business Logic
The obvious question: what if I need custom logic on save/delete?
That's what DataProxy is for. Annotate your class, implement the hooks you care about:
@Erupt(name = "Users", dataProxy = UserDataProxy.class)
public class User extends BaseModel { ... }
@Component
public class UserDataProxy implements DataProxy<User> {
@Override
public void beforeAdd(User user) {
user.setPassword(BCrypt.hashpw(user.getPassword()));
}
@Override
public void afterDelete(User user) {
auditLog.record("deleted user: " + user.getUsername());
}
@Override
public String searchCondition() {
// Row-level security: only show users in current admin's org
return "org_id = " + getCurrentUserOrgId();
}
}
All the lifecycle hooks: beforeAdd, afterAdd, beforeUpdate, afterUpdate, beforeDelete, afterDelete, beforeFetch, afterFetch, searchCondition, validate. You implement what you need, ignore the rest.
What About Complex Cases?
Erupt handles more than simple CRUD tables:
-
Tree views — hierarchical data with
@Tree -
Left-tree right-table — master/detail layout with
@LinkTree - Tab sub-tables — inline child records
-
Gantt charts — built-in via
erupt-extra - Card views — grid/kanban style layouts
- 20+ form components — sliders, date ranges, rich text, file upload, image crop
For anything truly custom, @Tpl lets you drop in your own HTML/JS template inside a field.
The AI Integration Surprised Me
I expected the AI module to be a thin wrapper. It's not.
@AiToolbox
@Component
public class OrderTools {
@Tool("Query order status by order ID")
public String getOrderStatus(String orderId) {
return orderService.getStatus(orderId);
}
@Tool("List all pending approvals for a user")
public List<Approval> getPendingApprovals(Long userId) {
return approvalService.getPending(userId);
}
}
Add @AiToolbox to any Spring component, annotate methods with @Tool, and those methods become callable tools for any connected LLM. No boilerplate, no schema definitions — Erupt reflects on the method signature and generates the tool spec automatically.
It supports OpenAI, Claude, Gemini, DeepSeek, Qwen, and a bunch more, all configurable through the built-in admin UI without touching code or restarting.
What I'd Warn You About
It's not for everything. If you're building a complex consumer-facing UI with heavily custom designs, Erupt won't help — it's specifically for admin/ops dashboards, internal tools, and data management interfaces.
The documentation is still catching up. The project is primarily Chinese and the English docs are sparse. I've had to dig through source code a few times. (That said, the annotations themselves are well-commented, and the sample app covers most cases.)
GSON instead of Jackson. Erupt uses GSON internally. If your project heavily relies on Jackson customizations, you might hit some friction.
My Honest Assessment After 6 Months
That 6-week estimate? I delivered the dashboard in 3 days.
Not because I worked faster. Because I wrote about 200 lines of Java total — mostly entity classes with annotations — and Erupt handled everything else.
I'm not saying it replaces real engineering. Complex business logic still lives in your services. Row-level security, custom workflows, integrations — you still write those. But the repetitive scaffolding that makes admin dashboards expensive? Erupt makes it disappear.
If you're a Java developer who regularly builds internal tools, operations dashboards, or data management UIs, this framework will change how you think about that work.
Try It in 2 Minutes
git clone https://github.com/erupts/erupt
cd erupt/erupt-sample
mvn spring-boot:run
Open http://localhost:8080. Login: erupt / erupt.
No database setup. No config changes. It just works.
If this saved you some time (or at least made you curious), the best thing you can do is drop a ⭐ on GitHub:
It's open source, Apache 2.0, and the maintainer is actively building it. The project deserves more visibility in the English-speaking Java world — hopefully this article helps a little.
Have you used Erupt or a similar annotation-driven framework? I'm curious how others are handling the "we need an admin panel by Friday" problem. Drop a comment below.
Top comments (0)