Website Backend Development with Java (Spring Boot)

Our company is engaged in the development, support and maintenance of sites of any complexity. From simple one-page sites to large-scale cluster systems built on micro services. Experience of developers is confirmed by certificates from vendors.
Development and maintenance of all types of websites:
Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Showing 1 of 1 servicesAll 2065 services
Website Backend Development with Java (Spring Boot)
Complex
from 2 weeks to 3 months
FAQ
Our competencies:
Development stages
Latest works
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    822
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    847
  • image_website-sbh_0.png
    Website development for SBH Partners
    999
  • image_website-_0.png
    Website development for Red Pear
    451

Website Backend Development with Java (Spring Boot)

Spring Boot is the industrial standard for Java backends. It's not a framework in the conventional sense, but a configuration layer on top of Spring Framework that removes XML configuration and "guesses" necessary settings based on classpath dependencies. Result: embedded Tomcat/Undertow, auto-configured JPA, Security, Actuator — everything is ready to work after adding starters.

Spring Boot is chosen for: enterprise systems with multi-year support requirements, projects needing mature transaction and security ecosystems, teams with existing Java expertise, integrations with legacy Java systems.

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
  domain/
    user/
    order/
  infrastructure/
    persistence/
    messaging/
    external/
  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;

    // getters/setters or @Data Lombok
}

Repository

Spring Data JPA generates implementation from method signature:

@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);

    // Projection for lightweight queries
    @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

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class ProductService {

    private final ProductRepository productRepository;
    private final CategoryRepository categoryRepository;
    private final ApplicationEventPublisher eventPublisher;
    private final CacheManager cacheManager;

    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);
    }

    @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

@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));
    }

    @GetMapping("/{id}")
    public ResponseEntity<ProductResponse> get(@PathVariable Long id) {
        return ResponseEntity.ok(productService.findById(id));
    }

    @PostMapping
    @PreAuthorize("hasRole('ADMIN')")
    @ResponseStatus(HttpStatus.CREATED)
    public ProductResponse create(@Valid @RequestBody CreateProductRequest request) {
        return productService.create(request);
    }

    @PutMapping("/{id}")
    @PreAuthorize("hasRole('ADMIN')")
    public ProductResponse update(
            @PathVariable Long id,
            @Valid @RequestBody UpdateProductRequest request) {
        return productService.update(id, request);
    }
}

DTO with Validation

public record CreateProductRequest(
    @NotBlank @Size(min = 2, max = 255)
    String name,

    @NotNull @Positive
    BigDecimal price,

    @Positive
    Long categoryId,

    @Size(max = 5000)
    String description
) {}

public record ProductResponse(
    Long id,
    String name,
    String slug,
    BigDecimal price,
    CategoryDto category,
    Instant createdAt
) {
    public static ProductResponse from(Product p) {
        return new ProductResponse(
            p.getId(), p.getName(), p.getSlug(), p.getPrice(),
            p.getCategory() != null ? CategoryDto.from(p.getCategory()) : null,
            p.getCreatedAt()
        );
    }
}

Security

@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)
            .exceptionHandling(e -> e
                .authenticationEntryPoint((req, res, ex) ->
                    res.sendError(401, "Unauthorized"))
            )
            .build();
    }
}

Async and events

@Async
@TransactionalEventListener(phase = AFTER_COMMIT)
public void handleProductCreated(ProductCreatedEvent event) {
    notificationService.notifyAdmins("New product: " + event.product().getName());
    searchIndexer.index(event.product());
}

// Scheduled tasks
@Scheduled(cron = "0 0 3 * * *", zone = "Europe/Moscow")
public void cleanupExpiredSessions() {
    sessionRepository.deleteExpired(Instant.now().minus(Duration.ofDays(30)));
}

Caching

@Service
public class ProductService {

    @Cacheable(value = "products", key = "#id")
    public ProductResponse findById(Long id) {
        return productRepository.findById(id)
            .map(ProductResponse::from)
            .orElseThrow(() -> new EntityNotFoundException("Product " + id));
    }

    @CacheEvict(value = "products", key = "#id")
    @Transactional
    public void delete(Long id) {
        productRepository.deleteById(id);
    }
}

Actuator and monitoring

Spring Boot Actuator provides health checks, Micrometer metrics, Prometheus endpoints:

# application.yml
management:
  endpoints:
    web:
      exposure:
        include: health, metrics, prometheus, info
  endpoint:
    health:
      show-details: when-authorized
  metrics:
    export:
      prometheus:
        enabled: true

Development timeline

Spring Boot requires setup time but provides mature infrastructure:

  • Architecture + Spring scaffold — 1 week
  • Entities + JPA + Flyway migrations — 1 week
  • Controllers + Services + Security — 2–3 weeks
  • Tests (JUnit 5 + MockMvc + Testcontainers) — 1–2 weeks
  • Integrations + Kafka/RabbitMQ — 1–2 weeks

Mid-scale enterprise backend: 8–16 weeks. Spring Boot is the right choice when you need ecosystem maturity, strict typing, and a team with Java experience.