DEV Community

Building Pultsnab — a Tender & Procurement Platform with Spring Boot 3 and Java 21

How we built a full-featured tender management system — from requests and proposals to contracts, deliveries, and reporting.

Pultsnab is currently available in Russian only; localization (i18n) is planned for future releases. Website: pultsnab.ru (UTF-8).

Why we built it
Procurement and tender processes are often scattered across spreadsheets, email, and legacy tools. We wanted a single platform where customers could publish tenders, suppliers could submit proposals, and managers could run the full cycle: evaluation, contract award, delivery tracking, invoicing, and reporting — all with proper roles and audit trails.

So we built Pultsnab: a Spring Boot backend that powers exactly that.

Tech stack at a glance
Layer Choice
Runtime Java 21
Framework Spring Boot 3.5
Database PostgreSQL + JPA/Hibernate
Migrations Liquibase
Auth Spring Security + JWT (access + refresh)
Validation Hibernate Validator 8
Mapping MapStruct
Files AWS S3–compatible (e.g. MinIO)
Documents Apache POI (Excel), OpenPDF (PDF)
Mail Spring Boot Mail (notifications)
Nothing exotic — just a solid, maintainable stack that's easy to deploy and reason about.

What the system does
Tenders — Create, publish, and manage tenders with statuses: Draft → Published → Bidding → Evaluation → Awarded (or Cancelled). Each tender has items, deadlines, and terms.
Supplier proposals — Suppliers submit proposals per tender; the system supports comparison and price analysis.
Contracts — Awarded tenders turn into contracts with items, delivery and payment terms.
Deliveries & warehouses — Track deliveries against contract items and warehouses.
Invoicing — Invoices linked to contracts, with PDF generation (e.g. bank payment slips) and payment tracking.
Companies & contacts — Customers and suppliers as companies with contacts, bank details, and requisites.
Requests — Internal requests that can be turned into tenders (request → tender → contract flow).
Dashboard & reports — Dashboards by period and role, plus operational/financial reports and Excel export.
Alerts & notifications — Email notifications for key events (tender published, proposal submitted, tender awarded/cancelled, reminders).
Security & audit — Role-based access (e.g. Admin, Manager, Supplier, Viewer), JWT auth, and audit logs for important actions.
So the backend is a full tender-to-contract-to-delivery-to-invoice pipeline, not just a CRUD API.

App preview (generated data)
Below is a mockup of the main screens with sample data: dashboard, tenders, contracts, and deliveries. Switch tabs to explore.

Pultsnab — основные экраны (демо-данные)

Architecture highlights
REST API — Dozens of controllers under /api/*: tenders, proposals, contracts, deliveries, invoices, companies, users, reports, etc. We use @PreAuthorize for role checks so each endpoint is explicitly protected.

Layered structure — Controllers → Services → Repositories, with DTOs and MapStruct mappers so we don't expose entities. Validation on DTOs with @valid and Hibernate Validator keeps invalid data out of the core.

Database — PostgreSQL with Liquibase for versioned migrations. We use ddl-auto: none and rely on Liquibase for schema changes. Hibernate is tuned for batching and fetch depth to avoid N+1 and lazy-load issues.

File storage — AWS S3–compatible API (e.g. MinIO in dev). Uploads go to a configurable bucket; documents and exports can be generated (e.g. PDF bank invoices) and stored or streamed back.

Scheduled jobs — An alert scheduler runs periodic checks (e.g. tender deadlines, reminders) and triggers email notifications. Spring's @Scheduled keeps this simple.

What we'd share in follow-up posts
JWT setup — Access + refresh tokens, storage, and how we plug them into Spring Security.
Role-based access — How we map roles (Admin, Manager, Supplier, Viewer) to endpoints and UI capabilities.
From request to tender to contract — How one entity drives the next and how we keep data consistent.
Generating PDFs — OpenPDF + Cyrillic fonts for bank invoices and other documents.
Excel import/export — Apache POI for bulk data and report export with configurable column mapping.
Takeaways
Spring Boot 3 + Java 21 — Smooth experience; no need for the latest experimental features, just stable LTS and framework support.
Liquibase — Versioned migrations made schema evolution and multi-environment deploys predictable.
MapStruct — Kept mapping logic out of services and made it easy to add new DTOs without boilerplate.
S3-compatible storage — One integration (AWS SDK v2) works with MinIO locally and AWS in production.
Clear API boundaries — REST + DTOs + validation made it easier to add a frontend and later mobile clients without touching core logic.
Try it or contribute
The project is Pultsnab (tender management backend). You can run it with Java 21, PostgreSQL, and an S3-compatible store (e.g. MinIO); see the repo for configuration and optional Docker/local setup. Visit the app at pultsnab.ru.

If you're building something similar — procurement, contracts, or document-heavy backends — we're happy to go deeper on any part (auth, workflows, PDF/Excel, or deployment) in future posts.

What would you like to read about next: JWT + Spring Security, the tender→contract flow, or PDF/Excel generation?

Tags: pultsnab java springboot postgresql backend rest-api jwt spring-security liquibase mapstruct

Top comments (0)