Wednesday, December 10, 2025

Spring Boot web app with MYSQL integration

 

1) Spring Boot (with REST & MySQL JPA)

Project type: Spring Boot (Maven).
Java version: 11+ recommended.

pom.xml (essential deps)

<project ...>
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>profitloss-service</artifactId>
  <version>1.0.0</version>

  <properties>
    <java.version>11</java.version>
    <spring.boot.version>3.1.0
</spring.boot.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot
</groupId>
      <artifactId>spring-boot-starter-web
</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot
</groupId>
      <artifactId>spring-boot-
starter-data-jpa</artifactId>
    </dependency>

    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-
java</artifactId>
      <scope>runtime</scope>
    </dependency>

    <dependency>
      <groupId>org.springframework.
boot</groupId>
      <artifactId>spring-boot-
starter-validation</artifactId>
    </dependency>
    
    <dependency>
      <groupId>com.fasterxml.
jackson.core</groupId>
      <artifactId>jackson-
databind</artifactId>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.
boot</groupId>
        <artifactId>spring-boot-
maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>

src/main/resources/application.properties

Replace username/password/dbname with your MySQL values.

spring.datasource.url=jdbc:mysql:
//localhost:3306/profitlossdb?useSSL=
false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=yourpassword

spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.
format_sql=true

server.port=8080

Domain: com.example.profitloss.model.Calculation.java

package com.example.profitloss.model;

import jakarta.persistence.*;
import java.time.Instant;

@Entity
public class Calculation {

    @Id
    @GeneratedValue(strategy =
 GenerationType.IDENTITY)
    private Long id;

    private double cp;
    private double sp;
    private double amount; // 
profit or loss magnitude
    private double percent;
    private boolean isProfit;
 // true -> profit, false -> 
loss (if equal, treat as neither)
    private Instant createdAt =
 Instant.now();

    // Constructors, getters, setters
    public Calculation() {}
    public Calculation(double cp,
 double sp, double amount, 
double percent, boolean isProfit) {
        this.cp = cp; this.sp = 
sp; this.amount = amount;
 this.percent = percent; this.isProfit = isProfit;
    }
    // getters and setters
 omitted for brevity (generate in IDE)
    // ...
    public Long getId(){return id;}
    public double getCp(){return cp;}
    public void setCp(double cp){this.cp=cp;}
    public double getSp(){return sp;}
    public void setSp(double sp){this.sp=sp;}
    public double getAmount(){return amount;}
    public void setAmount(double amt)
{this.amount=amt;}
    public double getPercent()
{return percent;}
    public void setPercent(double p)
{this.percent=p;}
    public boolean isProfit()
{return isProfit;}
    public void setProfit
(boolean profit){this.isProfit=profit;}
    public Instant getCreatedAt()
{return createdAt;}
    public void setCreatedAt
(Instant t){this.createdAt=t;}
}

Repository: CalculationRepository.java

package com.example.profitloss.repo;

import com.example.profitloss.
model.Calculation;
import org.springframework.data.
jpa.repository.JpaRepository;

public interface CalculationRepository 
extends JpaRepository<Calculation, Long> {}

DTO: CalculateRequest.java

package com.example.profitloss.dto;

import jakarta.validation.constraints.Min;

public class CalculateRequest {
    @Min(value = 0, message = 
"Cost price must be >= 0")
    private double cp;

    @Min(value = 0, message =
 "Selling price must be >= 0")
    private double sp;

    public double getCp(){return cp;}
    public void setCp(double cp){this.cp=cp;}
    public double getSp(){return sp;}
    public void setSp(double sp){this.sp=sp;}
}

Response DTO: CalculateResponse.java

package com.example.profitloss.dto;

public class CalculateResponse {
    private String type; // "profit",
"loss","no-profit-no-loss"
    private double amount;
    private double percent;
    private long id; // persisted 
record id (if saved)

    public CalculateResponse
(String type, double amount, 
double percent, long id){
        this.type = type; 
this.amount = amount; this.percent = 
percent; this.id = id;
    }
    // getters
    public String getType(){return type;}
    public double getAmount(){return amount;}
    public double getPercent(){return percent;}
    public long getId(){return id;}
}

Service: CalculationService.java

package com.example.profitloss.service;

import com.example.profitloss.
dto.CalculateRequest;
import com.example.profitloss.
model.Calculation;
import com.example.profitloss.
repo.CalculationRepository;
import org.springframework.
stereotype.Service;

@Service
public class CalculationService {

    private final CalculationRepository repo;

    public CalculationService
(CalculationRepository repo){
        this.repo = repo;
    }

    public Calculation calculateAndSave
(double cp, double sp){
        if (cp == sp) {
            Calculation calc = new 
Calculation(cp, sp, 0.0, 0.0, false);
            return repo.save(calc);
        }
        boolean isProfit = sp > cp;
        double amount = Math.abs(sp - cp);
        double percent = (amount / cp) * 100;
        Calculation calc = new 
Calculation(cp, sp, amount, percent, isProfit);
        return repo.save(calc);
    }
}

Controller (REST): CalculationController.java

package com.example.profitloss.controller;

import com.example.profitloss.
dto.CalculateRequest;
import com.example.profitloss.
dto.CalculateResponse;
import com.example.profitloss.
model.Calculation;
import com.example.profitloss.
service.CalculationService;
import jakarta.validation.Valid;
import org.springframework.http.
ResponseEntity;
import org.springframework.web.
bind.annotation.*;

@RestController
@RequestMapping("/api")
public class CalculationController {

    private final CalculationService service;

    public CalculationController
(CalculationService service){
        this.service = service;
    }

    // POST /api/calculate -> saves 
to DB and returns result
    @PostMapping("/calculate")
    public ResponseEntity<CalculateResponse>
 calculateAndSave(@Valid @RequestBody
 CalculateRequest req) {
        Calculation saved = service.
calculateAndSave(req.getCp(), req.getSp());
        String type;
        if (saved.getAmount() == 0.0) 
type = "no-profit-no-loss";
        else type = saved.isProfit() ?
 "profit" : "loss";
        CalculateResponse resp = new
 CalculateResponse(type, saved.getAmount(), 
saved.getPercent(), saved.getId());
        return ResponseEntity.ok(resp);
    }

    // GET /api/calculation/{id} -> 
fetch saved calculation
    @GetMapping("/calculation/{id}")
    public ResponseEntity<Calculation>
 getById(@PathVariable Long id) {
        return service.repo.findById(id)
                .map(ResponseEntity::ok)
                .orElse(ResponseEntity.
notFound().build());
    }
}

Note: For brevity service.repo was used in controller GET. In production extract via service method.

Application main

package com.example.profitloss;

import org.springframework.boot.
SpringApplication;
import org.springframework.boot.
autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ProfitlossServiceApplication {
    public static void main(String[] args){
        SpringApplication.run
(ProfitlossServiceApplication.class, args);
    }
}

How to run

  1. Start MySQL and create DB profitlossdb (or let JPA create; ensure user has privileges).
    Example:
    CREATE DATABASE profitlossdb;
    
  2. Update application.properties credentials.
  3. mvn spring-boot:run or build jar.

Example REST call (curl)

curl -X POST http://localhost:
8080/api/calculate \
 -H "Content-Type: application/json" \
 -d '{"cp":500,"sp":650}'

Response:

{"type":"profit","amount":
150.0,"percent":30.0,"id":1}

2) MySQL Integration (already included above)

  • JPA entity Calculation persists each calculation.
  • spring.jpa.hibernate.ddl-auto=update will create the table automatically.
  • You can query results via JPA repository or exposed REST endpoint.

If you want explicit SQL schema:

CREATE TABLE calculation (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  cp DOUBLE,
  sp DOUBLE,
  amount DOUBLE,
  percent DOUBLE,
  is_profit BOOLEAN,
  created_at TIMESTAMP DEFAULT
 CURRENT_TIMESTAMP
);

3) REST API (standalone description)

The Spring Boot app provides:

  • POST /api/calculate — accepts JSON { "cp": <number>, "sp": <number> } and returns { type, amount, percent, id }. Saves to DB.
  • GET /api/calculation/{id} — fetch saved record.

Validation: @Min(0) on input; add more error handling as needed.

4) Kotlin Android App (Android Studio)

Minimum: Android Gradle Plugin + Kotlin. Create new project (Empty Activity) and replace files.

res/layout/activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android=
"http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:padding="24dp"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <EditText
        android:id="@+id/etCP"
        android:hint="Cost Price (CP)"
        android:inputType="numberDecimal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <EditText
        android:id="@+id/etSP"
        android:hint="Selling Price (SP)"
        android:inputType="numberDecimal"
        android:layout_marginTop="12dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/btnCalculate"
        android:text="Calculate"
        android:layout_marginTop="16dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/tvResult"
        android:textSize="18sp"
        android:layout_marginTop="18dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

MainActivity.kt

package com.example.profitlossapp

import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {

    private lateinit var etCP: EditText
    private lateinit var etSP: EditText
    private lateinit var btnCalculate: Button
    private lateinit var tvResult: TextView

    override fun onCreate(savedInstanceState:
 Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        etCP = findViewById(R.id.etCP)
        etSP = findViewById(R.id.etSP)
        btnCalculate = findViewById
(R.id.btnCalculate)
        tvResult = findViewById(R.id.tvResult)

        btnCalculate.setOnClickListener {
            val cpText = etCP.text.
toString().trim()
            val spText = etSP.text.
toString().trim()
            if (cpText.isEmpty() || 
spText.isEmpty()) {
                tvResult.text = 
"Please enter both CP and SP."
                return@setOnClickListener
            }

            val cp = cpText.toDoubleOrNull()
            val sp = spText.toDoubleOrNull()
            if (cp == null || sp == null) {
                tvResult.text = 
"Invalid numbers."
                return@setOnClickListener
            }
            if (cp == 0.0) {
                tvResult.text = 
"CP cannot be zero for percentage calculation."
                return@setOnClickListener
            }

            when {
                sp > cp -> {
                    val profit = sp - cp
                    val percent = 
profit / cp * 100
                    tvResult.text =
 "Profit: %.2f\nProfit %%: %.2f".
format(profit, percent)
                }
                cp > sp -> {
                    val loss = cp - sp
                    val percent = 
loss / cp * 100
                    tvResult.text = 
"Loss: %.2f\nLoss %%: %.2f".format
(loss, percent)
                }
                else -> {
                    tvResult.text = 
"No Profit, No Loss."
                }
            }
        }
    }
}

Optional: Call Spring Boot REST from Android

  • Add Retrofit or OkHttp to build.gradle and POST to /api/calculate to persist results remotely.

Final notes & next steps

  • The Spring Boot project already includes REST endpoints and MySQL JPA integration — that covers items (1)-(3).
  • The Kotlin Android project is a simple UI app; it can be extended to call the REST API to persist calculations remotely.

Docker plus Docker Compose for Spring Boot app

 


Docker + Docker Compose for Spring Boot app

Kubernetes manifests (Deployment, Service, HorizontalPodAutoscaler, ConfigMap, Secret)

GitHub Actions CI/CD workflow (build, test, build Docker image, push to registry)

Android app updated to MVVM (Kotlin) with Retrofit integration and simple persistence

placeholders is the place where you must insert credentials (Docker registry, secrets). Copy/paste into your projects and adjust names/credentials.

Docker (Spring Boot)

Dockerfile

# Use a multi-stage build for a Spring 
Boot jar
FROM eclipse-temurin:17-jdk-jammy AS build
WORKDIR /app
COPY pom.xml mvnw ./
COPY .mvn .mvn
COPY src src
RUN ./mvnw -B -DskipTests package

FROM eclipse-temurin:17-jre-jammy
ARG JAR_FILE=target/*.jar
COPY --from=build /app/${JAR_FILE} 
/app/app.jar
EXPOSE 8080
ENTRYPOINT ["java","-Xms256m","
-Xmx512m","-jar","/app/app.jar"]

.dockerignore

target/
.vscode/
.idea/
*.iml
.mvn/wrapper/maven-wrapper.jar

docker-compose.yml (app + mysql)

version: "3.8"
services:
  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: examplepassword
      MYSQL_DATABASE: profitlossdb
    ports:
      - "3306:3306"
    volumes:
      - mysql-data:/var/lib/mysql

  profitloss-app:
    build: .
    environment:
      SPRING_DATASOURCE_URL: jdbc:mysql
://mysql:3306/profitlossdb?useSSL=
false&serverTimezone=UTC
      SPRING_DATASOURCE_USERNAME: root
      SPRING_DATASOURCE_PASSWORD: 
examplepassword
    depends_on:
      - mysql
    ports:
      - "8080:8080"

volumes:
  mysql-data:

Kubernetes manifests

Assume namespace profitloss. Secrets contain DB password and Docker registry creds — replace placeholders.

k8s/namespace.yaml

apiVersion: v1
kind: Namespace
metadata:
  name: profitloss

k8s/secret.yaml (DB password & optional registry)

apiVersion: v1
kind: Secret
metadata:
  name: profitloss-secret
  namespace: profitloss
type: Opaque
stringData:
  DB_PASSWORD: "examplepassword"        
   # replace
  REGISTRY_USERNAME: "your-registry-user" 
# replace if pushing private image
  REGISTRY_PASSWORD: "registry-pass"      
# replace

k8s/configmap.yaml (app config)

apiVersion: v1
kind: ConfigMap
metadata:
  name: profitloss-config
  namespace: profitloss
data:
  SPRING_DATASOURCE_URL: jdbc:mysql:
//profitloss-mysql:3306/profitlossdb?
useSSL=false&serverTimezone=UTC
  SPRING_DATASOURCE_USERNAME: root
  SPRING_JPA_HIBERNATE_DDL_AUTO: update

k8s/mysql-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: profitloss-mysql
  namespace: profitloss
spec:
  replicas: 1
  selector:
    matchLabels:
      app: profitloss-mysql
  template:
    metadata:
      labels:
        app: profitloss-mysql
    spec:
      containers:
        - name: mysql
          image: mysql:8.0
          env:
            - name: MYSQL_ROOT_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: profitloss-secret
                  key: DB_PASSWORD
            - name: MYSQL_DATABASE
              value: profitlossdb
          ports:
            - containerPort: 3306
          volumeMounts:
            - name: mysql-data
              mountPath: /var/lib/mysql
      volumes:
        - name: mysql-data
          persistentVolumeClaim:
            claimName: mysql-pvc
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pvc
  namespace: profitloss
spec:
  accessModes: ["ReadWriteOnce"]
  resources:
    requests:
      storage: 5Gi

k8s/app-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: profitloss-app
  namespace: profitloss
spec:
  replicas: 2
  selector:
    matchLabels:
      app: profitloss-app
  template:
    metadata:
      labels:
        app: profitloss-app
    spec:
      containers:
        - name: profitloss-app
          image: your-registry/profitloss
-service:latest   # replace with built image
          imagePullPolicy: IfNotPresent
          env:
            - name: SPRING_DATASOURCE_URL
              valueFrom:
                configMapKeyRef:
                  name: profitloss-config
                  key: SPRING_DATASOURCE_URL
            - name: SPRING_DATASOURCE_USERNAME
              valueFrom:
                configMapKeyRef:
                  name: profitloss-config
                  key:
 SPRING_DATASOURCE_USERNAME
            - name: SPRING_DATASOURCE_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: profitloss-secret
                  key: DB_PASSWORD
          ports:
            - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: profitloss-service
  namespace: profitloss
spec:
  selector:
    app: profitloss-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080
  type: ClusterIP

k8s/hpa.yaml (autoscale)

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: profitloss-hpa
  namespace: profitloss
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: profitloss-app
  minReplicas: 2
  maxReplicas: 6
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 60

Optional: add Ingress manifest depending on your cluster.

GitHub Actions — CI/CD

This workflow:

  • Runs tests
  • Builds jar
  • Builds Docker image
  • Logs into Docker registry (Docker Hub/GitHub Packages)
  • Pushes image
  • Optionally applies Kubernetes manifests via kubectl (if you configure KUBE_CONFIG)

Create .github/workflows/ci-cd.yml:

name: CI/CD Pipeline

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

env:
  IMAGE_NAME: your-registry/profitloss-service

jobs:
  build-test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: 17

      - name: Build and run tests
        run: ./mvnw -B clean verify

  build-and-push:
    needs: build-test
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v2

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2

      - name: Login to registry
        uses: docker/login-action@v2
        with:
          registry: docker.io
          username: ${{ secrets.
DOCKERHUB_USERNAME }}   # set in repo secrets
          password: ${{ secrets.
DOCKERHUB_TOKEN }}

      - name: Build and push
        uses: docker/build-push-action@v4
        with:
          context: .
          push: true
          tags: ${{ env.IMAGE_NAME }}:latest

  deploy-k8s:
    needs: build-and-push
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup kubectl
        uses: azure/setup-kubectl@v3
        with:
          version: 'latest'

      - name: Configure kubectl
        run: |
          echo "${{ secrets.KUBE_CONFIG }}" 
> kubeconfig
          export KUBECONFIG=$PWD/kubeconfig

      - name: Update image in deployment
 (kubectl set image)
        run: |
          kubectl -n profitloss set 
image deployment/profitloss-app 
profitloss-app=${{ env.IMAGE_NAME }}
:latest || true
          kubectl -n profitloss
 rollout status deployment/
profitloss-app --timeout=120s || true

Secrets to add in repo settings

  • DOCKERHUB_USERNAME, DOCKERHUB_TOKEN (or GitHub Packages token)
  • KUBE_CONFIG — base64 encoded kubeconfig or raw kubeconfig contents (use caution)

Android — MVVM architecture (Kotlin)

I’ll provide a minimal, clean MVVM structure with Retrofit + LiveData + ViewModel + Repository.

Gradle dependencies (app/build.gradle)

plugins {
    id 'com.android.application'
    id 'kotlin-android'
}

android {
    compileSdk 34
    defaultConfig {
        applicationId "com.example.
profitlossapp"
        minSdk 21
        targetSdk 34
        versionCode 1
        versionName "1.0"
    }
}

dependencies {
    implementation "org.jetbrains.
kotlin:kotlin-stdlib:1.9.0"
    implementation 'androidx.core:
core-ktx:1.12.0'
    implementation 'androidx.
appcompat:appcompat:1.6.1'
    implementation 'com.google.
android.material:material:1.9.0'
    implementation 'androidx.
lifecycle:lifecycle-livedata-ktx:2.6.2'
    implementation 'androidx.
lifecycle:lifecycle-viewmodel-ktx:2.6.2'
    implementation 'com.
squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.
squareup.retrofit2:converter-gson:2.9.0'
    implementation 
'org.jetbrains.kotlinx:
kotlinx-coroutines-android:1.7.3'
}

Network layer (Retrofit)

network/ProfitLossApi.kt

package com.example.profitlossapp.network

import com.example.
profitlossapp.model.CalculateRequest
import com.example.
profitlossapp.model.CalculateResponse
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.POST

interface ProfitLossApi {
    @POST("/api/calculate")
    suspend fun calculate
(@Body req: CalculateRequest): 
Response<CalculateResponse>
}

network/RetrofitClient.kt

package com.example.profitlossapp.network

import retrofit2.Retrofit
import retrofit2.converter.
gson.GsonConverterFactory

object RetrofitClient {
    private const val BASE_URL = 
"http://10.0.2.2:8080" // emulator -> host

    val api: ProfitLossApi by lazy {
        Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory
(GsonConverterFactory.create())
            .build()
            .create
(ProfitLossApi::class.java)
    }
}

Models

model/CalculateRequest.kt

package com.example.profitlossapp.model
data class CalculateRequest(val cp: 
Double, val sp: Double)

model/CalculateResponse.kt

package com.example.profitlossapp.model
data class CalculateResponse(val type:
 String, val amount: Double, 
val percent: Double, val id: Long)

Repository

repository/ProfitLossRepository.kt

package com.example.profitlossapp.repository

import com.example.profitlossapp.
model.CalculateRequest
import com.example.profitlossapp.
model.CalculateResponse
import com.example.profitlossapp.
network.RetrofitClient
import retrofit2.Response

class ProfitLossRepository {
    suspend fun calculate(req: 
CalculateRequest): Response<CalculateResponse> {
        return RetrofitClient.
api.calculate(req)
    }
}

ViewModel

ui/MainViewModel.kt

package com.example.profitlossapp.ui

import androidx.lifecycle.*
import com.example.profitlossapp.
model.CalculateRequest
import com.example.profitlossapp.
model.CalculateResponse
import com.example.profitlossapp.
repository.ProfitLossRepository
import kotlinx.coroutines.launch

class MainViewModel : ViewModel() {

    private val repo = ProfitLossRepository()

    private val _result = 
MutableLiveData<CalculateResponse?>()
    val result: 
LiveData<CalculateResponse?> = _result

    private val _error = 
MutableLiveData<String?>()
    val error: LiveData<String?> = _error

    fun calculate(cp: Double, sp: Double) {
        viewModelScope.launch {
            try {
                val response =
 repo.calculate(CalculateRequest(cp, sp))
                if (response.isSuccessful) {
                    _result.value = 
response.body()
                    _error.value = null
                } else {
                    _error.value =
 "Server error: ${response.code()}"
                }
            } catch (e: Exception) {
                _error.value = e.message
            }
        }
    }
}

UI (Activity)

ui/MainActivity.kt

package com.example.profitlossapp.ui

import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import android.widget.TextView
import androidx.activity.viewModels
import androidx.appcompat.
app.AppCompatActivity
import com.example.profitlossapp.R

class MainActivity : AppCompatActivity() {
    private val vm:
 MainViewModel by viewModels()

    override fun 
onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val etCP =
 findViewById<EditText>(R.id.etCP)
        val etSP = 
findViewById<EditText>(R.id.etSP)
        val btn =
 findViewById<Button>(R.id.btnCalculate)
        val tv =
 findViewById<TextView>(R.id.tvResult)

        btn.setOnClickListener {
            val cp =
 etCP.text.toString().toDoubleOrNull()
            val sp =
 etSP.text.toString().toDoubleOrNull()
            if (cp == 
null || sp == null) {
                tv.text = "Enter valid numbers"
                return@setOnClickListener
            }
            vm.calculate(cp, sp)
        }

        vm.result.observe(this) { resp ->
            resp?.let {
                tv.text = 
"Type: ${it.type}\nAmount:
 ${it.amount}\nPercent:
 ${it.percent}\nID: ${it.id}"
            }
        }

        vm.error.observe(this) { err ->
            err?.let { tv.text = "Error: $it" }
        }
    }
}

Layout (res/layout/activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android=
"http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:padding="24dp"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <EditText android:id="@+id/etCP" 
android:hint="Cost Price" 
android:inputType="numberDecimal" 
android:layout_width="match_parent" 
android:layout_height="wrap_content"/>
    <EditText android:id="@+id/etSP"
 android:hint="Selling Price" 
android:inputType="numberDecimal"
 android:layout_marginTop="12dp"
 android:layout_width="match_parent" 
android:layout_height="wrap_content"/>
    <Button android:id="@+id/btnCalculate" 
android:text="Calculate" 
android:layout_marginTop="16dp" 
android:layout_width="match_parent"
 android:layout_height="wrap_content"/>
    <TextView android:id="@+id/tvResult" 
android:textSize="16sp" 
android:layout_marginTop="18dp" 
android:layout_width="wrap_content" 
android:layout_height="wrap_content"/>
</LinearLayout>

Quick instructions & notes

  • Docker: docker build -t your-registry/profitloss-service:latest . then docker push ... (or use the GitHub Actions workflow).
  • Docker Compose: docker compose up --build
  • Kubernetes: kubectl apply -f k8s/namespace.yaml && kubectl apply -f k8s/secret.yaml && kubectl apply -f k8s/configmap.yaml && kubectl apply -f k8s/mysql-deployment.yaml && kubectl apply -f k8s/app-deployment.yaml && kubectl apply -f k8s/hpa.yaml
  • GitHub Actions: set repository secrets (DOCKERHUB_USERNAME, DOCKERHUB_TOKEN, KUBE_CONFIG).
  • Android MVVM: run emulator and ensure Spring Boot is reachable — use 10.0.2.2 for the emulator or your machine IP for a real device. Allow CORS on Spring Boot if calling from other origins (add @CrossOrigin to controller or CorsFilter).

Class-Based Java Program (Object-Oriented)

 

Class-Based Java Program (Object-Oriented)



1. Class-Based Java Program (Object-Oriented)

class ProfitLoss {

    private double cp;
    private double sp;

    public ProfitLoss(double cp, double sp) {
        this.cp = cp;
        this.sp = sp;
    }

    public void calculate() {
        if (sp > cp) {
            double profit = sp - cp;
            double profitPercent = 
(profit / cp) * 100;
            System.out.println(
"Profit: " + profit);
            System.out.println(
"Profit Percentage: " + profitPercent + "%");

        } else if (cp > sp) {
            double loss = cp - sp;
            double lossPercent = 
(loss / cp) * 100;
            System.out.println
("Loss: " + loss);
            System.out.println
("Loss Percentage: " + lossPercent + "%");

        } else {
            System.out.println
("No Profit, No Loss.");
        }
    }
}

public class ProfitLossMain {
    public static void main(String[] args) {
        ProfitLoss obj = 
new ProfitLoss(500, 650);
        obj.calculate();
    }
}

2. Menu-Driven Console Version

(Choose 1 for profit/loss, 2 to exit)

import java.util.Scanner;

public class ProfitLossMenu {
    public static void main(String[] args) {

        Scanner sc = new Scanner(System.in);
        int choice;

        do {
            System.out.println("\n
===== PROFIT & LOSS CALCULATOR =====");
            System.out.println("1.
 Calculate Profit / Loss");
            System.out.println("2. Exit");
            System.out.print("Enter
 your choice: ");
            choice = sc.nextInt();

            switch (choice) {

                case 1:
                    System.out.print
("Enter Cost Price (CP): ");
                    double cp = 
sc.nextDouble();

                    System.out.print
("Enter Selling Price (SP): ");
                    double sp = 
sc.nextDouble();

                    if (sp > cp) {
                        double profit = 
sp - cp;
                        double profitPercent
 = (profit / cp) * 100;
                        System.out.println
("Profit: " + profit);
                        System.out.println
("Profit Percentage: " + profitPercent + "%");

                    } else if (cp > sp) {
                        double loss = cp - sp;
                        double lossPercent 
= (loss / cp) * 100;
                        System.out.println
("Loss: " + loss);
                        System.out.println
("Loss Percentage: " + lossPercent + "%");

                    } else {
                        System.out.println
("No Profit, No Loss.");
                    }
                    break;

                case 2:
                    System.out.println
("Exiting...");
                    break;

                default:
                    System.out.println
("Invalid Choice! Try again.");
            }

        } while (choice != 2);

        sc.close();
    }
}

3. GUI Version (Java Swing)

This version shows a small graphical window for input and output.

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class ProfitLossGUI extends JFrame {

    private JTextField cpField, spField;
    private JButton btnCalculate;
    private JLabel resultLabel;

    public ProfitLossGUI() {
        setTitle("Profit & Loss Calculator");
        setSize(350, 250);
        setDefaultCloseOperation
(JFrame.EXIT_ON_CLOSE);
        setLayout(new GridLayout(4, 2));

        add(new JLabel("Cost Price (CP): "));
        cpField = new JTextField();
        add(cpField);

        add(new JLabel("Selling Price (SP): "));
        spField = new JTextField();
        add(spField);

        btnCalculate = new JButton
("Calculate");
        add(btnCalculate);

        resultLabel = new JLabel("");
        add(resultLabel);

        btnCalculate.
addActionListener(new ActionListener() {
            public void
 actionPerformed(ActionEvent e) {
                double cp = 
Double.parseDouble(cpField.getText());
                double sp = 
Double.parseDouble(spField.getText());

                if (sp > cp) {
                    double profit = sp - cp;
                    double profitPer
 = (profit / cp) * 100;
                    resultLabel.setText
("Profit: " + profit + " 
| " + profitPer + "%");

                } else if (cp > sp) {
                    double loss = cp - sp;
                    double lossPer = 
(loss / cp) * 100;
                    resultLabel.
setText("Loss: " + loss + " | " 
+ lossPer + "%");

                } else {
                    resultLabel.
setText("No Profit, No Loss.");
                }
            }
        });

        setVisible(true);
    }

    public static void main(String[] args) {
        new ProfitLossGUI();
    }
}

🎉 All Versions Completed

You now have: ✔ Simple Java Program
✔ OOP/Class-based Program
✔ Menu-driven Console Program
✔ GUI Program using Swing

How to Build Your Own VPN (WireGuard)

 


Free Guide: How to Build Your Own VPN (WireGuard)

Free Guide: How to Build Your Own VPN (WireGuard)


This expanded tutorial combines:



✔ Step-by-step VPS provider guides (DigitalOcean, AWS, Linode, Vultr,

 Hostinger)
✔ Multiple client configuration files
✔ Automation scripts
✔ Code samples
✔ Security best practices
✔ Troubleshooting

1. Introduction – What Is a VPN and Why Build Your Own?

A Virtual Private Network (VPN) creates an encrypted tunnel between a device and a remote server. When connected, all internet traffic travels through this tunnel, hiding your real IP address and protecting your data from snooping, surveillance, and insecure networks.

Most users rely on commercial VPN apps, but building your own VPN has several advantages:

  • No third-party logging risks — only you control traffic
  • Faster speeds because of zero user-sharing congestion
  • Fixed static IP for remote development, SSH access, or hosting
  • Learning experience in networking, tunneling, encryption, and Linux

This guide will use WireGuard, a modern VPN protocol designed for simplicity and high performance.

2. Why WireGuard?

WireGuard has become popular because:

  • It uses state-of-the-art cryptography
  • It is extremely lightweight
  • The configuration is simple and readable
  • It provides better speed and stability than OpenVPN/IPSec

A typical WireGuard configuration consists of:

  • A server with a public IP
  • One or more clients (Windows, Android, iOS, Linux, macOS)
  • A pair of keys (private/public) for each device

3. System Requirements

You need:

  • A VPS (any provider) with a public IPv4
  • Ubuntu 22.04 or Debian 12 (recommended)
  • Root or sudo privileges
  • WireGuard client apps for your devices

4. Installing WireGuard on Linux Server

Update packages

sudo apt update && sudo apt upgrade -y

Install WireGuard

sudo apt install -y wireguard 
iptables-persistent

A network interface named wg0 will be created later in configuration.

5. Generating Server & Client Keys

Create a secure folder:

sudo mkdir -p /etc/wireguard/keys
sudo chmod 700 /etc/wireguard/keys
cd /etc/wireguard/keys

Generate server keys

wg genkey | tee server_
private.key | wg pubkey > server_public.key

Generate one client key (client1)

wg genkey | tee client1_private.key 
| wg pubkey > client1_public.key

6. Creating the WireGuard Server Configuration

Create:

sudo nano /etc/wireguard/wg0.conf

Paste:

[Interface]
PrivateKey = SERVER_PRIVATE_KEY
Address = 10.10.0.1/24
ListenPort = 51820
SaveConfig = true

[Peer]
PublicKey = CLIENT1_PUBLIC_KEY
AllowedIPs = 10.10.0.2/32

Replace:

  • SERVER_PRIVATE_KEY → content of server_private.key
  • CLIENT1_PUBLIC_KEY → content of client1_public.key

Secure:

sudo chmod 600 /etc/wireguard/wg0.conf

7. Enable IP Forwarding & NAT

Temporary forwarding

sudo sysctl -w net.ipv4.ip_forward=1

Permanent forwarding

echo "net.ipv4.ip_forward=1" | 
sudo tee -a /etc/sysctl.conf
sudo sysctl -p

Enable NAT

Assume your public interface is eth0:

sudo iptables -t nat -A POSTROUTING
 -o eth0 -j MASQUERADE
sudo iptables -A FORWARD -i wg0 -j ACCEPT
sudo iptables -A FORWARD -o wg0 -j ACCEPT
sudo netfilter-persistent save

8. Start the VPN Server

sudo systemctl enable wg-quick@wg0
sudo systemctl start wg-quick@wg0

Check status:

sudo wg show

9. Creating the Client Configuration File

Create client1.conf:

[Interface]
PrivateKey = CLIENT1_PRIVATE_KEY
Address = 10.10.0.2/24
DNS = 1.1.1.1

[Peer]
PublicKey = SERVER_PUBLIC_KEY
Endpoint = YOUR.SERVER.IP:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25

Use the WireGuard app (Windows, macOS, Android, iOS) to import the .conf.

10. Automation Script: Create New Clients Easily

Create:

sudo nano /usr/local/bin/wg-add-client

Paste:

#!/bin/bash

NAME=$1
WG_DIR="/etc/wireguard"
KEY_DIR="$WG_DIR/keys"

wg genkey | tee $KEY_DIR
/${NAME}_private.key 
| wg pubkey > $KEY_DIR
/${NAME}_public.key

SERVER_PUB=$(cat $KEY_DIR
/server_public.key)
CLIENT_PRIV=$(cat $KEY_DIR
/${NAME}_private.key)
CLIENT_PUB=$(cat $KEY_DIR
/${NAME}_public.key)

cat <<EOF > $WG_DIR/${NAME}.conf
[Interface]
PrivateKey = ${CLIENT_PRIV}
Address = 10.10.0.10/24
DNS = 1.1.1.1

[Peer]
PublicKey = ${SERVER_PUB}
Endpoint = YOUR.SERVER.IP:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25
EOF

wg set wg0 peer ${CLIENT_PUB}
 allowed-ips 
10.10.0.10/32
echo "Client $NAME created."

Make executable:

sudo chmod +x /usr/local/bin/wg-add-client

Use:

sudo wg-add-client client2

11. Provider-Specific Setup Guides

DigitalOcean

  1. Create a Droplet → Ubuntu 22.04
  2. Allow UDP 51820 under firewall
  3. Follow this guide normally
  4. Use Droplet public IP in Endpoint=

AWS EC2

  1. Launch Ubuntu EC2 instance
  2. In Security Groups, allow UDP 51820
  3. Disable Source/Dest Check (for NAT)
  4. Add an Elastic IP (optional)

Linode / Akamai

  1. Create Ubuntu Linode
  2. Check "Firewall" → allow UDP 51820
  3. Configure NAT rules

Vultr

  1. Deploy Ubuntu 22.04
  2. Under "Firewall" allow UDP 51820
  3. Add NAT

Hostinger VPS

  1. Create VPS instance
  2. Use panel to enable "Open Ports"
  3. Install WireGuard normally

12. Client Setup Instructions

Windows

  1. Install WireGuard from the official site
  2. Click → Add Tunnel → Import From File
  3. Select client1.conf

macOS

  1. Install WireGuard from App Store
  2. Import config

Linux

sudo wg-quick up client1

Android / iOS

  1. Install app from Play Store/App Store
  2. Tap "+" → Import from file or QR code

13. Testing Your VPN

Ping

ping 10.10.0.1

Check IP

curl ifconfig.me

You should now appear from your server’s IP.

14. Security Best Practices

  • Rotate keys every few months
  • Disable unused clients
  • Limit AllowedIPs for internal devices
  • Use strong DNS (Cloudflare / Quad9)
  • Keep the OS updated

15. Troubleshooting

No handshake?

  • UDP 51820 blocked
  • Wrong public/private keys
  • NAT rules missing

No internet inside VPN?

  • Missing MASQUERADE rule
  • Incorrect interface name (eth0 vs ens3)

Mobile clients disconnecting?

Set:

PersistentKeepalive = 25

16. Conclusion

Congratulations — you now have a fully working, secure, self-hosted VPN using WireGuard. You can add unlimited devices, automate client creation, or expand your VPN into a multi-region network with multiple servers.

Monday, December 8, 2025

The Definitive Guide to Bootstrap CSS Buttons Reference: Styling, Sizing, and State Management

 

The Definitive Guide to Bootstrap CSS Buttons Reference: Styling, Sizing, and State Management

Bootstrap CSS buttons reference unlocks quick builds.



Buttons drive user actions in web apps. They let people click to submit forms, navigate pages, or trigger events. A poor button design can confuse users and hurt your site's flow. Bootstrap changes that with simple classes that make buttons look sharp and work well across devices.

You know how frustrating it is when buttons clash with your site's style? Bootstrap fixes this fast. Its utility-first system lets you style buttons without writing custom CSS from scratch. This guide covers everything from basic classes to advanced tweaks. By the end, you'll style Bootstrap CSS buttons like a pro, ensuring smooth user interactions and a clean look.

Understanding the Foundation: Basic Bootstrap Button Classes

Bootstrap buttons start simple. You take a plain HTML element like <button> or <a>. Add a few classes, and it transforms into something polished.

The Primary Button Component Class: btn

The .btn class is your starting point. It adds padding, borders, and font tweaks to make elements feel like real buttons. Without it, your button looks flat and boring.

In Bootstrap 4 and 5, .btn works on buttons, links, or inputs. It sets a default border radius for that rounded edge. You can't skip this 

class if you want Bootstrap's magic. For example, <button class="btn">

Click Me</button> gives you a basic gray button right away.

This base lets you layer on more styles. It ensures consistency across your site. Think of .btn as the canvas for your button art.

Contextual Color Classes: Setting the Tone (e.g., btn-primary, btn-success)

Colors tell users what to expect. Bootstrap offers classes like .btn-primary for main actions, blue by default. It draws eyes to key spots, like a "Buy Now" button.

Then there's .btn-success in green. Use it for confirmations, such as "Save Changes." It signals all is good. Red .btn-danger warns of risks, perfect for "Delete Account."

You also get .btn-warning in yellow for cautions, like "Edit Profile." Blue .btn-info shares details, gray .btn-secondary plays support roles. Light .btn-light and dark .btn-dark fit subtle needs.

  • Primary: Bold calls to action.
  • Success: Positive outcomes.
  • Danger: Risky steps.
  • Warning: Alerts.
  • Info: Helpful info.
  • Secondary: Neutral options.
  • Light/Dark: Background blends.

These classes tie into user feelings. Green calms, red grabs attention. Pick based on your message to boost UX.

Outline vs. Solid Buttons: Toggle Styling Options

Solid buttons grab focus with full color fills. Outline versions use borders only, keeping things light. Classes like .btn-outline-primary give a blue border on a clear background.

Use outlines when you want less clutter. They shine in crowded forms or sidebars. Solid buttons work for stand-alone spots, like hero sections.

Hover turns outlines solid in Bootstrap, adding life. This subtle shift keeps users engaged. Compare: <button class="btn btn-outline-success">Save</button> vs. the filled one. Outlines save space and feel modern.

When do you choose? Outlines for secondary actions in toolbars. Solids for primary tasks. Mix them for layered designs that guide the eye.

Advanced Button Styling: Sizing and State Modifications

Now let's size up your buttons. Bootstrap makes scaling easy for different screens. States add smarts, like disabling clicks when needed.

Controlling Button Dimensions with Size Modifiers

Big buttons help on touch devices. The .btn-lg class pumps up padding and font size. It creates targets at least 44 pixels wide, ideal for thumbs on phones.

Small .btn-sm shrinks things down. Use it for inline lists or dense menus. It keeps space tight without losing readability.

Pair sizes with contexts. Large primary buttons in mobile CTAs. Small ones in desktop nav bars. Here's a quick setup:

<button class="btn btn-primary btn-lg">
Large Action</button>
<button class="btn btn-secondary btn-sm">
Small Link</button>

Test on devices. Large sizes cut mobile errors by up to 20%, per UX studies. They make your site friendlier.

Button States: Active, Disabled, and Hover Effects

Bootstrap handles hover and focus out of the box. Mouse over a button, and it darkens or glows. This feedback reassures users their click registered.

For active states, add .active. It mimics a pressed look, great for toggles. Disabled buttons need the disabled attribute plus .disabled class. They gray out and block clicks.

Why bother? Clear states prevent mistakes. Imagine a form submit button that stays lit until valid. Use .active for selected tabs. Disabled for loading spinners.

In code: <button class="btn btn-primary disabled">Wait</button>. Focus outlines aid keyboard users. These touches build trust.

Block Level Buttons for Full Width Layouts

Full-width buttons fill their container. The .btn-block class does this in older Bootstrap versions. In v5, use .w-100 utility instead.

They fit mobile forms or drawers. A submit button stretching edge to edge feels natural to tap. Skip them in grids; they can overwhelm.

Picture a login screen. <button class="btn btn-primary w-100">Sign In</button> spans the width. It boosts completion rates on small screens. Use sparingly for impact.

Integrating Buttons with Layouts: Button Groups and Toggles

Buttons don't stand alone. Groups tie them into bars or menus. Toggles turn them into switches.

Creating Cohesive Button Groups with .btn-group

Wrap buttons in <div class="btn-group">. Bootstrap kills gaps between them. They touch for a unified bar.

This setup suits nav or tools. Add .btn classes inside. For example:

<div class="btn-group">
  <button class="btn btn-outline-secondary">
Left</button>
  <button class="btn btn-outline-secondary">
Middle</button>
  <button class="btn btn-outline-secondary">
Right</button>
</div>

Use <nav> for semantic wins. Groups save space and look pro. They mimic app interfaces users know.

Implementing Radio and Checkbox Button Toggles

Toggles act like form inputs. Hide real <input> with .btn-check. Link via labels for clicks.

For radio: One choice from many. Checkboxes allow multiples. Structure like this:

<input type="checkbox" class="btn-check" 
id="toggle1">
<label class="btn btn-outline-primary"
 for="toggle1">Option 1</label>

Active class shows selection. This emulates native controls without extra JS. 

Great for filters or settings.

Users love the visual pick. It cuts confusion in surveys. Test for screen readers; ARIA helps here.

Vertical Stacking Within Groups

Stack buttons with .btn-group-vertical.

 It aligns them top to bottom. Perfect for sidebars or dropdowns.

Gaps form naturally between rows. Add it to your wrapper: <div class="btn-group-vertical">. Inside, buttons stack clean.

Use in narrow spaces. Think mobile menus or tool panels. It keeps options readable without scrolling wide.

Customization and Accessibility Considerations

Tailor buttons to your brand. Bootstrap flexes with utilities and vars. Don't forget access for all users.

Utility Classes for Spacing and Alignment

Space buttons with m-2 for margins or p-3 for padding. Align with text-center or flex classes.

These tweak solo buttons outside groups. <button class="btn btn-primary mt-3 mx-auto">Centered</button> 

floats it middle with top space.

Mix for polish. Bottom margins separate from text. This fine control beats rigid styles. Your layout breathes easy.

Customizing Colors with Sass Variables (Advanced Tip)

Go deep with Sass. Edit $theme-colors map in your build. Swap primary blue for your brand green.

Avoid CSS overrides; they fight Bootstrap updates. Compile fresh for clean code. This keeps buttons unique yet true to the framework.

Tools like Bootstrap's docs guide vars. Test shades for contrast. 

Your site stands out without breaking.

Accessibility (A11Y) Best Practices for Buttons

Stick to <button> for actions, <a> for links. Add role="button" if needed. ARIA labels describe states, like aria-pressed for toggles.

Keyboard focus matters. Skip fancy images; text buttons read better. Colors? Ensure 4.5:1 contrast ratios.

Why care? One in four web users has disabilities. Good A11Y widens your reach. Tools like WAVE check issues fast.

Conclusion: Rapid Prototyping and Consistent Design

Bootstrap CSS buttons reference unlocks quick builds. From basic .btn to groups and toggles, classes speed your work. You control styles, sizes, and states with ease.

Key points: Start with context colors for meaning. Size for devices. Handle states for clarity. Groups unite multiples. Customize smart, access first.

This system cuts dev time by half, per surveys. Your interfaces stay pro and welcoming. Grab Bootstrap, code a button group today, and see the difference. Your users will thank you.

Java program to calculate Profit or Loss based on Cost Price (CP) and Selling Price (SP).

 

Here is a simple and clean Java program to calculate Profit or Loss based on Cost Price (CP) and Selling Price (SP).


You can run it in any Java compiler (online/offline).

Java program to calculate Profit or Loss based on Cost Price (CP) and Selling Price (SP).


Java Program for Profit and Loss

import java.util.Scanner;

public class ProfitLossCalculator {
    public static void main(String[] args) {

        Scanner sc = new Scanner(System.in);

        // Input cost price and selling price
        System.out.print("Enter Cost Price (CP): ");
        double cp = sc.nextDouble();

        System.out.print("Enter Selling Price (SP): ");
        double sp = sc.nextDouble();

        // Calculate profit or loss
        if (sp > cp) {
            double profit = sp - cp;
            double profitPercent = (profit / cp) * 100;

            System.out.println("Profit: " + profit);
            System.out.println("Profit Percentage: " + profitPercent + "%");

        } else if (cp > sp) {
            double loss = cp - sp;
            double lossPercent = (loss / cp) * 100;

            System.out.println("Loss: " + loss);
            System.out.println("Loss Percentage: " + lossPercent + "%");

        } else {
            System.out.println("No Profit, No Loss.");
        }

        sc.close();
    }
}

How This Works

  • If Selling Price > Cost Price → Profit
  • If Cost Price > Selling Price → Loss
  • If both are equal → No Profit, No Loss

Percentages are calculated by:

Profit % = (Profit / CP) × 100
Loss % = (Loss / CP) × 100

⭐ Sample Output

Enter Cost Price (CP): 500
Enter Selling Price (SP): 650
Profit: 150.0
Profit Percentage: 30.0%


Spring Boot Unit Tests Explanation Guide with full code

 

Spring Boot Unit Tests Explanation Guide with full code

Spring Boot Unit Tests Explanation Guide with full code


✅ 1. Spring Boot Unit Tests

Create folder:

src/test/java/com/example/profitloss/

Test 1 — Service Logic Test (CalculationServiceTest.java)

package com.example.profitloss;

import com.example.profitloss.model.
Calculation;
import com.example.profitloss.repo.
CalculationRepository;
import com.example.profitloss.service.
CalculationService;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import static org.junit.jupiter.
api.Assertions.*;

public class CalculationServiceTest {

    private final CalculationRepository
 repo = Mockito.mock(CalculationRepository.
class);
    private final CalculationService 
service = new CalculationService(repo);

    @Test
    void testProfit() {
        double cp = 500;
        double sp = 650;

        Calculation saved = new 
Calculation(cp, sp, 150, 30, true);
        Mockito.when(repo.save
(Mockito.any())).thenReturn(saved);

        Calculation calc = service.
calculateAndSave(cp, sp);

        assertEquals(150, calc.getAmount());
        assertEquals(30, calc.getPercent());
        assertTrue(calc.isProfit());
    }

    @Test
    void testLoss() {
        double cp = 500;
        double sp = 300;

        Calculation saved = new 
Calculation(cp, sp, 200, 40, false);
        Mockito.when(repo.save
(Mockito.any())).thenReturn(saved);

        Calculation calc = service.
calculateAndSave(cp, sp);

        assertEquals(200, calc.getAmount());
        assertEquals(40, calc.getPercent());
        assertFalse(calc.isProfit());
    }

    @Test
    void testNoProfitNoLoss() {
        double cp = 500;
        double sp = 500;

        Calculation saved =
 new Calculation(cp, sp, 0, 0, false);
        Mockito.when(repo.save
(Mockito.any())).thenReturn(saved);

        Calculation calc = service.
calculateAndSave(cp, sp);

        assertEquals(0, calc.getAmount());
        assertEquals(0, calc.getPercent());
    }
}

Test 2 — REST API Test Using MockMVC (CalculationControllerTest.java)

package com.example.profitloss;

import com.example.profitloss.
controller.CalculationController;
import com.example.profitloss.
dto.CalculateRequest;
import com.example.profitloss.
model.Calculation;
import com.example.profitloss.
service.CalculationService;

import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.
beans.factory.annotation.Autowired;
import org.springframework.
boot.test.autoconfigure.web.
servlet.WebMvcTest;
import org.springframework.
boot.test.mock.mockito.MockBean;
import org.springframework.
http.MediaType;
import org.springframework.
test.web.servlet.MockMvc;

import static org.springframework.
test.web.servlet.request.
MockMvcRequestBuilders.post;
import static org.springframework.
test.web.servlet.result.
MockMvcResultMatchers.*;

@WebMvcTest(CalculationController.class)
public class CalculationControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private CalculationService 
calculationService;

    @Test
    void testPostCalculate() 
throws Exception {
        Calculation mockSave =
 new Calculation(500, 650, 150, 30, true);
        mockSave.setId(1L);

        Mockito.when(calculationService.
calculateAndSave(500, 650))
                .thenReturn(mockSave);

        mockMvc.perform(
                post("/api/calculate")
                    .contentType
(MediaType.APPLICATION_JSON)
                    .content
("{\"cp\":500,\"sp\":650}")
        )
        .andExpect(status().isOk())
        .andExpect(jsonPath("$.type")
.value("profit"))
        .andExpect(jsonPath("$.amount")
.value(150.0))
        .andExpect(jsonPath("$.percent")
.value(30.0))
        .andExpect(jsonPath("$.id")
.value(1));
    }
}

✅ 2. Retrofit Integration (Android Kotlin)

Integrate your Android app with the REST API running on Spring Boot.

Step 1: Add Retrofit dependency

In app/build.gradle:

implementation("com.squareup.
retrofit2:retrofit:2.9.0")
implementation("com.squareup.
retrofit2:converter-gson:2.9.0")

Step 2: Create Data Models

CalculateRequest.kt

data class CalculateRequest(
    val cp: Double,
    val sp: Double
)

CalculateResponse.kt

data class CalculateResponse(
    val type: String,
    val amount: Double,
    val percent: Double,
    val id: Long
)

Step 3: Retrofit API Interface

ProfitLossApi.kt

import retrofit2.Call
import retrofit2.http.Body
import retrofit2.http.POST

interface ProfitLossApi {
    @POST("/api/calculate")
    fun calculate(@Body req: 
CalculateRequest): Call<CalculateResponse>
}

Step 4: Retrofit Client

RetrofitClient.kt

import retrofit2.Retrofit
import retrofit2.converter.
gson.GsonConverterFactory

object RetrofitClient {
    val instance: ProfitLossApi by lazy {
        Retrofit.Builder()
            .baseUrl("http://10.0.2.2:8080")
 // Android emulator → localhost
            .addConverterFactory
(GsonConverterFactory.create())
            .build()
            .create(ProfitLossApi:
:class.java)
    }
}

Step 5: Call the API from MainActivity

RetrofitClient.instance.calculate
(CalculateRequest(cp, sp))
    .enqueue(object : retrofit2.
Callback<CalculateResponse> {
        override fun onResponse(
            call: retrofit2.Call<
CalculateResponse>,
            response: retrofit2.
Response<CalculateResponse>
        ) {
            val body = response.body()
            if (body != null) {
                tvResult.text = """
                    Type: ${body.type}
                    Amount: ${body.amount}
                    Percent: ${body.percent}
                    Saved ID: ${body.id}
                """.trimIndent()
            }
        }

        override fun onFailure(call:
 retrofit2.Call<CalculateResponse>,
 t: Throwable) {
            tvResult.text = "Error: 
${t.message}"
        }
    })

✅ 3. Postman Collection (Export JSON)

Copy and paste into a .json file and import into Postman.

profitloss.postman_collection.json

{
  "info": {
    "name": "Profit Loss API",
    "_postman_id": "a1b2c3d4",
    "schema": "https://schema.
getpostman.com/json/collection/v2.1.0/
collection.json"
  },
  "item": [
    {
      "name": "Calculate Profit or Loss",
      "request": {
        "method": "POST",
        "header": [{"key": "Content-Type",
 "value": "application/json"}],
        "body": {
          "mode": "raw",
          "raw": "{\"cp\":500,\"sp\":650}"
        },
        "url": "http://localhost:
8080/api/calculate"
      }
    },
    {
      "name": "Get Saved Calculation",
      "request": {
        "method": "GET",
        "url": "http://localhost:
8080/api/calculation/1"
      }
    }
  ]
}

✅ 4. OpenAPI / Swagger Specification (openapi.yaml)

openapi: 3.0.1
info:
  title: Profit & Loss API
  version: 1.0.0

paths:
  /api/calculate:
    post:
      summary: Calculate and save profit/loss
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                cp:
                  type: number
                sp:
                  type: number
      responses:
        "200":
          description: Calculation result
          content:
            application/json:
              schema:
                type: object
                properties:
                  type:
                    type: string
                  amount:
                    type: number
                  percent:
                    type: number
                  id:
                    type: integer

  /api/calculation/{id}:
    get:
      summary: Get saved calculation by ID
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Calculation found
        "404":
          description: Not found

If you include SpringDoc OpenAPI UI, you get Swagger UI automatically:

Add to pom.xml:

<dependency>
  <groupId>org.springdoc</groupId>
  <artifactId>springdoc-openapi-
starter-webmvc-ui</artifactId>
  <version>2.2.0</version>
</dependency>

Then open:

👉 http://localhost:8080/swagger-ui.html

🎉 All items completed!

You now have:

✔ Unit Tests
✔ Retrofit Android integration
✔ Postman Collection
✔ OpenAPI/Swagger Spec

BEST OF LUCK ...

YouTube Gives Creators Smarter Ad Targeting: What It Means for the Creator Economy

  YouTube Gives Creators Smarter Ad Targeting: What It Means for the Creator Economy In an era where content creation is no longer a hobby...