Spring Data JPA
JPA tutorial
Java persistence
Non-Spring Boot
Database integration

Spring Data JPA without Spring Boot

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

Spring Data JPA works perfectly well without Spring Boot, but you have to provide all of the wiring that Boot would normally auto-configure for you. That means creating the DataSource, the entity manager factory, the transaction manager, and repository scanning explicitly. The upside is more control over the stack, which is useful in legacy Spring applications and modular systems that do not use Boot.

What You Must Configure Yourself

A non-Boot Spring Data JPA setup usually needs these pieces:

  • JDBC DataSource
  • JPA vendor adapter, often Hibernate
  • 'EntityManagerFactory'
  • 'PlatformTransactionManager'
  • repository scanning with @EnableJpaRepositories

If any one of these is missing, the repositories may compile but fail at runtime.

Java Configuration Example

The core configuration can be expressed in a Java @Configuration class.

java
1package com.example.config;
2
3import java.util.Properties;
4import javax.sql.DataSource;
5import org.springframework.context.annotation.Bean;
6import org.springframework.context.annotation.ComponentScan;
7import org.springframework.context.annotation.Configuration;
8import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
9import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
10import org.springframework.jdbc.datasource.DriverManagerDataSource;
11import org.springframework.orm.jpa.JpaTransactionManager;
12import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
13import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
14import org.springframework.transaction.PlatformTransactionManager;
15import org.springframework.transaction.annotation.EnableTransactionManagement;
16
17@Configuration
18@EnableTransactionManagement
19@EnableJpaRepositories(basePackages = "com.example.repository")
20@ComponentScan(basePackages = "com.example")
21public class PersistenceConfig {
22
23    @Bean
24    public DataSource dataSource() {
25        DriverManagerDataSource ds = new DriverManagerDataSource();
26        ds.setDriverClassName("org.postgresql.Driver");
27        ds.setUrl("jdbc:postgresql://localhost:5432/appdb");
28        ds.setUsername("appuser");
29        ds.setPassword("secret");
30        return ds;
31    }
32
33    @Bean
34    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
35        LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
36        emf.setDataSource(dataSource());
37        emf.setPackagesToScan("com.example.model");
38        emf.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
39
40        Properties props = new Properties();
41        props.setProperty("hibernate.hbm2ddl.auto", "update");
42        props.setProperty("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
43        props.setProperty("hibernate.show_sql", "true");
44        emf.setJpaProperties(props);
45        return emf;
46    }
47
48    @Bean
49    public PlatformTransactionManager transactionManager() {
50        JpaTransactionManager tx = new JpaTransactionManager();
51        tx.setEntityManagerFactory(entityManagerFactory().getObject());
52        return tx;
53    }
54
55    @Bean
56    public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
57        return new PersistenceExceptionTranslationPostProcessor();
58    }
59}

This is the setup Boot would usually infer automatically.

Entity and Repository Look Familiar

Once the infrastructure exists, the entity and repository code looks almost the same as a Boot application.

java
1package com.example.model;
2
3import jakarta.persistence.Entity;
4import jakarta.persistence.GeneratedValue;
5import jakarta.persistence.GenerationType;
6import jakarta.persistence.Id;
7
8@Entity
9public class Book {
10    @Id
11    @GeneratedValue(strategy = GenerationType.IDENTITY)
12    private Long id;
13
14    private String title;
15
16    public Long getId() { return id; }
17    public String getTitle() { return title; }
18    public void setTitle(String title) { this.title = title; }
19}
java
1package com.example.repository;
2
3import com.example.model.Book;
4import org.springframework.data.jpa.repository.JpaRepository;
5
6public interface BookRepository extends JpaRepository<Book, Long> {
7}

The repository implementation is still generated by Spring Data at runtime.

Transactions Still Belong in the Service Layer

Repositories are not a substitute for clear transaction boundaries.

java
1package com.example.service;
2
3import com.example.model.Book;
4import com.example.repository.BookRepository;
5import org.springframework.stereotype.Service;
6import org.springframework.transaction.annotation.Transactional;
7
8@Service
9public class BookService {
10    private final BookRepository bookRepository;
11
12    public BookService(BookRepository bookRepository) {
13        this.bookRepository = bookRepository;
14    }
15
16    @Transactional
17    public Book create(String title) {
18        Book book = new Book();
19        book.setTitle(title);
20        return bookRepository.save(book);
21    }
22}

This is where write operations should usually be wrapped, not scattered across controllers or utility classes.

Version Consistency Matters

A common non-Boot problem is mixing incompatible generations of Spring, Hibernate, and JPA imports. For example, projects that use jakarta.persistence APIs need a compatible Spring and Hibernate stack. Older projects may still use javax.persistence.

The key rule is consistency across the whole dependency set.

Common Pitfalls

  • Forgetting @EnableJpaRepositories, which prevents repository beans from being created.
  • Configuring the entity manager factory but forgetting transaction management.
  • Mixing javax.persistence and jakarta.persistence imports in an incompatible stack.
  • Assuming Boot-style defaults exist when every key persistence bean must be wired manually.
  • Putting transactional logic in the wrong layer instead of defining clear service-level boundaries.

Summary

  • Spring Data JPA does not require Spring Boot, but it does require explicit infrastructure wiring.
  • The key pieces are the data source, entity manager factory, transaction manager, and repository scanning.
  • Entities and repositories look almost the same once the infrastructure is in place.
  • Service methods should still define transaction boundaries clearly.
  • Most non-Boot failures come from incomplete configuration or inconsistent dependency versions.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.