Enterprise Backend Development with Java Spring Boot
Manually configuring a Spring application eats hours on XML configs and boilerplate. Switching to Spring Boot gives you auto-configuration, embedded Tomcat, and a ready-made JPA + Security stack. We’ve been using this stack for over 10 years, building transactionally reliable backends for large projects. Typical issues include N+1 queries in JPA and complex security setup. Below we show how we solve them. Development savings can reach up to 40% compared to manual configuration, translating to over $10,000 per project. According to a Stack Overflow survey, Spring Boot is the most popular Java framework. Our certified team with 10+ years of Java experience guarantees quality and timely delivery.
Why Spring Boot Is the Industry Standard for Java Backends
Spring Boot is not a framework but a configuration layer on top of the Spring Framework that eliminates XML and “guesses” settings based on dependencies. The result: embedded Tomcat, JPA, Security, Actuator—all ready after adding starters. It wins over Plain Spring in development speed by 3–4 times, and over Node.js in reliability for heavy transactions. This approach can cut development costs by up to 40% compared to manual setup, with payback within the first year of operation.
What Problems We Solve with This Stack
-
N+1 queries in JPA. Laziness is the driving force: without
@EntityGraphorJOIN FETCH, each getter call spawns SQL. We use@Querywith explicitLEFT JOIN FETCHand projections for read-models. This reduces the number of queries by 3–5 times. - Security configuration. Spring Security with JWT is not trivial: filter chain, CORS, CSRF, method-security. We configure it once and forget it.
-
Transaction management.
@Transactional(readOnly = true)for reads,@Transactionalfor writes, propagation REQUIRES_NEW for long operations. Otherwise deadlocks in MySQL.
How to Avoid N+1 Queries in JPA
Use @EntityGraph or JOIN FETCH in JPQL. We prefer projections for read-models: this reduces transmitted data by 60% and speeds up queries by 2–3 times. Example: @Query("SELECT p.id as id, p.name as name, p.price as price FROM Product p WHERE p.isActive = true") returns only needed fields, not the entire entity.
How We Do It: Stack and Approach
- Java 17+, Spring Boot 3.x (latest stable).
- Database: PostgreSQL 15 with Flyway migrations, indexes via
@Index. - Cache: Redis via
@Cacheable, TTL for each cache. - Async:
@Async+ Spring Cloud Stream for RabbitMQ. - Tests: JUnit 5 + Testcontainers (spins up PostgreSQL in a container).
Example project structure:
src/main/java/com/myapp/
Application.java # entry point
config/
SecurityConfig.java
SwaggerConfig.java
CacheConfig.java
domain/
product/
Product.java # JPA Entity
ProductRepository.java # Spring Data
ProductService.java
ProductController.java
dto/
CreateProductRequest.java
ProductResponse.java
exception/
GlobalExceptionHandler.java
src/main/resources/
application.yml
application-prod.yml
Entity and JPA
@Entity
@Table(name = "products",
indexes = {
@Index(name = "idx_products_slug", columnList = "slug", unique = true),
@Index(name = "idx_products_cat_active", columnList = "category_id, is_active")
})
@EntityListeners(AuditingEntityListener.class)
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 255)
private String name;
@Column(unique = true, length = 255)
private String slug;
@Column(nullable = false, precision = 10, scale = 2)
private BigDecimal price;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "category_id")
private Category category;
@Column(columnDefinition = "jsonb")
@Convert(converter = JsonAttributeConverter.class)
private Map<String, Object> attributes = new HashMap<>();
@Column(nullable = false)
private boolean isActive = true;
@CreatedDate
private Instant createdAt;
@LastModifiedDate
private Instant updatedAt;
}
Repository
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
Page<Product> findByIsActiveTrueOrderByCreatedAtDesc(Pageable pageable);
Page<Product> findByCategoryIdAndIsActiveTrueOrderByCreatedAtDesc(
Long categoryId, Pageable pageable);
Optional<Product> findBySlug(String slug);
@Query("""
SELECT p FROM Product p
LEFT JOIN FETCH p.category
WHERE p.isActive = true
AND (:search IS NULL OR LOWER(p.name) LIKE LOWER(CONCAT('%', :search, '%')))
""")
Page<Product> searchActive(@Param("search") String search, Pageable pageable);
@Query("SELECT p.id as id, p.name as name, p.price as price FROM Product p WHERE p.isActive = true")
List<ProductSummary> findAllSummaries();
}
Service with Cache
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class ProductService {
private final ProductRepository productRepository;
private final CategoryRepository categoryRepository;
private final ApplicationEventPublisher eventPublisher;
public Page<ProductResponse> findAll(ProductListRequest request) {
Pageable pageable = PageRequest.of(
request.page(), request.limit(),
Sort.by(Sort.Direction.DESC, "createdAt")
);
Page<Product> page = (request.search() != null)
? productRepository.searchActive(request.search(), pageable)
: productRepository.findByIsActiveTrueOrderByCreatedAtDesc(pageable);
return page.map(ProductResponse::from);
}
@Cacheable(value = "products", key = "#id")
public ProductResponse findById(Long id) {
return productRepository.findById(id)
.map(ProductResponse::from)
.orElseThrow(() -> new EntityNotFoundException("Product " + id));
}
@Transactional
@CacheEvict(value = "products", allEntries = true)
public ProductResponse create(CreateProductRequest request) {
Category category = request.categoryId() != null
? categoryRepository.findById(request.categoryId())
.orElseThrow(() -> new EntityNotFoundException("Category not found"))
: null;
Product product = new Product();
product.setName(request.name());
product.setSlug(SlugUtils.generate(request.name()));
product.setPrice(request.price());
product.setCategory(category);
product = productRepository.save(product);
eventPublisher.publishEvent(new ProductCreatedEvent(product));
return ProductResponse.from(product);
}
}
Controller and Security
@RestController
@RequestMapping("/api/v1/products")
@RequiredArgsConstructor
@Tag(name = "Products", description = "Product management API")
public class ProductController {
private final ProductService productService;
@GetMapping
public ResponseEntity<Page<ProductResponse>> list(@Valid ProductListRequest request) {
return ResponseEntity.ok(productService.findAll(request));
}
@PostMapping
@PreAuthorize("hasRole('ADMIN')")
@ResponseStatus(HttpStatus.CREATED)
public ProductResponse create(@Valid @RequestBody CreateProductRequest request) {
return productService.create(request);
}
}
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http, JwtAuthFilter jwtFilter) throws Exception {
return http
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(s -> s.sessionCreationPolicy(STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/v1/auth/**").permitAll()
.requestMatchers(GET, "/api/v1/products/**").permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
}
Comparison: Spring Boot vs Plain Spring
| Parameter | Spring Boot | Plain Spring |
|---|---|---|
| Setup time | 1–2 days | 3–5 days |
| Configuration volume | 5–10 lines | 200–500 lines of XML |
| Embedded server | Tomcat, Jetty | None (needs separate) |
| Auto-configuration | Yes (starters) | No |
| Production readiness | Metrics, health starters | Must be configured |
Process: Phases and Timelines
| Phase | Duration | Result |
|---|---|---|
| Analysis and design | 1–2 weeks | API documentation, domain model |
| Scaffolding | 0.5 weeks | Project with configuration |
| Module development | 4–6 weeks | Entities, Repositories, Services |
| Integration | 1–2 weeks | Payments, SMS, email |
| Testing | 1–2 weeks | Unit, integration, load tests |
| Deployment and handover | 1 week | CI/CD, documentation, training |
What Is Included in the Work (Deliverables)
- Source code in a private Git repository with history.
- CI/CD pipeline (GitLab CI): linter, tests, build, deploy to staging/prod.
- Docker images and docker-compose for local setup.
- Swagger/OpenAPI specification.
- Deployment runbook.
- Monitoring: Spring Boot Actuator + Prometheus + Grafana (health, metrics, alerts).
- Architecture and API documentation.
- Training for your team (2–3 sessions).
How We Work
- Analytics and design — gather requirements, design domain model, choose database, agree on API.
- Scaffolding — create project via Spring Initializr, configure dependencies, config files.
- Development — entities, repositories, services, controllers. Each module covered by unit tests.
- Integration — connect external services: payment gateway, SMS, email.
- Testing — integration tests with Testcontainers, load testing (Locust).
- Deploy to staging — verification in production-like environment.
- Handover — code, documentation, training.
Estimated Timelines
- Basic CRUD + authentication: from 3 weeks.
- Full backend (catalog, cart, orders, admin panel, 2 integrations): 8–12 weeks.
- Enterprise system with microservices, RabbitMQ, complex business logic: 14–20 weeks.
Exact timelines are calculated after auditing your requirements. The official repository on GitHub is a mature project. Get a commercial proposal today by contacting us.
Common Mistakes Beginners Make in Spring Boot
- Ignoring projections — SELECT entire entity when only 2 fields are needed. Use interface-based projections.
- Too broad transactions — @Transactional on the whole method with read and write. Separate read-only and write.
- Missing indexes —
findByCategoryIdAndIsActiveTruewithout an index → full scan. Always check explain. - Improper caching — @Cacheable without TTL → memory fills with stale data. Configure eviction.
We use Spring Boot daily and know all the pitfalls. The Wikipedia article provides a detailed overview of the framework, and Boot makes working with it comfortable. Request a free audit of your project — we’ll assess scope and complexity. Contact us for a consultation.
Quick Reference for Frequently Used Annotations
-
@SpringBootApplication— application entry point. -
@RestController— REST API controller. -
@Service— business logic. -
@Repository— DAO layer. -
@Entity— JPA entity. -
@Transactional— transaction management. -
@Cacheable— cache the result. -
@PreAuthorize— access control check.







