Ruby on Rails vs Spring Boot — How I Choose for a New Project
by Arif Ikhsanudin, Backend Developer
The decision nobody wants to make twice
Your CTO has handed you a greenfield service and a six-week runway to first deployment. The team has two senior engineers — one who has spent the last four years in Java microservices land, one who came up through Rails startups. You are about to have the framework argument. Skip it. Here is how I actually make this call.
What Rails gets right that Spring Boot does not
Rails wins on time-to-first-feature. Convention over configuration means a new resource — model, controller, serializer, migration — is four files and one generator command. There is no XML, no annotation soup, no context configuration class. Active Record makes join-heavy queries readable without a query builder library. For a domain that is mostly CRUD with some business logic layered on top, Rails will put you in production faster than Spring Boot by a factor of two or three in my experience.
The other thing Rails gets right is the default stack coherence. Action Mailer, Active Job, Action Cable, Turbo — these are not third-party integrations. They are first-class citizens with shared conventions. When your service needs background jobs, you reach for Sidekiq backed by Redis. The integration is documented, battle-tested, and works identically across every Rails app I have built.
# A typical Rails service object — no ceremony
class ProcessPayment
def initialize(order:, payment_method:)
@order = order
@payment_method = payment_method
end
def call
ActiveRecord::Base.transaction do
charge = @payment_method.charge(@order.total)
@order.update!(status: :paid, charge_id: charge.id)
OrderMailer.confirmation(@order).deliver_later
end
end
end
That is the whole thing. No dependency injection container. No interface declaration. No bean lifecycle.
What Spring Boot gets right that Rails does not
Concurrency is where Spring Boot earns its place. Rails is single-threaded per Puma worker by default. You can tune it, but the GIL (Global Interpreter Lock) in MRI Ruby means true parallelism requires multiple OS processes, not threads. A Spring Boot service running on the virtual thread model introduced in Java 21 (Project Loom) can handle tens of thousands of concurrent connections with a modest heap. If your service sits on a high-throughput messaging pipeline — say, 5,000 requests per second sustained — Rails will require significantly more horizontal scaling to match the same throughput.
Spring Boot also wins when the domain model is genuinely complex. Hibernate's type system, Spring Data's repository abstractions, and the Java type checker catch a category of bugs at compile time that Rails will only surface in production or in tests if you wrote them. When you have a payment processing service with 15 entity types, 40 business rules, and regulatory compliance requirements, that type safety is not pedantry — it is incident prevention.
@Service
@Transactional
public class PaymentService {
public PaymentResult process(Order order, PaymentMethod method) {
// The compiler tells you if order or method is nullable
// before this ships — Rails won't
var charge = method.charge(order.getTotal());
order.setStatus(OrderStatus.PAID);
order.setChargeId(charge.getId());
orderRepository.save(order);
eventPublisher.publish(new OrderPaidEvent(order.getId()));
return PaymentResult.success(charge);
}
}
The actual decision criteria
Choose Rails when:
- Your team has strong Ruby experience or is small and needs to move fast
- The domain is CRUD-heavy with moderate business logic
- You need to validate product-market fit and expect the schema to evolve rapidly
- You are building an internal tool, admin dashboard, or API that serves a single frontend
Choose Spring Boot when:
- You need sustained high throughput (>1,000 RPS per instance) or low-latency SLAs (< 50ms P99)
- The domain is complex, with strict type contracts between services
- Your team is Java/Kotlin-native and you cannot afford a ramp-up cost
- You are operating in a regulated industry where auditability and compile-time correctness matter
The cases where I have been wrong: I once pushed Rails on a team of three Java engineers because I estimated the delivery speed gain would outweigh the learning curve. It did not. The team wrote defensive, un-idiomatic Ruby that performed worse than equivalent Java would have, because they were fighting the language instead of using it. Team composition overrides everything else on this list.
What to do this week
If you are stuck in this decision right now: build the same endpoint in both frameworks with your actual team. Time it — not the runtime performance, the development time. Whichever takes fewer hours to produce a tested, deployed endpoint wins, because that ratio holds for the rest of the project. The performance gap between Rails and Spring Boot is real but rarely the bottleneck. The productivity gap between a team using its native stack versus a foreign one almost always is.