The backend engine powering my personal portfolio web application. Built with Java 17 and Spring Boot 3.5.5, this service exposes secure REST endpoints for managing my professional skills, projects, education and experiences.
Frontend Reference: Looking for the UI client? Check out the portfolio-ui repository.
Java 17 LTS, Spring Boot 3.5.5, MavenPostgreSQL (Hosted on a free cloud instance)Liquibase CoreSpring Security (OAuth2 Resource Server via JWT validation)springdoc-openapi v2.7.0 (Swagger UI available in development)shared-service (Internal dependency for re-usable domains, global exception handling, and custom validation groups)spring-boot-starter-actuator (for zero-downtime deployment health checks)ADR 1: PostgreSQL over MySQL
PostgreSQL as the primary database engine.PostgreSQL supports transactional DDL, meaning if a database migration (Liquibase) fails halfway through, the structural changes roll back cleanly. Furthermore, excellent free tiers are readily available for hosting small-scale PostgreSQL instances.ADR 2: Client-Generated Entity Keys (my_key via UUID)
UUID (my_key) instead of relying on auto-incrementing database primary keys for external references.ADR 3: Data Transfer Objects (DTO) Isolation via Java Records
HTTP requests and responses use Java 17 Records.ADR 4: Stateless Authentication & Targeted CSRF Disabling
STATELESS. And, CSRF protection is explicitely disabled (.csrf(csrf -> csrf.disable())).CSRF attacks. While the client app runs on Angular, administrative (mutative) requests are guarded by OAuth2 JWT tokens, while the rest of the endpoints remain completely public.ADR 5: OpenAPI & Swagger UI for Development
OpenAPI/Swagger UI endpoints exclusively during local development.Java Records, and quick frontend-to-backend integration without needing an external tool like Postman. These endpoints are strictly locked down or omitted in production profiles to avoid leaking API signatures.Entity Lifecycle Hooks (@PrePersist and @PreUpdate)
To ensure auditing fields remain clean and automated, entities utilize JPA lifestyle annotations rather than manual controller/service-level date mapping.
@PrePersist: Generates a random UUID for my_key and populates created_at with Instant.now().@PreUpdate: Automatically overrides updated_at with Instant.now().Standardized Domain Handling
Enums are enforced across models for strict categorization (e.g., SkillType for filtering skills by category).JpaRepository methods (e.g., findBySkillType(SkillType type)).@Slf4j annotation for cohesive console/file outputs.REST endpoint strictly standardizes execution outputs utilizing Spring's native ResponseEntity.status(...).body(...).Liquibase handles all schema initializations and schema upgrades prior to application runtime bootstrap.
Master Changelog (src/main/resources/db/changelog/changelog-master.xml)
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
<include file="db/changelog/changelog-v1.0.sql" />
</databaseChangeLog>
NOTE: The referenced .sql changelog files must contain precise Liquibase SQL format headers (e.g., --liquibase formatted sql) followed by distinct changeSet IDs (--changeset author:id), otherwise the migration parser will crash during compilation.
The system records console behaviors alongside rolling physical files partitioned on a monthly timeline.
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/portfolio_service.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/portfolio_service.%d{yyyy-MM}.log.gz</fileNamePattern>
<maxHistory>12</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</root>
</configuration>
CI/CD Flow: From Local Dev to ProductionPipeline Overview
When a commit lands on the render branch, the automated GitHub Actions workflow kicks off:
settings.xml on the runner to authorize access to the private shared-service repository using a GitHub secret token..jar file using Maven.latest and commit-sha) to Docker Hub.Render Webhook to notify the hosting instance to pull the updated image and restart.Local Compilation
To compile the package locally (bypassing unit validations if necessary):
mvn clean package -DskipTests
Production Resource Profile (application-prod.yaml)
Configured to seamlessly bind with the hosting infrastructure environment variables:
server:
port: ${PORT:8080}
address: 0.0.0.0
spring:
datasource:
url: jdbc:postgresql://${DB_HOST}:${DB_PORT}/${DB_NAME}
username: ${DB_USER}
password: ${DB_PASSWORD}
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: https://auth-server.com/.well-known/jwks.json
management:
endpoints:
web:
exposure:
include: health
endpoint:
health:
show-details: never #Keeps it lightweight and secure for production
NOTE: Make sure DB_HOST, DB_PORT, DB_NAME, DB_USER, and DB_PASSWORD are configured securely within Render’s dashboard environment variables.
Dockerization (Dockerfile)
FROM openjdk:25-ea-26-jdk-oraclelinux8
ADD target/portfolio-service.jar portfolio-service.jar
ENTRYPOINT ["java", "-jar", "/portfolio-service.jar"]
Automated Pipeline Blueprint (.github/workflows/deploy.yml)
name: Build, Push, Deploy
on:
push:
branches:
- render
jobs:
build-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout portfolio-service
uses: actions/checkout@v3
- name: Create Maven settings.xml
run: |
mkdir -p ~/.m2
cat > ~/.m2/settings.xml <<EOF
<settings>
<servers>
<server>
<id>github</id>
<username>Kranthi0307</username>
<password>${{ secrets.SHARED_SERVICE }}</password>
</server>
</servers>
</settings>
EOF
- name: Build portfolio-service
run: mvn clean package -DskipTests
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: |
docker.io/${{ secrets.DOCKER_USERNAME }}/portfolio-service:latest
docker.io/${{ secrets.DOCKER_USERNAME }}/portfolio-service:${{ github.sha }}
- name: Deploy to Render
run: |
curl -X POST \
-H "Accept: application/json" \
-H "Authorization: Bearer ${{ secrets.RENDER_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{}' \
https://api.render.com/v1/services/${{ secrets.RENDER_SERVICE_ID }}/deploys
Liquibase Migration Failures:
change set or application crashes instantly at boot.SQL changesets have valid headers (--liquibase formatted sql). If manual testing dirtied the metadata tracking schema, verify the status of the DATABASECHANGELOG tracking table directly.The CORS Blocked Errors (Development vs. Production):
CORS error.Security configuration, allow localhost:4200 under an explicit @Profile("dev") bean, while reserving production configuration exclusively for GitHub Pages deployment URL.Docker Registry Authentication Failures:
GitHub Action logs fail at the docker/login-action step.DOCKER_ACCESS_TOKEN is a Personal Access Token (PAT) generated from Docker Hub settings with write access, not the standard account password.Render Deployment Timeout / Port Binding Error:
Render logs state that the service failed to bind to a port within the allotted time.server.address is configured exactly to 0.0.0.0 in production (as defined in application-prod.yaml), allowing the container to receive external routing traffic on Render's assigned dynamic port.Dockerfile from an Early Access build (openjdk:25-ea-26-jdk-oraclelinux8) to a stable, long-term support release runtime matching compilation settings (e.g., eclipse-temurin:17-jre)..github/workflows/deploy.yml (e.g., updating checkout actions to @v4 and login/buildx actions to eliminate deprecation warnings).@RestController mapping to /health that returns ResponseEntity.status(HttpStatus.OK).body("Service is UP") to satisfy Render's zero-downtime health check requirements without pulling in the full spring-boot-starter-actuator library.server.shutdown: graceful to application-prod.yaml to allow the portfolio service to cleanly wrap up any active in-flight HTTP requests before Render tears down the old container instance during a deployment.